go.dev: stop being a submodule Now that x/website is Go 1.15-safe, we can make go.dev a plain subdirectory and use x/website's copy of the template packages instead of the go.dev copy. Change-Id: I55a1c3d47b6a96cedfd6eab260533d4e4b28ea88 Reviewed-on: https://go-review.googlesource.com/c/website/+/323893 Trust: Russ Cox <rsc@golang.org> Reviewed-by: Jamal Carvalho <jamal@golang.org>
diff --git a/go.dev/cmd/internal/fmtsort/export_test.go b/go.dev/cmd/internal/fmtsort/export_test.go deleted file mode 100644 index 25cbb5d..0000000 --- a/go.dev/cmd/internal/fmtsort/export_test.go +++ /dev/null
@@ -1,11 +0,0 @@ -// Copyright 2018 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package fmtsort - -import "reflect" - -func Compare(a, b reflect.Value) int { - return compare(a, b) -}
diff --git a/go.dev/cmd/internal/fmtsort/sort.go b/go.dev/cmd/internal/fmtsort/sort.go deleted file mode 100644 index 7127ba6..0000000 --- a/go.dev/cmd/internal/fmtsort/sort.go +++ /dev/null
@@ -1,220 +0,0 @@ -// Copyright 2018 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -// Package fmtsort provides a general stable ordering mechanism -// for maps, on behalf of the fmt and text/template packages. -// It is not guaranteed to be efficient and works only for types -// that are valid map keys. -package fmtsort - -import ( - "reflect" - "sort" -) - -// Note: Throughout this package we avoid calling reflect.Value.Interface as -// it is not always legal to do so and it's easier to avoid the issue than to face it. - -// SortedMap represents a map's keys and values. The keys and values are -// aligned in index order: Value[i] is the value in the map corresponding to Key[i]. -type SortedMap struct { - Key []reflect.Value - Value []reflect.Value -} - -func (o *SortedMap) Len() int { return len(o.Key) } -func (o *SortedMap) Less(i, j int) bool { return compare(o.Key[i], o.Key[j]) < 0 } -func (o *SortedMap) Swap(i, j int) { - o.Key[i], o.Key[j] = o.Key[j], o.Key[i] - o.Value[i], o.Value[j] = o.Value[j], o.Value[i] -} - -// Sort accepts a map and returns a SortedMap that has the same keys and -// values but in a stable sorted order according to the keys, modulo issues -// raised by unorderable key values such as NaNs. -// -// The ordering rules are more general than with Go's < operator: -// -// - when applicable, nil compares low -// - ints, floats, and strings order by < -// - NaN compares less than non-NaN floats -// - bool compares false before true -// - complex compares real, then imag -// - pointers compare by machine address -// - channel values compare by machine address -// - structs compare each field in turn -// - arrays compare each element in turn. -// Otherwise identical arrays compare by length. -// - interface values compare first by reflect.Type describing the concrete type -// and then by concrete value as described in the previous rules. -// -func Sort(mapValue reflect.Value) *SortedMap { - if mapValue.Type().Kind() != reflect.Map { - return nil - } - // Note: this code is arranged to not panic even in the presence - // of a concurrent map update. The runtime is responsible for - // yelling loudly if that happens. See issue 33275. - n := mapValue.Len() - key := make([]reflect.Value, 0, n) - value := make([]reflect.Value, 0, n) - iter := mapValue.MapRange() - for iter.Next() { - key = append(key, iter.Key()) - value = append(value, iter.Value()) - } - sorted := &SortedMap{ - Key: key, - Value: value, - } - sort.Stable(sorted) - return sorted -} - -// compare compares two values of the same type. It returns -1, 0, 1 -// according to whether a > b (1), a == b (0), or a < b (-1). -// If the types differ, it returns -1. -// See the comment on Sort for the comparison rules. -func compare(aVal, bVal reflect.Value) int { - aType, bType := aVal.Type(), bVal.Type() - if aType != bType { - return -1 // No good answer possible, but don't return 0: they're not equal. - } - switch aVal.Kind() { - case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: - a, b := aVal.Int(), bVal.Int() - switch { - case a < b: - return -1 - case a > b: - return 1 - default: - return 0 - } - case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: - a, b := aVal.Uint(), bVal.Uint() - switch { - case a < b: - return -1 - case a > b: - return 1 - default: - return 0 - } - case reflect.String: - a, b := aVal.String(), bVal.String() - switch { - case a < b: - return -1 - case a > b: - return 1 - default: - return 0 - } - case reflect.Float32, reflect.Float64: - return floatCompare(aVal.Float(), bVal.Float()) - case reflect.Complex64, reflect.Complex128: - a, b := aVal.Complex(), bVal.Complex() - if c := floatCompare(real(a), real(b)); c != 0 { - return c - } - return floatCompare(imag(a), imag(b)) - case reflect.Bool: - a, b := aVal.Bool(), bVal.Bool() - switch { - case a == b: - return 0 - case a: - return 1 - default: - return -1 - } - case reflect.Ptr, reflect.UnsafePointer: - a, b := aVal.Pointer(), bVal.Pointer() - switch { - case a < b: - return -1 - case a > b: - return 1 - default: - return 0 - } - case reflect.Chan: - if c, ok := nilCompare(aVal, bVal); ok { - return c - } - ap, bp := aVal.Pointer(), bVal.Pointer() - switch { - case ap < bp: - return -1 - case ap > bp: - return 1 - default: - return 0 - } - case reflect.Struct: - for i := 0; i < aVal.NumField(); i++ { - if c := compare(aVal.Field(i), bVal.Field(i)); c != 0 { - return c - } - } - return 0 - case reflect.Array: - for i := 0; i < aVal.Len(); i++ { - if c := compare(aVal.Index(i), bVal.Index(i)); c != 0 { - return c - } - } - return 0 - case reflect.Interface: - if c, ok := nilCompare(aVal, bVal); ok { - return c - } - c := compare(reflect.ValueOf(aVal.Elem().Type()), reflect.ValueOf(bVal.Elem().Type())) - if c != 0 { - return c - } - return compare(aVal.Elem(), bVal.Elem()) - default: - // Certain types cannot appear as keys (maps, funcs, slices), but be explicit. - panic("bad type in compare: " + aType.String()) - } -} - -// nilCompare checks whether either value is nil. If not, the boolean is false. -// If either value is nil, the boolean is true and the integer is the comparison -// value. The comparison is defined to be 0 if both are nil, otherwise the one -// nil value compares low. Both arguments must represent a chan, func, -// interface, map, pointer, or slice. -func nilCompare(aVal, bVal reflect.Value) (int, bool) { - if aVal.IsNil() { - if bVal.IsNil() { - return 0, true - } - return -1, true - } - if bVal.IsNil() { - return 1, true - } - return 0, false -} - -// floatCompare compares two floating-point values. NaNs compare low. -func floatCompare(a, b float64) int { - switch { - case isNaN(a): - return -1 // No good answer if b is a NaN so don't bother checking. - case isNaN(b): - return 1 - case a < b: - return -1 - case a > b: - return 1 - } - return 0 -} - -func isNaN(a float64) bool { - return a != a -}
diff --git a/go.dev/cmd/internal/fmtsort/sort_test.go b/go.dev/cmd/internal/fmtsort/sort_test.go deleted file mode 100644 index 5c73fce..0000000 --- a/go.dev/cmd/internal/fmtsort/sort_test.go +++ /dev/null
@@ -1,269 +0,0 @@ -// Copyright 2018 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package fmtsort_test - -import ( - "fmt" - "math" - "reflect" - "strings" - "testing" - "unsafe" - - "golang.org/x/website/go.dev/cmd/internal/fmtsort" -) - -var compareTests = [][]reflect.Value{ - ct(reflect.TypeOf(int(0)), -1, 0, 1), - ct(reflect.TypeOf(int8(0)), -1, 0, 1), - ct(reflect.TypeOf(int16(0)), -1, 0, 1), - ct(reflect.TypeOf(int32(0)), -1, 0, 1), - ct(reflect.TypeOf(int64(0)), -1, 0, 1), - ct(reflect.TypeOf(uint(0)), 0, 1, 5), - ct(reflect.TypeOf(uint8(0)), 0, 1, 5), - ct(reflect.TypeOf(uint16(0)), 0, 1, 5), - ct(reflect.TypeOf(uint32(0)), 0, 1, 5), - ct(reflect.TypeOf(uint64(0)), 0, 1, 5), - ct(reflect.TypeOf(uintptr(0)), 0, 1, 5), - ct(reflect.TypeOf(string("")), "", "a", "ab"), - ct(reflect.TypeOf(float32(0)), math.NaN(), math.Inf(-1), -1e10, 0, 1e10, math.Inf(1)), - ct(reflect.TypeOf(float64(0)), math.NaN(), math.Inf(-1), -1e10, 0, 1e10, math.Inf(1)), - ct(reflect.TypeOf(complex64(0+1i)), -1-1i, -1+0i, -1+1i, 0-1i, 0+0i, 0+1i, 1-1i, 1+0i, 1+1i), - ct(reflect.TypeOf(complex128(0+1i)), -1-1i, -1+0i, -1+1i, 0-1i, 0+0i, 0+1i, 1-1i, 1+0i, 1+1i), - ct(reflect.TypeOf(false), false, true), - ct(reflect.TypeOf(&ints[0]), &ints[0], &ints[1], &ints[2]), - ct(reflect.TypeOf(unsafe.Pointer(&ints[0])), unsafe.Pointer(&ints[0]), unsafe.Pointer(&ints[1]), unsafe.Pointer(&ints[2])), - ct(reflect.TypeOf(chans[0]), chans[0], chans[1], chans[2]), - ct(reflect.TypeOf(toy{}), toy{0, 1}, toy{0, 2}, toy{1, -1}, toy{1, 1}), - ct(reflect.TypeOf([2]int{}), [2]int{1, 1}, [2]int{1, 2}, [2]int{2, 0}), - ct(reflect.TypeOf(interface{}(interface{}(0))), iFace, 1, 2, 3), -} - -var iFace interface{} - -func ct(typ reflect.Type, args ...interface{}) []reflect.Value { - value := make([]reflect.Value, len(args)) - for i, v := range args { - x := reflect.ValueOf(v) - if !x.IsValid() { // Make it a typed nil. - x = reflect.Zero(typ) - } else { - x = x.Convert(typ) - } - value[i] = x - } - return value -} - -func TestCompare(t *testing.T) { - for _, test := range compareTests { - for i, v0 := range test { - for j, v1 := range test { - c := fmtsort.Compare(v0, v1) - var expect int - switch { - case i == j: - expect = 0 - // NaNs are tricky. - if typ := v0.Type(); (typ.Kind() == reflect.Float32 || typ.Kind() == reflect.Float64) && math.IsNaN(v0.Float()) { - expect = -1 - } - case i < j: - expect = -1 - case i > j: - expect = 1 - } - if c != expect { - t.Errorf("%s: compare(%v,%v)=%d; expect %d", v0.Type(), v0, v1, c, expect) - } - } - } - } -} - -type sortTest struct { - data interface{} // Always a map. - print string // Printed result using our custom printer. -} - -var sortTests = []sortTest{ - { - map[int]string{7: "bar", -3: "foo"}, - "-3:foo 7:bar", - }, - { - map[uint8]string{7: "bar", 3: "foo"}, - "3:foo 7:bar", - }, - { - map[string]string{"7": "bar", "3": "foo"}, - "3:foo 7:bar", - }, - { - map[float64]string{7: "bar", -3: "foo", math.NaN(): "nan", math.Inf(0): "inf"}, - "NaN:nan -3:foo 7:bar +Inf:inf", - }, - { - map[complex128]string{7 + 2i: "bar2", 7 + 1i: "bar", -3: "foo", complex(math.NaN(), 0i): "nan", complex(math.Inf(0), 0i): "inf"}, - "(NaN+0i):nan (-3+0i):foo (7+1i):bar (7+2i):bar2 (+Inf+0i):inf", - }, - { - map[bool]string{true: "true", false: "false"}, - "false:false true:true", - }, - { - chanMap(), - "CHAN0:0 CHAN1:1 CHAN2:2", - }, - { - pointerMap(), - "PTR0:0 PTR1:1 PTR2:2", - }, - { - unsafePointerMap(), - "UNSAFEPTR0:0 UNSAFEPTR1:1 UNSAFEPTR2:2", - }, - { - map[toy]string{{7, 2}: "72", {7, 1}: "71", {3, 4}: "34"}, - "{3 4}:34 {7 1}:71 {7 2}:72", - }, - { - map[[2]int]string{{7, 2}: "72", {7, 1}: "71", {3, 4}: "34"}, - "[3 4]:34 [7 1]:71 [7 2]:72", - }, -} - -func sprint(data interface{}) string { - om := fmtsort.Sort(reflect.ValueOf(data)) - if om == nil { - return "nil" - } - b := new(strings.Builder) - for i, key := range om.Key { - if i > 0 { - b.WriteRune(' ') - } - b.WriteString(sprintKey(key)) - b.WriteRune(':') - b.WriteString(fmt.Sprint(om.Value[i])) - } - return b.String() -} - -// sprintKey formats a reflect.Value but gives reproducible values for some -// problematic types such as pointers. Note that it only does special handling -// for the troublesome types used in the test cases; it is not a general -// printer. -func sprintKey(key reflect.Value) string { - switch str := key.Type().String(); str { - case "*int": - ptr := key.Interface().(*int) - for i := range ints { - if ptr == &ints[i] { - return fmt.Sprintf("PTR%d", i) - } - } - return "PTR???" - case "unsafe.Pointer": - ptr := key.Interface().(unsafe.Pointer) - for i := range ints { - if ptr == unsafe.Pointer(&ints[i]) { - return fmt.Sprintf("UNSAFEPTR%d", i) - } - } - return "UNSAFEPTR???" - case "chan int": - c := key.Interface().(chan int) - for i := range chans { - if c == chans[i] { - return fmt.Sprintf("CHAN%d", i) - } - } - return "CHAN???" - default: - return fmt.Sprint(key) - } -} - -var ( - ints [3]int - chans = [3]chan int{make(chan int), make(chan int), make(chan int)} -) - -func pointerMap() map[*int]string { - m := make(map[*int]string) - for i := 2; i >= 0; i-- { - m[&ints[i]] = fmt.Sprint(i) - } - return m -} - -func unsafePointerMap() map[unsafe.Pointer]string { - m := make(map[unsafe.Pointer]string) - for i := 2; i >= 0; i-- { - m[unsafe.Pointer(&ints[i])] = fmt.Sprint(i) - } - return m -} - -func chanMap() map[chan int]string { - m := make(map[chan int]string) - for i := 2; i >= 0; i-- { - m[chans[i]] = fmt.Sprint(i) - } - return m -} - -type toy struct { - A int // Exported. - b int // Unexported. -} - -func TestOrder(t *testing.T) { - for _, test := range sortTests { - got := sprint(test.data) - if got != test.print { - t.Errorf("%s: got %q, want %q", reflect.TypeOf(test.data), got, test.print) - } - } -} - -func TestInterface(t *testing.T) { - // A map containing multiple concrete types should be sorted by type, - // then value. However, the relative ordering of types is unspecified, - // so test this by checking the presence of sorted subgroups. - m := map[interface{}]string{ - [2]int{1, 0}: "", - [2]int{0, 1}: "", - true: "", - false: "", - 3.1: "", - 2.1: "", - 1.1: "", - math.NaN(): "", - 3: "", - 2: "", - 1: "", - "c": "", - "b": "", - "a": "", - struct{ x, y int }{1, 0}: "", - struct{ x, y int }{0, 1}: "", - } - got := sprint(m) - typeGroups := []string{ - "NaN: 1.1: 2.1: 3.1:", // float64 - "false: true:", // bool - "1: 2: 3:", // int - "a: b: c:", // string - "[0 1]: [1 0]:", // [2]int - "{0 1}: {1 0}:", // struct{ x int; y int } - } - for _, g := range typeGroups { - if !strings.Contains(got, g) { - t.Errorf("sorted map should contain %q", g) - } - } -}
diff --git a/go.dev/cmd/internal/html/template/attr.go b/go.dev/cmd/internal/html/template/attr.go deleted file mode 100644 index 22922e6..0000000 --- a/go.dev/cmd/internal/html/template/attr.go +++ /dev/null
@@ -1,175 +0,0 @@ -// Copyright 2011 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package template - -import ( - "strings" -) - -// attrTypeMap[n] describes the value of the given attribute. -// If an attribute affects (or can mask) the encoding or interpretation of -// other content, or affects the contents, idempotency, or credentials of a -// network message, then the value in this map is contentTypeUnsafe. -// This map is derived from HTML5, specifically -// https://www.w3.org/TR/html5/Overview.html#attributes-1 -// as well as "%URI"-typed attributes from -// https://www.w3.org/TR/html4/index/attributes.html -var attrTypeMap = map[string]contentType{ - "accept": contentTypePlain, - "accept-charset": contentTypeUnsafe, - "action": contentTypeURL, - "alt": contentTypePlain, - "archive": contentTypeURL, - "async": contentTypeUnsafe, - "autocomplete": contentTypePlain, - "autofocus": contentTypePlain, - "autoplay": contentTypePlain, - "background": contentTypeURL, - "border": contentTypePlain, - "checked": contentTypePlain, - "cite": contentTypeURL, - "challenge": contentTypeUnsafe, - "charset": contentTypeUnsafe, - "class": contentTypePlain, - "classid": contentTypeURL, - "codebase": contentTypeURL, - "cols": contentTypePlain, - "colspan": contentTypePlain, - "content": contentTypeUnsafe, - "contenteditable": contentTypePlain, - "contextmenu": contentTypePlain, - "controls": contentTypePlain, - "coords": contentTypePlain, - "crossorigin": contentTypeUnsafe, - "data": contentTypeURL, - "datetime": contentTypePlain, - "default": contentTypePlain, - "defer": contentTypeUnsafe, - "dir": contentTypePlain, - "dirname": contentTypePlain, - "disabled": contentTypePlain, - "draggable": contentTypePlain, - "dropzone": contentTypePlain, - "enctype": contentTypeUnsafe, - "for": contentTypePlain, - "form": contentTypeUnsafe, - "formaction": contentTypeURL, - "formenctype": contentTypeUnsafe, - "formmethod": contentTypeUnsafe, - "formnovalidate": contentTypeUnsafe, - "formtarget": contentTypePlain, - "headers": contentTypePlain, - "height": contentTypePlain, - "hidden": contentTypePlain, - "high": contentTypePlain, - "href": contentTypeURL, - "hreflang": contentTypePlain, - "http-equiv": contentTypeUnsafe, - "icon": contentTypeURL, - "id": contentTypePlain, - "ismap": contentTypePlain, - "keytype": contentTypeUnsafe, - "kind": contentTypePlain, - "label": contentTypePlain, - "lang": contentTypePlain, - "language": contentTypeUnsafe, - "list": contentTypePlain, - "longdesc": contentTypeURL, - "loop": contentTypePlain, - "low": contentTypePlain, - "manifest": contentTypeURL, - "max": contentTypePlain, - "maxlength": contentTypePlain, - "media": contentTypePlain, - "mediagroup": contentTypePlain, - "method": contentTypeUnsafe, - "min": contentTypePlain, - "multiple": contentTypePlain, - "name": contentTypePlain, - "novalidate": contentTypeUnsafe, - // Skip handler names from - // https://www.w3.org/TR/html5/webappapis.html#event-handlers-on-elements,-document-objects,-and-window-objects - // since we have special handling in attrType. - "open": contentTypePlain, - "optimum": contentTypePlain, - "pattern": contentTypeUnsafe, - "placeholder": contentTypePlain, - "poster": contentTypeURL, - "profile": contentTypeURL, - "preload": contentTypePlain, - "pubdate": contentTypePlain, - "radiogroup": contentTypePlain, - "readonly": contentTypePlain, - "rel": contentTypeUnsafe, - "required": contentTypePlain, - "reversed": contentTypePlain, - "rows": contentTypePlain, - "rowspan": contentTypePlain, - "sandbox": contentTypeUnsafe, - "spellcheck": contentTypePlain, - "scope": contentTypePlain, - "scoped": contentTypePlain, - "seamless": contentTypePlain, - "selected": contentTypePlain, - "shape": contentTypePlain, - "size": contentTypePlain, - "sizes": contentTypePlain, - "span": contentTypePlain, - "src": contentTypeURL, - "srcdoc": contentTypeHTML, - "srclang": contentTypePlain, - "srcset": contentTypeSrcset, - "start": contentTypePlain, - "step": contentTypePlain, - "style": contentTypeCSS, - "tabindex": contentTypePlain, - "target": contentTypePlain, - "title": contentTypePlain, - "type": contentTypeUnsafe, - "usemap": contentTypeURL, - "value": contentTypeUnsafe, - "width": contentTypePlain, - "wrap": contentTypePlain, - "xmlns": contentTypeURL, -} - -// attrType returns a conservative (upper-bound on authority) guess at the -// type of the lowercase named attribute. -func attrType(name string) contentType { - if strings.HasPrefix(name, "data-") { - // Strip data- so that custom attribute heuristics below are - // widely applied. - // Treat data-action as URL below. - name = name[5:] - } else if colon := strings.IndexRune(name, ':'); colon != -1 { - if name[:colon] == "xmlns" { - return contentTypeURL - } - // Treat svg:href and xlink:href as href below. - name = name[colon+1:] - } - if t, ok := attrTypeMap[name]; ok { - return t - } - // Treat partial event handler names as script. - if strings.HasPrefix(name, "on") { - return contentTypeJS - } - - // Heuristics to prevent "javascript:..." injection in custom - // data attributes and custom attributes like g:tweetUrl. - // https://www.w3.org/TR/html5/dom.html#embedding-custom-non-visible-data-with-the-data-*-attributes - // "Custom data attributes are intended to store custom data - // private to the page or application, for which there are no - // more appropriate attributes or elements." - // Developers seem to store URL content in data URLs that start - // or end with "URI" or "URL". - if strings.Contains(name, "src") || - strings.Contains(name, "uri") || - strings.Contains(name, "url") { - return contentTypeURL - } - return contentTypePlain -}
diff --git a/go.dev/cmd/internal/html/template/attr_string.go b/go.dev/cmd/internal/html/template/attr_string.go deleted file mode 100644 index babe70c..0000000 --- a/go.dev/cmd/internal/html/template/attr_string.go +++ /dev/null
@@ -1,16 +0,0 @@ -// Code generated by "stringer -type attr"; DO NOT EDIT. - -package template - -import "strconv" - -const _attr_name = "attrNoneattrScriptattrScriptTypeattrStyleattrURLattrSrcset" - -var _attr_index = [...]uint8{0, 8, 18, 32, 41, 48, 58} - -func (i attr) String() string { - if i >= attr(len(_attr_index)-1) { - return "attr(" + strconv.FormatInt(int64(i), 10) + ")" - } - return _attr_name[_attr_index[i]:_attr_index[i+1]] -}
diff --git a/go.dev/cmd/internal/html/template/clone_test.go b/go.dev/cmd/internal/html/template/clone_test.go deleted file mode 100644 index c42d241..0000000 --- a/go.dev/cmd/internal/html/template/clone_test.go +++ /dev/null
@@ -1,280 +0,0 @@ -// Copyright 2011 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package template - -import ( - "bytes" - "errors" - "fmt" - "io/ioutil" - "strings" - "sync" - "testing" - - "golang.org/x/website/go.dev/cmd/internal/text/template/parse" -) - -func TestAddParseTreeHTML(t *testing.T) { - root := Must(New("root").Parse(`{{define "a"}} {{.}} {{template "b"}} {{.}} "></a>{{end}}`)) - tree, err := parse.Parse("t", `{{define "b"}}<a href="{{end}}`, "", "", nil, nil) - if err != nil { - t.Fatal(err) - } - added := Must(root.AddParseTree("b", tree["b"])) - b := new(bytes.Buffer) - err = added.ExecuteTemplate(b, "a", "1>0") - if err != nil { - t.Fatal(err) - } - if got, want := b.String(), ` 1>0 <a href=" 1%3e0 "></a>`; got != want { - t.Errorf("got %q want %q", got, want) - } -} - -func TestClone(t *testing.T) { - // The {{.}} will be executed with data "<i>*/" in different contexts. - // In the t0 template, it will be in a text context. - // In the t1 template, it will be in a URL context. - // In the t2 template, it will be in a JavaScript context. - // In the t3 template, it will be in a CSS context. - const tmpl = `{{define "a"}}{{template "lhs"}}{{.}}{{template "rhs"}}{{end}}` - b := new(bytes.Buffer) - - // Create an incomplete template t0. - t0 := Must(New("t0").Parse(tmpl)) - - // Clone t0 as t1. - t1 := Must(t0.Clone()) - Must(t1.Parse(`{{define "lhs"}} <a href=" {{end}}`)) - Must(t1.Parse(`{{define "rhs"}} "></a> {{end}}`)) - - // Execute t1. - b.Reset() - if err := t1.ExecuteTemplate(b, "a", "<i>*/"); err != nil { - t.Fatal(err) - } - if got, want := b.String(), ` <a href=" %3ci%3e*/ "></a> `; got != want { - t.Errorf("t1: got %q want %q", got, want) - } - - // Clone t0 as t2. - t2 := Must(t0.Clone()) - Must(t2.Parse(`{{define "lhs"}} <p onclick="javascript: {{end}}`)) - Must(t2.Parse(`{{define "rhs"}} "></p> {{end}}`)) - - // Execute t2. - b.Reset() - if err := t2.ExecuteTemplate(b, "a", "<i>*/"); err != nil { - t.Fatal(err) - } - if got, want := b.String(), ` <p onclick="javascript: "\u003ci\u003e*/" "></p> `; got != want { - t.Errorf("t2: got %q want %q", got, want) - } - - // Clone t0 as t3, but do not execute t3 yet. - t3 := Must(t0.Clone()) - Must(t3.Parse(`{{define "lhs"}} <style> {{end}}`)) - Must(t3.Parse(`{{define "rhs"}} </style> {{end}}`)) - - // Complete t0. - Must(t0.Parse(`{{define "lhs"}} ( {{end}}`)) - Must(t0.Parse(`{{define "rhs"}} ) {{end}}`)) - - // Clone t0 as t4. Redefining the "lhs" template should not fail. - t4 := Must(t0.Clone()) - if _, err := t4.Parse(`{{define "lhs"}} OK {{end}}`); err != nil { - t.Errorf(`redefine "lhs": got err %v want nil`, err) - } - // Cloning t1 should fail as it has been executed. - if _, err := t1.Clone(); err == nil { - t.Error("cloning t1: got nil err want non-nil") - } - // Redefining the "lhs" template in t1 should fail as it has been executed. - if _, err := t1.Parse(`{{define "lhs"}} OK {{end}}`); err == nil { - t.Error(`redefine "lhs": got nil err want non-nil`) - } - - // Execute t0. - b.Reset() - if err := t0.ExecuteTemplate(b, "a", "<i>*/"); err != nil { - t.Fatal(err) - } - if got, want := b.String(), ` ( <i>*/ ) `; got != want { - t.Errorf("t0: got %q want %q", got, want) - } - - // Clone t0. This should fail, as t0 has already executed. - if _, err := t0.Clone(); err == nil { - t.Error(`t0.Clone(): got nil err want non-nil`) - } - - // Similarly, cloning sub-templates should fail. - if _, err := t0.Lookup("a").Clone(); err == nil { - t.Error(`t0.Lookup("a").Clone(): got nil err want non-nil`) - } - if _, err := t0.Lookup("lhs").Clone(); err == nil { - t.Error(`t0.Lookup("lhs").Clone(): got nil err want non-nil`) - } - - // Execute t3. - b.Reset() - if err := t3.ExecuteTemplate(b, "a", "<i>*/"); err != nil { - t.Fatal(err) - } - if got, want := b.String(), ` <style> ZgotmplZ </style> `; got != want { - t.Errorf("t3: got %q want %q", got, want) - } -} - -func TestTemplates(t *testing.T) { - names := []string{"t0", "a", "lhs", "rhs"} - // Some template definitions borrowed from TestClone. - const tmpl = ` - {{define "a"}}{{template "lhs"}}{{.}}{{template "rhs"}}{{end}} - {{define "lhs"}} <a href=" {{end}} - {{define "rhs"}} "></a> {{end}}` - t0 := Must(New("t0").Parse(tmpl)) - templates := t0.Templates() - if len(templates) != len(names) { - t.Errorf("expected %d templates; got %d", len(names), len(templates)) - } - for _, name := range names { - found := false - for _, tmpl := range templates { - if name == tmpl.text.Name() { - found = true - break - } - } - if !found { - t.Error("could not find template", name) - } - } -} - -// This used to crash; https://golang.org/issue/3281 -func TestCloneCrash(t *testing.T) { - t1 := New("all") - Must(t1.New("t1").Parse(`{{define "foo"}}foo{{end}}`)) - t1.Clone() -} - -// Ensure that this guarantee from the docs is upheld: -// "Further calls to Parse in the copy will add templates -// to the copy but not to the original." -func TestCloneThenParse(t *testing.T) { - t0 := Must(New("t0").Parse(`{{define "a"}}{{template "embedded"}}{{end}}`)) - t1 := Must(t0.Clone()) - Must(t1.Parse(`{{define "embedded"}}t1{{end}}`)) - if len(t0.Templates())+1 != len(t1.Templates()) { - t.Error("adding a template to a clone added it to the original") - } - // double check that the embedded template isn't available in the original - err := t0.ExecuteTemplate(ioutil.Discard, "a", nil) - if err == nil { - t.Error("expected 'no such template' error") - } -} - -// https://golang.org/issue/5980 -func TestFuncMapWorksAfterClone(t *testing.T) { - funcs := FuncMap{"customFunc": func() (string, error) { - return "", errors.New("issue5980") - }} - - // get the expected error output (no clone) - uncloned := Must(New("").Funcs(funcs).Parse("{{customFunc}}")) - wantErr := uncloned.Execute(ioutil.Discard, nil) - - // toClone must be the same as uncloned. It has to be recreated from scratch, - // since cloning cannot occur after execution. - toClone := Must(New("").Funcs(funcs).Parse("{{customFunc}}")) - cloned := Must(toClone.Clone()) - gotErr := cloned.Execute(ioutil.Discard, nil) - - if wantErr.Error() != gotErr.Error() { - t.Errorf("clone error message mismatch want %q got %q", wantErr, gotErr) - } -} - -// https://golang.org/issue/16101 -func TestTemplateCloneExecuteRace(t *testing.T) { - const ( - input = `<title>{{block "a" .}}a{{end}}</title><body>{{block "b" .}}b{{end}}<body>` - overlay = `{{define "b"}}A{{end}}` - ) - outer := Must(New("outer").Parse(input)) - tmpl := Must(Must(outer.Clone()).Parse(overlay)) - - var wg sync.WaitGroup - for i := 0; i < 10; i++ { - wg.Add(1) - go func() { - defer wg.Done() - for i := 0; i < 100; i++ { - if err := tmpl.Execute(ioutil.Discard, "data"); err != nil { - panic(err) - } - } - }() - } - wg.Wait() -} - -func TestTemplateCloneLookup(t *testing.T) { - // Template.escape makes an assumption that the template associated - // with t.Name() is t. Check that this holds. - tmpl := Must(New("x").Parse("a")) - tmpl = Must(tmpl.Clone()) - if tmpl.Lookup(tmpl.Name()) != tmpl { - t.Error("after Clone, tmpl.Lookup(tmpl.Name()) != tmpl") - } -} - -func TestCloneGrowth(t *testing.T) { - tmpl := Must(New("root").Parse(`<title>{{block "B". }}Arg{{end}}</title>`)) - tmpl = Must(tmpl.Clone()) - Must(tmpl.Parse(`{{define "B"}}Text{{end}}`)) - for i := 0; i < 10; i++ { - tmpl.Execute(ioutil.Discard, nil) - } - if len(tmpl.DefinedTemplates()) > 200 { - t.Fatalf("too many templates: %v", len(tmpl.DefinedTemplates())) - } -} - -// https://golang.org/issue/17735 -func TestCloneRedefinedName(t *testing.T) { - const base = ` -{{ define "a" -}}<title>{{ template "b" . -}}</title>{{ end -}} -{{ define "b" }}{{ end -}} -` - const page = `{{ template "a" . }}` - - t1 := Must(New("a").Parse(base)) - - for i := 0; i < 2; i++ { - t2 := Must(t1.Clone()) - t2 = Must(t2.New(fmt.Sprintf("%d", i)).Parse(page)) - err := t2.Execute(ioutil.Discard, nil) - if err != nil { - t.Fatal(err) - } - } -} - -// Issue 24791. -func TestClonePipe(t *testing.T) { - a := Must(New("a").Parse(`{{define "a"}}{{range $v := .A}}{{$v}}{{end}}{{end}}`)) - data := struct{ A []string }{A: []string{"hi"}} - b := Must(a.Clone()) - var buf strings.Builder - if err := b.Execute(&buf, &data); err != nil { - t.Fatal(err) - } - if got, want := buf.String(), "hi"; got != want { - t.Errorf("got %q want %q", got, want) - } -}
diff --git a/go.dev/cmd/internal/html/template/content.go b/go.dev/cmd/internal/html/template/content.go deleted file mode 100644 index 6ba87a9..0000000 --- a/go.dev/cmd/internal/html/template/content.go +++ /dev/null
@@ -1,185 +0,0 @@ -// Copyright 2011 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package template - -import ( - "fmt" - "reflect" -) - -// Strings of content from a trusted source. -type ( - // CSS encapsulates known safe content that matches any of: - // 1. The CSS3 stylesheet production, such as `p { color: purple }`. - // 2. The CSS3 rule production, such as `a[href=~"https:"].foo#bar`. - // 3. CSS3 declaration productions, such as `color: red; margin: 2px`. - // 4. The CSS3 value production, such as `rgba(0, 0, 255, 127)`. - // See https://www.w3.org/TR/css3-syntax/#parsing and - // https://web.archive.org/web/20090211114933/http://w3.org/TR/css3-syntax#style - // - // Use of this type presents a security risk: - // the encapsulated content should come from a trusted source, - // as it will be included verbatim in the template output. - CSS string - - // HTML encapsulates a known safe HTML document fragment. - // It should not be used for HTML from a third-party, or HTML with - // unclosed tags or comments. The outputs of a sound HTML sanitizer - // and a template escaped by this package are fine for use with HTML. - // - // Use of this type presents a security risk: - // the encapsulated content should come from a trusted source, - // as it will be included verbatim in the template output. - HTML string - - // HTMLAttr encapsulates an HTML attribute from a trusted source, - // for example, ` dir="ltr"`. - // - // Use of this type presents a security risk: - // the encapsulated content should come from a trusted source, - // as it will be included verbatim in the template output. - HTMLAttr string - - // JS encapsulates a known safe EcmaScript5 Expression, for example, - // `(x + y * z())`. - // Template authors are responsible for ensuring that typed expressions - // do not break the intended precedence and that there is no - // statement/expression ambiguity as when passing an expression like - // "{ foo: bar() }\n['foo']()", which is both a valid Expression and a - // valid Program with a very different meaning. - // - // Use of this type presents a security risk: - // the encapsulated content should come from a trusted source, - // as it will be included verbatim in the template output. - // - // Using JS to include valid but untrusted JSON is not safe. - // A safe alternative is to parse the JSON with json.Unmarshal and then - // pass the resultant object into the template, where it will be - // converted to sanitized JSON when presented in a JavaScript context. - JS string - - // JSStr encapsulates a sequence of characters meant to be embedded - // between quotes in a JavaScript expression. - // The string must match a series of StringCharacters: - // StringCharacter :: SourceCharacter but not `\` or LineTerminator - // | EscapeSequence - // Note that LineContinuations are not allowed. - // JSStr("foo\\nbar") is fine, but JSStr("foo\\\nbar") is not. - // - // Use of this type presents a security risk: - // the encapsulated content should come from a trusted source, - // as it will be included verbatim in the template output. - JSStr string - - // URL encapsulates a known safe URL or URL substring (see RFC 3986). - // A URL like `javascript:checkThatFormNotEditedBeforeLeavingPage()` - // from a trusted source should go in the page, but by default dynamic - // `javascript:` URLs are filtered out since they are a frequently - // exploited injection vector. - // - // Use of this type presents a security risk: - // the encapsulated content should come from a trusted source, - // as it will be included verbatim in the template output. - URL string - - // Srcset encapsulates a known safe srcset attribute - // (see https://w3c.github.io/html/semantics-embedded-content.html#element-attrdef-img-srcset). - // - // Use of this type presents a security risk: - // the encapsulated content should come from a trusted source, - // as it will be included verbatim in the template output. - Srcset string -) - -type contentType uint8 - -const ( - contentTypePlain contentType = iota - contentTypeCSS - contentTypeHTML - contentTypeHTMLAttr - contentTypeJS - contentTypeJSStr - contentTypeURL - contentTypeSrcset - // contentTypeUnsafe is used in attr.go for values that affect how - // embedded content and network messages are formed, vetted, - // or interpreted; or which credentials network messages carry. - contentTypeUnsafe -) - -// indirect returns the value, after dereferencing as many times -// as necessary to reach the base type (or nil). -func indirect(a interface{}) interface{} { - if a == nil { - return nil - } - if t := reflect.TypeOf(a); t.Kind() != reflect.Ptr { - // Avoid creating a reflect.Value if it's not a pointer. - return a - } - v := reflect.ValueOf(a) - for v.Kind() == reflect.Ptr && !v.IsNil() { - v = v.Elem() - } - return v.Interface() -} - -var ( - errorType = reflect.TypeOf((*error)(nil)).Elem() - fmtStringerType = reflect.TypeOf((*fmt.Stringer)(nil)).Elem() -) - -// indirectToStringerOrError returns the value, after dereferencing as many times -// as necessary to reach the base type (or nil) or an implementation of fmt.Stringer -// or error, -func indirectToStringerOrError(a interface{}) interface{} { - if a == nil { - return nil - } - v := reflect.ValueOf(a) - for !v.Type().Implements(fmtStringerType) && !v.Type().Implements(errorType) && v.Kind() == reflect.Ptr && !v.IsNil() { - v = v.Elem() - } - return v.Interface() -} - -// stringify converts its arguments to a string and the type of the content. -// All pointers are dereferenced, as in the text/template package. -func stringify(args ...interface{}) (string, contentType) { - if len(args) == 1 { - switch s := indirect(args[0]).(type) { - case string: - return s, contentTypePlain - case CSS: - return string(s), contentTypeCSS - case HTML: - return string(s), contentTypeHTML - case HTMLAttr: - return string(s), contentTypeHTMLAttr - case JS: - return string(s), contentTypeJS - case JSStr: - return string(s), contentTypeJSStr - case URL: - return string(s), contentTypeURL - case Srcset: - return string(s), contentTypeSrcset - } - } - i := 0 - for _, arg := range args { - // We skip untyped nil arguments for backward compatibility. - // Without this they would be output as <nil>, escaped. - // See issue 25875. - if arg == nil { - continue - } - - args[i] = indirectToStringerOrError(arg) - i++ - } - return fmt.Sprint(args[:i]...), contentTypePlain -}
diff --git a/go.dev/cmd/internal/html/template/content_test.go b/go.dev/cmd/internal/html/template/content_test.go deleted file mode 100644 index 70cbd4e..0000000 --- a/go.dev/cmd/internal/html/template/content_test.go +++ /dev/null
@@ -1,458 +0,0 @@ -// Copyright 2011 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package template - -import ( - "bytes" - "fmt" - "strings" - "testing" -) - -func TestTypedContent(t *testing.T) { - data := []interface{}{ - `<b> "foo%" O'Reilly &bar;`, - CSS(`a[href =~ "//example.com"]#foo`), - HTML(`Hello, <b>World</b> &tc!`), - HTMLAttr(` dir="ltr"`), - JS(`c && alert("Hello, World!");`), - JSStr(`Hello, World & O'Reilly\u0021`), - URL(`greeting=H%69,&addressee=(World)`), - Srcset(`greeting=H%69,&addressee=(World) 2x, https://golang.org/favicon.ico 500.5w`), - URL(`,foo/,`), - } - - // For each content sensitive escaper, see how it does on - // each of the typed strings above. - tests := []struct { - // A template containing a single {{.}}. - input string - want []string - }{ - { - `<style>{{.}} { color: blue }</style>`, - []string{ - `ZgotmplZ`, - // Allowed but not escaped. - `a[href =~ "//example.com"]#foo`, - `ZgotmplZ`, - `ZgotmplZ`, - `ZgotmplZ`, - `ZgotmplZ`, - `ZgotmplZ`, - `ZgotmplZ`, - `ZgotmplZ`, - }, - }, - { - `<div style="{{.}}">`, - []string{ - `ZgotmplZ`, - // Allowed and HTML escaped. - `a[href =~ "//example.com"]#foo`, - `ZgotmplZ`, - `ZgotmplZ`, - `ZgotmplZ`, - `ZgotmplZ`, - `ZgotmplZ`, - `ZgotmplZ`, - `ZgotmplZ`, - }, - }, - { - `{{.}}`, - []string{ - `<b> "foo%" O'Reilly &bar;`, - `a[href =~ "//example.com"]#foo`, - // Not escaped. - `Hello, <b>World</b> &tc!`, - ` dir="ltr"`, - `c && alert("Hello, World!");`, - `Hello, World & O'Reilly\u0021`, - `greeting=H%69,&addressee=(World)`, - `greeting=H%69,&addressee=(World) 2x, https://golang.org/favicon.ico 500.5w`, - `,foo/,`, - }, - }, - { - `<a{{.}}>`, - []string{ - `ZgotmplZ`, - `ZgotmplZ`, - `ZgotmplZ`, - // Allowed and HTML escaped. - ` dir="ltr"`, - `ZgotmplZ`, - `ZgotmplZ`, - `ZgotmplZ`, - `ZgotmplZ`, - `ZgotmplZ`, - }, - }, - { - `<a title={{.}}>`, - []string{ - `<b> "foo%" O'Reilly &bar;`, - `a[href =~ "//example.com"]#foo`, - // Tags stripped, spaces escaped, entity not re-escaped. - `Hello, World &tc!`, - ` dir="ltr"`, - `c && alert("Hello, World!");`, - `Hello, World & O'Reilly\u0021`, - `greeting=H%69,&addressee=(World)`, - `greeting=H%69,&addressee=(World) 2x, https://golang.org/favicon.ico 500.5w`, - `,foo/,`, - }, - }, - { - `<a title='{{.}}'>`, - []string{ - `<b> "foo%" O'Reilly &bar;`, - `a[href =~ "//example.com"]#foo`, - // Tags stripped, entity not re-escaped. - `Hello, World &tc!`, - ` dir="ltr"`, - `c && alert("Hello, World!");`, - `Hello, World & O'Reilly\u0021`, - `greeting=H%69,&addressee=(World)`, - `greeting=H%69,&addressee=(World) 2x, https://golang.org/favicon.ico 500.5w`, - `,foo/,`, - }, - }, - { - `<textarea>{{.}}</textarea>`, - []string{ - `<b> "foo%" O'Reilly &bar;`, - `a[href =~ "//example.com"]#foo`, - // Angle brackets escaped to prevent injection of close tags, entity not re-escaped. - `Hello, <b>World</b> &tc!`, - ` dir="ltr"`, - `c && alert("Hello, World!");`, - `Hello, World & O'Reilly\u0021`, - `greeting=H%69,&addressee=(World)`, - `greeting=H%69,&addressee=(World) 2x, https://golang.org/favicon.ico 500.5w`, - `,foo/,`, - }, - }, - { - `<script>alert({{.}})</script>`, - []string{ - `"\u003cb\u003e \"foo%\" O'Reilly \u0026bar;"`, - `"a[href =~ \"//example.com\"]#foo"`, - `"Hello, \u003cb\u003eWorld\u003c/b\u003e \u0026amp;tc!"`, - `" dir=\"ltr\""`, - // Not escaped. - `c && alert("Hello, World!");`, - // Escape sequence not over-escaped. - `"Hello, World & O'Reilly\u0021"`, - `"greeting=H%69,\u0026addressee=(World)"`, - `"greeting=H%69,\u0026addressee=(World) 2x, https://golang.org/favicon.ico 500.5w"`, - `",foo/,"`, - }, - }, - { - `<button onclick="alert({{.}})">`, - []string{ - `"\u003cb\u003e \"foo%\" O'Reilly \u0026bar;"`, - `"a[href =~ \"//example.com\"]#foo"`, - `"Hello, \u003cb\u003eWorld\u003c/b\u003e \u0026amp;tc!"`, - `" dir=\"ltr\""`, - // Not JS escaped but HTML escaped. - `c && alert("Hello, World!");`, - // Escape sequence not over-escaped. - `"Hello, World & O'Reilly\u0021"`, - `"greeting=H%69,\u0026addressee=(World)"`, - `"greeting=H%69,\u0026addressee=(World) 2x, https://golang.org/favicon.ico 500.5w"`, - `",foo/,"`, - }, - }, - { - `<script>alert("{{.}}")</script>`, - []string{ - `\u003cb\u003e \u0022foo%\u0022 O\u0027Reilly \u0026bar;`, - `a[href =~ \u0022\/\/example.com\u0022]#foo`, - `Hello, \u003cb\u003eWorld\u003c\/b\u003e \u0026amp;tc!`, - ` dir=\u0022ltr\u0022`, - `c \u0026\u0026 alert(\u0022Hello, World!\u0022);`, - // Escape sequence not over-escaped. - `Hello, World \u0026 O\u0027Reilly\u0021`, - `greeting=H%69,\u0026addressee=(World)`, - `greeting=H%69,\u0026addressee=(World) 2x, https:\/\/golang.org\/favicon.ico 500.5w`, - `,foo\/,`, - }, - }, - { - `<script type="text/javascript">alert("{{.}}")</script>`, - []string{ - `\u003cb\u003e \u0022foo%\u0022 O\u0027Reilly \u0026bar;`, - `a[href =~ \u0022\/\/example.com\u0022]#foo`, - `Hello, \u003cb\u003eWorld\u003c\/b\u003e \u0026amp;tc!`, - ` dir=\u0022ltr\u0022`, - `c \u0026\u0026 alert(\u0022Hello, World!\u0022);`, - // Escape sequence not over-escaped. - `Hello, World \u0026 O\u0027Reilly\u0021`, - `greeting=H%69,\u0026addressee=(World)`, - `greeting=H%69,\u0026addressee=(World) 2x, https:\/\/golang.org\/favicon.ico 500.5w`, - `,foo\/,`, - }, - }, - { - `<script type="text/javascript">alert({{.}})</script>`, - []string{ - `"\u003cb\u003e \"foo%\" O'Reilly \u0026bar;"`, - `"a[href =~ \"//example.com\"]#foo"`, - `"Hello, \u003cb\u003eWorld\u003c/b\u003e \u0026amp;tc!"`, - `" dir=\"ltr\""`, - // Not escaped. - `c && alert("Hello, World!");`, - // Escape sequence not over-escaped. - `"Hello, World & O'Reilly\u0021"`, - `"greeting=H%69,\u0026addressee=(World)"`, - `"greeting=H%69,\u0026addressee=(World) 2x, https://golang.org/favicon.ico 500.5w"`, - `",foo/,"`, - }, - }, - { - // Not treated as JS. The output is same as for <div>{{.}}</div> - `<script type="golang.org/x/website/go.dev/cmd/internal/text/template">{{.}}</script>`, - []string{ - `<b> "foo%" O'Reilly &bar;`, - `a[href =~ "//example.com"]#foo`, - // Not escaped. - `Hello, <b>World</b> &tc!`, - ` dir="ltr"`, - `c && alert("Hello, World!");`, - `Hello, World & O'Reilly\u0021`, - `greeting=H%69,&addressee=(World)`, - `greeting=H%69,&addressee=(World) 2x, https://golang.org/favicon.ico 500.5w`, - `,foo/,`, - }, - }, - { - `<button onclick='alert("{{.}}")'>`, - []string{ - `\u003cb\u003e \u0022foo%\u0022 O\u0027Reilly \u0026bar;`, - `a[href =~ \u0022\/\/example.com\u0022]#foo`, - `Hello, \u003cb\u003eWorld\u003c\/b\u003e \u0026amp;tc!`, - ` dir=\u0022ltr\u0022`, - `c \u0026\u0026 alert(\u0022Hello, World!\u0022);`, - // Escape sequence not over-escaped. - `Hello, World \u0026 O\u0027Reilly\u0021`, - `greeting=H%69,\u0026addressee=(World)`, - `greeting=H%69,\u0026addressee=(World) 2x, https:\/\/golang.org\/favicon.ico 500.5w`, - `,foo\/,`, - }, - }, - { - `<a href="?q={{.}}">`, - []string{ - `%3cb%3e%20%22foo%25%22%20O%27Reilly%20%26bar%3b`, - `a%5bhref%20%3d~%20%22%2f%2fexample.com%22%5d%23foo`, - `Hello%2c%20%3cb%3eWorld%3c%2fb%3e%20%26amp%3btc%21`, - `%20dir%3d%22ltr%22`, - `c%20%26%26%20alert%28%22Hello%2c%20World%21%22%29%3b`, - `Hello%2c%20World%20%26%20O%27Reilly%5cu0021`, - // Quotes and parens are escaped but %69 is not over-escaped. HTML escaping is done. - `greeting=H%69,&addressee=%28World%29`, - `greeting%3dH%2569%2c%26addressee%3d%28World%29%202x%2c%20https%3a%2f%2fgolang.org%2ffavicon.ico%20500.5w`, - `,foo/,`, - }, - }, - { - `<style>body { background: url('?img={{.}}') }</style>`, - []string{ - `%3cb%3e%20%22foo%25%22%20O%27Reilly%20%26bar%3b`, - `a%5bhref%20%3d~%20%22%2f%2fexample.com%22%5d%23foo`, - `Hello%2c%20%3cb%3eWorld%3c%2fb%3e%20%26amp%3btc%21`, - `%20dir%3d%22ltr%22`, - `c%20%26%26%20alert%28%22Hello%2c%20World%21%22%29%3b`, - `Hello%2c%20World%20%26%20O%27Reilly%5cu0021`, - // Quotes and parens are escaped but %69 is not over-escaped. HTML escaping is not done. - `greeting=H%69,&addressee=%28World%29`, - `greeting%3dH%2569%2c%26addressee%3d%28World%29%202x%2c%20https%3a%2f%2fgolang.org%2ffavicon.ico%20500.5w`, - `,foo/,`, - }, - }, - { - `<img srcset="{{.}}">`, - []string{ - `#ZgotmplZ`, - `#ZgotmplZ`, - // Commas are not esacped - `Hello,#ZgotmplZ`, - // Leading spaces are not percent escapes. - ` dir=%22ltr%22`, - // Spaces after commas are not percent escaped. - `#ZgotmplZ, World!%22%29;`, - `Hello,#ZgotmplZ`, - `greeting=H%69%2c&addressee=%28World%29`, - // Metadata is not escaped. - `greeting=H%69,&addressee=(World) 2x, https://golang.org/favicon.ico 500.5w`, - `%2cfoo/%2c`, - }, - }, - { - `<img srcset={{.}}>`, - []string{ - `#ZgotmplZ`, - `#ZgotmplZ`, - `Hello,#ZgotmplZ`, - // Spaces are HTML escaped not %-escaped - ` dir=%22ltr%22`, - `#ZgotmplZ, World!%22%29;`, - `Hello,#ZgotmplZ`, - `greeting=H%69%2c&addressee=%28World%29`, - `greeting=H%69,&addressee=(World) 2x, https://golang.org/favicon.ico 500.5w`, - // Commas are escaped. - `%2cfoo/%2c`, - }, - }, - { - `<img srcset="{{.}} 2x, https://golang.org/ 500.5w">`, - []string{ - `#ZgotmplZ`, - `#ZgotmplZ`, - `Hello,#ZgotmplZ`, - ` dir=%22ltr%22`, - `#ZgotmplZ, World!%22%29;`, - `Hello,#ZgotmplZ`, - `greeting=H%69%2c&addressee=%28World%29`, - `greeting=H%69,&addressee=(World) 2x, https://golang.org/favicon.ico 500.5w`, - `%2cfoo/%2c`, - }, - }, - { - `<img srcset="http://godoc.org/ {{.}}, https://golang.org/ 500.5w">`, - []string{ - `#ZgotmplZ`, - `#ZgotmplZ`, - `Hello,#ZgotmplZ`, - ` dir=%22ltr%22`, - `#ZgotmplZ, World!%22%29;`, - `Hello,#ZgotmplZ`, - `greeting=H%69%2c&addressee=%28World%29`, - `greeting=H%69,&addressee=(World) 2x, https://golang.org/favicon.ico 500.5w`, - `%2cfoo/%2c`, - }, - }, - { - `<img srcset="http://godoc.org/?q={{.}} 2x, https://golang.org/ 500.5w">`, - []string{ - `#ZgotmplZ`, - `#ZgotmplZ`, - `Hello,#ZgotmplZ`, - ` dir=%22ltr%22`, - `#ZgotmplZ, World!%22%29;`, - `Hello,#ZgotmplZ`, - `greeting=H%69%2c&addressee=%28World%29`, - `greeting=H%69,&addressee=(World) 2x, https://golang.org/favicon.ico 500.5w`, - `%2cfoo/%2c`, - }, - }, - { - `<img srcset="http://godoc.org/ 2x, {{.}} 500.5w">`, - []string{ - `#ZgotmplZ`, - `#ZgotmplZ`, - `Hello,#ZgotmplZ`, - ` dir=%22ltr%22`, - `#ZgotmplZ, World!%22%29;`, - `Hello,#ZgotmplZ`, - `greeting=H%69%2c&addressee=%28World%29`, - `greeting=H%69,&addressee=(World) 2x, https://golang.org/favicon.ico 500.5w`, - `%2cfoo/%2c`, - }, - }, - { - `<img srcset="http://godoc.org/ 2x, https://golang.org/ {{.}}">`, - []string{ - `#ZgotmplZ`, - `#ZgotmplZ`, - `Hello,#ZgotmplZ`, - ` dir=%22ltr%22`, - `#ZgotmplZ, World!%22%29;`, - `Hello,#ZgotmplZ`, - `greeting=H%69%2c&addressee=%28World%29`, - `greeting=H%69,&addressee=(World) 2x, https://golang.org/favicon.ico 500.5w`, - `%2cfoo/%2c`, - }, - }, - } - - for _, test := range tests { - tmpl := Must(New("x").Parse(test.input)) - pre := strings.Index(test.input, "{{.}}") - post := len(test.input) - (pre + 5) - var b bytes.Buffer - for i, x := range data { - b.Reset() - if err := tmpl.Execute(&b, x); err != nil { - t.Errorf("%q with %v: %s", test.input, x, err) - continue - } - if want, got := test.want[i], b.String()[pre:b.Len()-post]; want != got { - t.Errorf("%q with %v:\nwant\n\t%q,\ngot\n\t%q\n", test.input, x, want, got) - continue - } - } - } -} - -// Test that we print using the String method. Was issue 3073. -type myStringer struct { - v int -} - -func (s *myStringer) String() string { - return fmt.Sprintf("string=%d", s.v) -} - -type errorer struct { - v int -} - -func (s *errorer) Error() string { - return fmt.Sprintf("error=%d", s.v) -} - -func TestStringer(t *testing.T) { - s := &myStringer{3} - b := new(bytes.Buffer) - tmpl := Must(New("x").Parse("{{.}}")) - if err := tmpl.Execute(b, s); err != nil { - t.Fatal(err) - } - var expect = "string=3" - if b.String() != expect { - t.Errorf("expected %q got %q", expect, b.String()) - } - e := &errorer{7} - b.Reset() - if err := tmpl.Execute(b, e); err != nil { - t.Fatal(err) - } - expect = "error=7" - if b.String() != expect { - t.Errorf("expected %q got %q", expect, b.String()) - } -} - -// https://golang.org/issue/5982 -func TestEscapingNilNonemptyInterfaces(t *testing.T) { - tmpl := Must(New("x").Parse("{{.E}}")) - - got := new(bytes.Buffer) - testData := struct{ E error }{} // any non-empty interface here will do; error is just ready at hand - tmpl.Execute(got, testData) - - // A non-empty interface should print like an empty interface. - want := new(bytes.Buffer) - data := struct{ E interface{} }{} - tmpl.Execute(want, data) - - if !bytes.Equal(want.Bytes(), got.Bytes()) { - t.Errorf("expected %q got %q", string(want.Bytes()), string(got.Bytes())) - } -}
diff --git a/go.dev/cmd/internal/html/template/context.go b/go.dev/cmd/internal/html/template/context.go deleted file mode 100644 index 9b74fe7..0000000 --- a/go.dev/cmd/internal/html/template/context.go +++ /dev/null
@@ -1,265 +0,0 @@ -// Copyright 2011 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package template - -import ( - "fmt" - - "golang.org/x/website/go.dev/cmd/internal/text/template/parse" -) - -// context describes the state an HTML parser must be in when it reaches the -// portion of HTML produced by evaluating a particular template node. -// -// The zero value of type context is the start context for a template that -// produces an HTML fragment as defined at -// 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 - n parse.Node // for range break/continue - err *Error -} - -func (c context) String() string { - var err error - if c.err != nil { - err = c.err - } - return fmt.Sprintf("{%v %v %v %v %v %v %v}", c.state, c.delim, c.urlPart, c.jsCtx, c.attr, c.element, err) -} - -// eq reports whether two contexts are equal. -func (c context) eq(d context) bool { - return c.state == d.state && - c.delim == d.delim && - c.urlPart == d.urlPart && - c.jsCtx == d.jsCtx && - c.attr == d.attr && - c.element == d.element && - c.err == d.err -} - -// mangle produces an identifier that includes a suffix that distinguishes it -// from template names mangled with different contexts. -func (c context) mangle(templateName string) string { - // The mangled name for the default context is the input templateName. - if c.state == stateText { - return templateName - } - s := templateName + "$htmltemplate_" + c.state.String() - if c.delim != delimNone { - s += "_" + c.delim.String() - } - if c.urlPart != urlPartNone { - s += "_" + c.urlPart.String() - } - if c.jsCtx != jsCtxRegexp { - s += "_" + c.jsCtx.String() - } - if c.attr != attrNone { - s += "_" + c.attr.String() - } - if c.element != elementNone { - s += "_" + c.element.String() - } - return s -} - -// state describes a high-level HTML parser state. -// -// It bounds the top of the element stack, and by extension the HTML insertion -// mode, but also contains state that does not correspond to anything in the -// HTML5 parsing algorithm because a single token production in the HTML -// grammar may contain embedded actions in a template. For instance, the quoted -// HTML attribute produced by -// <div title="Hello {{.World}}"> -// is a single token in HTML's grammar but in a template spans several nodes. -type state uint8 - -//go:generate stringer -type state - -const ( - // stateText is parsed character data. An HTML parser is in - // this state when its parse position is outside an HTML tag, - // directive, comment, and special element body. - stateText state = iota - // stateTag occurs before an HTML attribute or the end of a tag. - stateTag - // stateAttrName occurs inside an attribute name. - // It occurs between the ^'s in ` ^name^ = value`. - stateAttrName - // stateAfterName occurs after an attr name has ended but before any - // equals sign. It occurs between the ^'s in ` name^ ^= value`. - stateAfterName - // stateBeforeValue occurs after the equals sign but before the value. - // It occurs between the ^'s in ` name =^ ^value`. - stateBeforeValue - // stateHTMLCmt occurs inside an <!-- HTML comment -->. - stateHTMLCmt - // stateRCDATA occurs inside an RCDATA element (<textarea> or <title>) - // as described at https://www.w3.org/TR/html5/syntax.html#elements-0 - stateRCDATA - // stateAttr occurs inside an HTML attribute whose content is text. - stateAttr - // stateURL occurs inside an HTML attribute whose content is a URL. - stateURL - // stateSrcset occurs inside an HTML srcset attribute. - stateSrcset - // stateJS occurs inside an event handler or script element. - stateJS - // stateJSDqStr occurs inside a JavaScript double quoted string. - stateJSDqStr - // stateJSSqStr occurs inside a JavaScript single quoted string. - stateJSSqStr - // stateJSRegexp occurs inside a JavaScript regexp literal. - stateJSRegexp - // stateJSBlockCmt occurs inside a JavaScript /* block comment */. - stateJSBlockCmt - // stateJSLineCmt occurs inside a JavaScript // line comment. - stateJSLineCmt - // stateCSS occurs inside a <style> element or style attribute. - stateCSS - // stateCSSDqStr occurs inside a CSS double quoted string. - stateCSSDqStr - // stateCSSSqStr occurs inside a CSS single quoted string. - stateCSSSqStr - // stateCSSDqURL occurs inside a CSS double quoted url("..."). - stateCSSDqURL - // stateCSSSqURL occurs inside a CSS single quoted url('...'). - stateCSSSqURL - // stateCSSURL occurs inside a CSS unquoted url(...). - stateCSSURL - // stateCSSBlockCmt occurs inside a CSS /* block comment */. - stateCSSBlockCmt - // stateCSSLineCmt occurs inside a CSS // line comment. - stateCSSLineCmt - // stateError is an infectious error state outside any valid - // HTML/CSS/JS construct. - stateError - // stateDead marks unreachable code after a {{break}} or {{continue}}. - stateDead -) - -// isComment is true for any state that contains content meant for template -// authors & maintainers, not for end-users or machines. -func isComment(s state) bool { - switch s { - case stateHTMLCmt, stateJSBlockCmt, stateJSLineCmt, stateCSSBlockCmt, stateCSSLineCmt: - return true - } - return false -} - -// isInTag return whether s occurs solely inside an HTML tag. -func isInTag(s state) bool { - switch s { - case stateTag, stateAttrName, stateAfterName, stateBeforeValue, stateAttr: - return true - } - return false -} - -// delim is the delimiter that will end the current HTML attribute. -type delim uint8 - -//go:generate stringer -type delim - -const ( - // delimNone occurs outside any attribute. - delimNone delim = iota - // delimDoubleQuote occurs when a double quote (") closes the attribute. - delimDoubleQuote - // delimSingleQuote occurs when a single quote (') closes the attribute. - delimSingleQuote - // delimSpaceOrTagEnd occurs when a space or right angle bracket (>) - // closes the attribute. - delimSpaceOrTagEnd -) - -// urlPart identifies a part in an RFC 3986 hierarchical URL to allow different -// encoding strategies. -type urlPart uint8 - -//go:generate stringer -type urlPart - -const ( - // urlPartNone occurs when not in a URL, or possibly at the start: - // ^ in "^http://auth/path?k=v#frag". - urlPartNone urlPart = iota - // urlPartPreQuery occurs in the scheme, authority, or path; between the - // ^s in "h^ttp://auth/path^?k=v#frag". - urlPartPreQuery - // urlPartQueryOrFrag occurs in the query portion between the ^s in - // "http://auth/path?^k=v#frag^". - urlPartQueryOrFrag - // urlPartUnknown occurs due to joining of contexts both before and - // after the query separator. - urlPartUnknown -) - -// jsCtx determines whether a '/' starts a regular expression literal or a -// division operator. -type jsCtx uint8 - -//go:generate stringer -type jsCtx - -const ( - // jsCtxRegexp occurs where a '/' would start a regexp literal. - jsCtxRegexp jsCtx = iota - // jsCtxDivOp occurs where a '/' would start a division operator. - jsCtxDivOp - // jsCtxUnknown occurs where a '/' is ambiguous due to context joining. - jsCtxUnknown -) - -// element identifies the HTML element when inside a start tag or special body. -// Certain HTML element (for example <script> and <style>) have bodies that are -// treated differently from stateText so the element type is necessary to -// transition into the correct context at the end of a tag and to identify the -// end delimiter for the body. -type element uint8 - -//go:generate stringer -type element - -const ( - // elementNone occurs outside a special tag or special element body. - elementNone element = iota - // elementScript corresponds to the raw text <script> element - // with JS MIME type or no type attribute. - elementScript - // elementStyle corresponds to the raw text <style> element. - elementStyle - // elementTextarea corresponds to the RCDATA <textarea> element. - elementTextarea - // elementTitle corresponds to the RCDATA <title> element. - elementTitle -) - -//go:generate stringer -type attr - -// attr identifies the current HTML attribute when inside the attribute, -// that is, starting from stateAttrName until stateTag/stateText (exclusive). -type attr uint8 - -const ( - // attrNone corresponds to a normal attribute or no attribute. - attrNone attr = iota - // attrScript corresponds to an event handler attribute. - attrScript - // attrScriptType corresponds to the type attribute in script HTML element - attrScriptType - // attrStyle corresponds to the style attribute whose value is CSS. - attrStyle - // attrURL corresponds to an attribute whose value is a URL. - attrURL - // attrSrcset corresponds to a srcset attribute. - attrSrcset -)
diff --git a/go.dev/cmd/internal/html/template/css.go b/go.dev/cmd/internal/html/template/css.go deleted file mode 100644 index eb92fc9..0000000 --- a/go.dev/cmd/internal/html/template/css.go +++ /dev/null
@@ -1,260 +0,0 @@ -// Copyright 2011 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package template - -import ( - "bytes" - "fmt" - "strings" - "unicode" - "unicode/utf8" -) - -// endsWithCSSKeyword reports whether b ends with an ident that -// case-insensitively matches the lower-case kw. -func endsWithCSSKeyword(b []byte, kw string) bool { - i := len(b) - len(kw) - if i < 0 { - // Too short. - return false - } - if i != 0 { - r, _ := utf8.DecodeLastRune(b[:i]) - if isCSSNmchar(r) { - // Too long. - return false - } - } - // Many CSS keywords, such as "!important" can have characters encoded, - // but the URI production does not allow that according to - // https://www.w3.org/TR/css3-syntax/#TOK-URI - // This does not attempt to recognize encoded keywords. For example, - // given "\75\72\6c" and "url" this return false. - return string(bytes.ToLower(b[i:])) == kw -} - -// isCSSNmchar reports whether rune is allowed anywhere in a CSS identifier. -func isCSSNmchar(r rune) bool { - // Based on the CSS3 nmchar production but ignores multi-rune escape - // sequences. - // https://www.w3.org/TR/css3-syntax/#SUBTOK-nmchar - return 'a' <= r && r <= 'z' || - 'A' <= r && r <= 'Z' || - '0' <= r && r <= '9' || - r == '-' || - r == '_' || - // Non-ASCII cases below. - 0x80 <= r && r <= 0xd7ff || - 0xe000 <= r && r <= 0xfffd || - 0x10000 <= r && r <= 0x10ffff -} - -// decodeCSS decodes CSS3 escapes given a sequence of stringchars. -// If there is no change, it returns the input, otherwise it returns a slice -// backed by a new array. -// https://www.w3.org/TR/css3-syntax/#SUBTOK-stringchar defines stringchar. -func decodeCSS(s []byte) []byte { - i := bytes.IndexByte(s, '\\') - if i == -1 { - return s - } - // The UTF-8 sequence for a codepoint is never longer than 1 + the - // number hex digits need to represent that codepoint, so len(s) is an - // upper bound on the output length. - b := make([]byte, 0, len(s)) - for len(s) != 0 { - i := bytes.IndexByte(s, '\\') - if i == -1 { - i = len(s) - } - b, s = append(b, s[:i]...), s[i:] - if len(s) < 2 { - break - } - // https://www.w3.org/TR/css3-syntax/#SUBTOK-escape - // escape ::= unicode | '\' [#x20-#x7E#x80-#xD7FF#xE000-#xFFFD#x10000-#x10FFFF] - if isHex(s[1]) { - // https://www.w3.org/TR/css3-syntax/#SUBTOK-unicode - // unicode ::= '\' [0-9a-fA-F]{1,6} wc? - j := 2 - for j < len(s) && j < 7 && isHex(s[j]) { - j++ - } - r := hexDecode(s[1:j]) - if r > unicode.MaxRune { - r, j = r/16, j-1 - } - n := utf8.EncodeRune(b[len(b):cap(b)], r) - // The optional space at the end allows a hex - // sequence to be followed by a literal hex. - // string(decodeCSS([]byte(`\A B`))) == "\nB" - b, s = b[:len(b)+n], skipCSSSpace(s[j:]) - } else { - // `\\` decodes to `\` and `\"` to `"`. - _, n := utf8.DecodeRune(s[1:]) - b, s = append(b, s[1:1+n]...), s[1+n:] - } - } - return b -} - -// isHex reports whether the given character is a hex digit. -func isHex(c byte) bool { - return '0' <= c && c <= '9' || 'a' <= c && c <= 'f' || 'A' <= c && c <= 'F' -} - -// hexDecode decodes a short hex digit sequence: "10" -> 16. -func hexDecode(s []byte) rune { - n := '\x00' - for _, c := range s { - n <<= 4 - switch { - case '0' <= c && c <= '9': - n |= rune(c - '0') - case 'a' <= c && c <= 'f': - n |= rune(c-'a') + 10 - case 'A' <= c && c <= 'F': - n |= rune(c-'A') + 10 - default: - panic(fmt.Sprintf("Bad hex digit in %q", s)) - } - } - return n -} - -// skipCSSSpace returns a suffix of c, skipping over a single space. -func skipCSSSpace(c []byte) []byte { - if len(c) == 0 { - return c - } - // wc ::= #x9 | #xA | #xC | #xD | #x20 - switch c[0] { - case '\t', '\n', '\f', ' ': - return c[1:] - case '\r': - // This differs from CSS3's wc production because it contains a - // probable spec error whereby wc contains all the single byte - // sequences in nl (newline) but not CRLF. - if len(c) >= 2 && c[1] == '\n' { - return c[2:] - } - return c[1:] - } - return c -} - -// isCSSSpace reports whether b is a CSS space char as defined in wc. -func isCSSSpace(b byte) bool { - switch b { - case '\t', '\n', '\f', '\r', ' ': - return true - } - return false -} - -// cssEscaper escapes HTML and CSS special characters using \<hex>+ escapes. -func cssEscaper(args ...interface{}) string { - s, _ := stringify(args...) - var b strings.Builder - r, w, written := rune(0), 0, 0 - for i := 0; i < len(s); i += w { - // See comment in htmlEscaper. - r, w = utf8.DecodeRuneInString(s[i:]) - var repl string - switch { - case int(r) < len(cssReplacementTable) && cssReplacementTable[r] != "": - repl = cssReplacementTable[r] - default: - continue - } - if written == 0 { - b.Grow(len(s)) - } - b.WriteString(s[written:i]) - b.WriteString(repl) - written = i + w - if repl != `\\` && (written == len(s) || isHex(s[written]) || isCSSSpace(s[written])) { - b.WriteByte(' ') - } - } - if written == 0 { - return s - } - b.WriteString(s[written:]) - return b.String() -} - -var cssReplacementTable = []string{ - 0: `\0`, - '\t': `\9`, - '\n': `\a`, - '\f': `\c`, - '\r': `\d`, - // Encode HTML specials as hex so the output can be embedded - // in HTML attributes without further encoding. - '"': `\22`, - '&': `\26`, - '\'': `\27`, - '(': `\28`, - ')': `\29`, - '+': `\2b`, - '/': `\2f`, - ':': `\3a`, - ';': `\3b`, - '<': `\3c`, - '>': `\3e`, - '\\': `\\`, - '{': `\7b`, - '}': `\7d`, -} - -var expressionBytes = []byte("expression") -var mozBindingBytes = []byte("mozbinding") - -// cssValueFilter allows innocuous CSS values in the output including CSS -// quantities (10px or 25%), ID or class literals (#foo, .bar), keyword values -// (inherit, blue), and colors (#888). -// It filters out unsafe values, such as those that affect token boundaries, -// and anything that might execute scripts. -func cssValueFilter(args ...interface{}) string { - s, t := stringify(args...) - if t == contentTypeCSS { - return s - } - b, id := decodeCSS([]byte(s)), make([]byte, 0, 64) - - // CSS3 error handling is specified as honoring string boundaries per - // https://www.w3.org/TR/css3-syntax/#error-handling : - // Malformed declarations. User agents must handle unexpected - // tokens encountered while parsing a declaration by reading until - // the end of the declaration, while observing the rules for - // matching pairs of (), [], {}, "", and '', and correctly handling - // escapes. For example, a malformed declaration may be missing a - // property, colon (:) or value. - // So we need to make sure that values do not have mismatched bracket - // or quote characters to prevent the browser from restarting parsing - // inside a string that might embed JavaScript source. - for i, c := range b { - switch c { - case 0, '"', '\'', '(', ')', '/', ';', '@', '[', '\\', ']', '`', '{', '}': - return filterFailsafe - case '-': - // Disallow <!-- or -->. - // -- should not appear in valid identifiers. - if i != 0 && b[i-1] == '-' { - return filterFailsafe - } - default: - if c < utf8.RuneSelf && isCSSNmchar(rune(c)) { - id = append(id, c) - } - } - } - id = bytes.ToLower(id) - if bytes.Contains(id, expressionBytes) || bytes.Contains(id, mozBindingBytes) { - return filterFailsafe - } - return string(b) -}
diff --git a/go.dev/cmd/internal/html/template/css_test.go b/go.dev/cmd/internal/html/template/css_test.go deleted file mode 100644 index a735638..0000000 --- a/go.dev/cmd/internal/html/template/css_test.go +++ /dev/null
@@ -1,281 +0,0 @@ -// Copyright 2011 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package template - -import ( - "strconv" - "strings" - "testing" -) - -func TestEndsWithCSSKeyword(t *testing.T) { - tests := []struct { - css, kw string - want bool - }{ - {"", "url", false}, - {"url", "url", true}, - {"URL", "url", true}, - {"Url", "url", true}, - {"url", "important", false}, - {"important", "important", true}, - {"image-url", "url", false}, - {"imageurl", "url", false}, - {"image url", "url", true}, - } - for _, test := range tests { - got := endsWithCSSKeyword([]byte(test.css), test.kw) - if got != test.want { - t.Errorf("want %t but got %t for css=%v, kw=%v", test.want, got, test.css, test.kw) - } - } -} - -func TestIsCSSNmchar(t *testing.T) { - tests := []struct { - rune rune - want bool - }{ - {0, false}, - {'0', true}, - {'9', true}, - {'A', true}, - {'Z', true}, - {'a', true}, - {'z', true}, - {'_', true}, - {'-', true}, - {':', false}, - {';', false}, - {' ', false}, - {0x7f, false}, - {0x80, true}, - {0x1234, true}, - {0xd800, false}, - {0xdc00, false}, - {0xfffe, false}, - {0x10000, true}, - {0x110000, false}, - } - for _, test := range tests { - got := isCSSNmchar(test.rune) - if got != test.want { - t.Errorf("%q: want %t but got %t", string(test.rune), test.want, got) - } - } -} - -func TestDecodeCSS(t *testing.T) { - tests := []struct { - css, want string - }{ - {``, ``}, - {`foo`, `foo`}, - {`foo\`, `foo`}, - {`foo\\`, `foo\`}, - {`\`, ``}, - {`\A`, "\n"}, - {`\a`, "\n"}, - {`\0a`, "\n"}, - {`\00000a`, "\n"}, - {`\000000a`, "\u0000a"}, - {`\1234 5`, "\u1234" + "5"}, - {`\1234\20 5`, "\u1234" + " 5"}, - {`\1234\A 5`, "\u1234" + "\n5"}, - {"\\1234\t5", "\u1234" + "5"}, - {"\\1234\n5", "\u1234" + "5"}, - {"\\1234\r\n5", "\u1234" + "5"}, - {`\12345`, "\U00012345"}, - {`\\`, `\`}, - {`\\ `, `\ `}, - {`\"`, `"`}, - {`\'`, `'`}, - {`\.`, `.`}, - {`\. .`, `. .`}, - { - `The \3c i\3equick\3c/i\3e,\d\A\3cspan style=\27 color:brown\27\3e brown\3c/span\3e fox jumps\2028over the \3c canine class=\22lazy\22 \3e dog\3c/canine\3e`, - "The <i>quick</i>,\r\n<span style='color:brown'>brown</span> fox jumps\u2028over the <canine class=\"lazy\">dog</canine>", - }, - } - for _, test := range tests { - got1 := string(decodeCSS([]byte(test.css))) - if got1 != test.want { - t.Errorf("%q: want\n\t%q\nbut got\n\t%q", test.css, test.want, got1) - } - recoded := cssEscaper(got1) - if got2 := string(decodeCSS([]byte(recoded))); got2 != test.want { - t.Errorf("%q: escape & decode not dual for %q", test.css, recoded) - } - } -} - -func TestHexDecode(t *testing.T) { - for i := 0; i < 0x200000; i += 101 /* coprime with 16 */ { - s := strconv.FormatInt(int64(i), 16) - if got := int(hexDecode([]byte(s))); got != i { - t.Errorf("%s: want %d but got %d", s, i, got) - } - s = strings.ToUpper(s) - if got := int(hexDecode([]byte(s))); got != i { - t.Errorf("%s: want %d but got %d", s, i, got) - } - } -} - -func TestSkipCSSSpace(t *testing.T) { - tests := []struct { - css, want string - }{ - {"", ""}, - {"foo", "foo"}, - {"\n", ""}, - {"\r\n", ""}, - {"\r", ""}, - {"\t", ""}, - {" ", ""}, - {"\f", ""}, - {" foo", "foo"}, - {" foo", " foo"}, - {`\20`, `\20`}, - } - for _, test := range tests { - got := string(skipCSSSpace([]byte(test.css))) - if got != test.want { - t.Errorf("%q: want %q but got %q", test.css, test.want, got) - } - } -} - -func TestCSSEscaper(t *testing.T) { - input := ("\x00\x01\x02\x03\x04\x05\x06\x07\x08\t\n\x0b\x0c\r\x0e\x0f" + - "\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f" + - ` !"#$%&'()*+,-./` + - `0123456789:;<=>?` + - `@ABCDEFGHIJKLMNO` + - `PQRSTUVWXYZ[\]^_` + - "`abcdefghijklmno" + - "pqrstuvwxyz{|}~\x7f" + - "\u00A0\u0100\u2028\u2029\ufeff\U0001D11E") - - want := ("\\0\x01\x02\x03\x04\x05\x06\x07" + - "\x08\\9 \\a\x0b\\c \\d\x0E\x0F" + - "\x10\x11\x12\x13\x14\x15\x16\x17" + - "\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f" + - ` !\22#$%\26\27\28\29*\2b,-.\2f ` + - `0123456789\3a\3b\3c=\3e?` + - `@ABCDEFGHIJKLMNO` + - `PQRSTUVWXYZ[\\]^_` + - "`abcdefghijklmno" + - `pqrstuvwxyz\7b|\7d~` + "\u007f" + - "\u00A0\u0100\u2028\u2029\ufeff\U0001D11E") - - got := cssEscaper(input) - if got != want { - t.Errorf("encode: want\n\t%q\nbut got\n\t%q", want, got) - } - - got = string(decodeCSS([]byte(got))) - if input != got { - t.Errorf("decode: want\n\t%q\nbut got\n\t%q", input, got) - } -} - -func TestCSSValueFilter(t *testing.T) { - tests := []struct { - css, want string - }{ - {"", ""}, - {"foo", "foo"}, - {"0", "0"}, - {"0px", "0px"}, - {"-5px", "-5px"}, - {"1.25in", "1.25in"}, - {"+.33em", "+.33em"}, - {"100%", "100%"}, - {"12.5%", "12.5%"}, - {".foo", ".foo"}, - {"#bar", "#bar"}, - {"corner-radius", "corner-radius"}, - {"-moz-corner-radius", "-moz-corner-radius"}, - {"#000", "#000"}, - {"#48f", "#48f"}, - {"#123456", "#123456"}, - {"U+00-FF, U+980-9FF", "U+00-FF, U+980-9FF"}, - {"color: red", "color: red"}, - {"<!--", "ZgotmplZ"}, - {"-->", "ZgotmplZ"}, - {"<![CDATA[", "ZgotmplZ"}, - {"]]>", "ZgotmplZ"}, - {"</style", "ZgotmplZ"}, - {`"`, "ZgotmplZ"}, - {`'`, "ZgotmplZ"}, - {"`", "ZgotmplZ"}, - {"\x00", "ZgotmplZ"}, - {"/* foo */", "ZgotmplZ"}, - {"//", "ZgotmplZ"}, - {"[href=~", "ZgotmplZ"}, - {"expression(alert(1337))", "ZgotmplZ"}, - {"-expression(alert(1337))", "ZgotmplZ"}, - {"expression", "ZgotmplZ"}, - {"Expression", "ZgotmplZ"}, - {"EXPRESSION", "ZgotmplZ"}, - {"-moz-binding", "ZgotmplZ"}, - {"-expr\x00ession(alert(1337))", "ZgotmplZ"}, - {`-expr\0ession(alert(1337))`, "ZgotmplZ"}, - {`-express\69on(alert(1337))`, "ZgotmplZ"}, - {`-express\69 on(alert(1337))`, "ZgotmplZ"}, - {`-exp\72 ession(alert(1337))`, "ZgotmplZ"}, - {`-exp\52 ession(alert(1337))`, "ZgotmplZ"}, - {`-exp\000052 ession(alert(1337))`, "ZgotmplZ"}, - {`-expre\0000073sion`, "-expre\x073sion"}, - {`@import url evil.css`, "ZgotmplZ"}, - } - for _, test := range tests { - got := cssValueFilter(test.css) - if got != test.want { - t.Errorf("%q: want %q but got %q", test.css, test.want, got) - } - } -} - -func BenchmarkCSSEscaper(b *testing.B) { - for i := 0; i < b.N; i++ { - cssEscaper("The <i>quick</i>,\r\n<span style='color:brown'>brown</span> fox jumps\u2028over the <canine class=\"lazy\">dog</canine>") - } -} - -func BenchmarkCSSEscaperNoSpecials(b *testing.B) { - for i := 0; i < b.N; i++ { - cssEscaper("The quick, brown fox jumps over the lazy dog.") - } -} - -func BenchmarkDecodeCSS(b *testing.B) { - s := []byte(`The \3c i\3equick\3c/i\3e,\d\A\3cspan style=\27 color:brown\27\3e brown\3c/span\3e fox jumps\2028over the \3c canine class=\22lazy\22 \3edog\3c/canine\3e`) - b.ResetTimer() - for i := 0; i < b.N; i++ { - decodeCSS(s) - } -} - -func BenchmarkDecodeCSSNoSpecials(b *testing.B) { - s := []byte("The quick, brown fox jumps over the lazy dog.") - b.ResetTimer() - for i := 0; i < b.N; i++ { - decodeCSS(s) - } -} - -func BenchmarkCSSValueFilter(b *testing.B) { - for i := 0; i < b.N; i++ { - cssValueFilter(` e\78preS\0Sio/**/n(alert(1337))`) - } -} - -func BenchmarkCSSValueFilterOk(b *testing.B) { - for i := 0; i < b.N; i++ { - cssValueFilter(`Times New Roman`) - } -}
diff --git a/go.dev/cmd/internal/html/template/delim_string.go b/go.dev/cmd/internal/html/template/delim_string.go deleted file mode 100644 index 6d80e09..0000000 --- a/go.dev/cmd/internal/html/template/delim_string.go +++ /dev/null
@@ -1,16 +0,0 @@ -// Code generated by "stringer -type delim"; DO NOT EDIT. - -package template - -import "strconv" - -const _delim_name = "delimNonedelimDoubleQuotedelimSingleQuotedelimSpaceOrTagEnd" - -var _delim_index = [...]uint8{0, 9, 25, 41, 59} - -func (i delim) String() string { - if i >= delim(len(_delim_index)-1) { - return "delim(" + strconv.FormatInt(int64(i), 10) + ")" - } - return _delim_name[_delim_index[i]:_delim_index[i+1]] -}
diff --git a/go.dev/cmd/internal/html/template/doc.go b/go.dev/cmd/internal/html/template/doc.go deleted file mode 100644 index fb3f173..0000000 --- a/go.dev/cmd/internal/html/template/doc.go +++ /dev/null
@@ -1,241 +0,0 @@ -// Copyright 2011 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -/* -Package template (html/template) implements data-driven templates for -generating HTML output safe against code injection. It provides the -same interface as package text/template and should be used instead of -text/template whenever the output is HTML. - -The documentation here focuses on the security features of the package. -For information about how to program the templates themselves, see the -documentation for text/template. - -Introduction - -This package wraps package text/template so you can share its template API -to parse and execute HTML templates safely. - - tmpl, err := template.New("name").Parse(...) - // Error checking elided - err = tmpl.Execute(out, data) - -If successful, tmpl will now be injection-safe. Otherwise, err is an error -defined in the docs for ErrorCode. - -HTML templates treat data values as plain text which should be encoded so they -can be safely embedded in an HTML document. The escaping is contextual, so -actions can appear within JavaScript, CSS, and URI contexts. - -The security model used by this package assumes that template authors are -trusted, while Execute's data parameter is not. More details are -provided below. - -Example - - import "golang.org/x/website/go.dev/cmd/internal/text/template" - ... - t, err := template.New("foo").Parse(`{{define "T"}}Hello, {{.}}!{{end}}`) - err = t.ExecuteTemplate(out, "T", "<script>alert('you have been pwned')</script>") - -produces - - Hello, <script>alert('you have been pwned')</script>! - -but the contextual autoescaping in html/template - - import "golang.org/x/website/go.dev/cmd/internal/html/template" - ... - t, err := template.New("foo").Parse(`{{define "T"}}Hello, {{.}}!{{end}}`) - err = t.ExecuteTemplate(out, "T", "<script>alert('you have been pwned')</script>") - -produces safe, escaped HTML output - - Hello, <script>alert('you have been pwned')</script>! - - -Contexts - -This package understands HTML, CSS, JavaScript, and URIs. It adds sanitizing -functions to each simple action pipeline, so given the excerpt - - <a href="/search?q={{.}}">{{.}}</a> - -At parse time each {{.}} is overwritten to add escaping functions as necessary. -In this case it becomes - - <a href="/search?q={{. | urlescaper | attrescaper}}">{{. | htmlescaper}}</a> - -where urlescaper, attrescaper, and htmlescaper are aliases for internal escaping -functions. - -For these internal escaping functions, if an action pipeline evaluates to -a nil interface value, it is treated as though it were an empty string. - -Namespaced and data- attributes - -Attributes with a namespace are treated as if they had no namespace. -Given the excerpt - - <a my:href="{{.}}"></a> - -At parse time the attribute will be treated as if it were just "href". -So at parse time the template becomes: - - <a my:href="{{. | urlescaper | attrescaper}}"></a> - -Similarly to attributes with namespaces, attributes with a "data-" prefix are -treated as if they had no "data-" prefix. So given - - <a data-href="{{.}}"></a> - -At parse time this becomes - - <a data-href="{{. | urlescaper | attrescaper}}"></a> - -If an attribute has both a namespace and a "data-" prefix, only the namespace -will be removed when determining the context. For example - - <a my:data-href="{{.}}"></a> - -This is handled as if "my:data-href" was just "data-href" and not "href" as -it would be if the "data-" prefix were to be ignored too. Thus at parse -time this becomes just - - <a my:data-href="{{. | attrescaper}}"></a> - -As a special case, attributes with the namespace "xmlns" are always treated -as containing URLs. Given the excerpts - - <a xmlns:title="{{.}}"></a> - <a xmlns:href="{{.}}"></a> - <a xmlns:onclick="{{.}}"></a> - -At parse time they become: - - <a xmlns:title="{{. | urlescaper | attrescaper}}"></a> - <a xmlns:href="{{. | urlescaper | attrescaper}}"></a> - <a xmlns:onclick="{{. | urlescaper | attrescaper}}"></a> - -Errors - -See the documentation of ErrorCode for details. - - -A fuller picture - -The rest of this package comment may be skipped on first reading; it includes -details necessary to understand escaping contexts and error messages. Most users -will not need to understand these details. - - -Contexts - -Assuming {{.}} is `O'Reilly: How are <i>you</i>?`, the table below shows -how {{.}} appears when used in the context to the left. - - Context {{.}} After - {{.}} O'Reilly: How are <i>you</i>? - <a title='{{.}}'> O'Reilly: How are you? - <a href="/{{.}}"> O'Reilly: How are %3ci%3eyou%3c/i%3e? - <a href="?q={{.}}"> O'Reilly%3a%20How%20are%3ci%3e...%3f - <a onx='f("{{.}}")'> O\x27Reilly: How are \x3ci\x3eyou...? - <a onx='f({{.}})'> "O\x27Reilly: How are \x3ci\x3eyou...?" - <a onx='pattern = /{{.}}/;'> O\x27Reilly: How are \x3ci\x3eyou...\x3f - -If used in an unsafe context, then the value might be filtered out: - - Context {{.}} After - <a href="{{.}}"> #ZgotmplZ - -since "O'Reilly:" is not an allowed protocol like "http:". - - -If {{.}} is the innocuous word, `left`, then it can appear more widely, - - Context {{.}} After - {{.}} left - <a title='{{.}}'> left - <a href='{{.}}'> left - <a href='/{{.}}'> left - <a href='?dir={{.}}'> left - <a style="border-{{.}}: 4px"> left - <a style="align: {{.}}"> left - <a style="background: '{{.}}'> left - <a style="background: url('{{.}}')> left - <style>p.{{.}} {color:red}</style> left - -Non-string values can be used in JavaScript contexts. -If {{.}} is - - struct{A,B string}{ "foo", "bar" } - -in the escaped template - - <script>var pair = {{.}};</script> - -then the template output is - - <script>var pair = {"A": "foo", "B": "bar"};</script> - -See package json to understand how non-string content is marshaled for -embedding in JavaScript contexts. - - -Typed Strings - -By default, this package assumes that all pipelines produce a plain text string. -It adds escaping pipeline stages necessary to correctly and safely embed that -plain text string in the appropriate context. - -When a data value is not plain text, you can make sure it is not over-escaped -by marking it with its type. - -Types HTML, JS, URL, and others from content.go can carry safe content that is -exempted from escaping. - -The template - - Hello, {{.}}! - -can be invoked with - - tmpl.Execute(out, template.HTML(`<b>World</b>`)) - -to produce - - Hello, <b>World</b>! - -instead of the - - Hello, <b>World<b>! - -that would have been produced if {{.}} was a regular string. - - -Security Model - -https://rawgit.com/mikesamuel/sanitized-jquery-templates/trunk/safetemplate.html#problem_definition defines "safe" as used by this package. - -This package assumes that template authors are trusted, that Execute's data -parameter is not, and seeks to preserve the properties below in the face -of untrusted data: - -Structure Preservation Property: -"... when a template author writes an HTML tag in a safe templating language, -the browser will interpret the corresponding portion of the output as a tag -regardless of the values of untrusted data, and similarly for other structures -such as attribute boundaries and JS and CSS string boundaries." - -Code Effect Property: -"... only code specified by the template author should run as a result of -injecting the template output into a page and all code specified by the -template author should run as a result of the same." - -Least Surprise Property: -"A developer (or code reviewer) familiar with HTML, CSS, and JavaScript, who -knows that contextual autoescaping happens should be able to look at a {{.}} -and correctly infer what sanitization happens." -*/ -package template
diff --git a/go.dev/cmd/internal/html/template/element_string.go b/go.dev/cmd/internal/html/template/element_string.go deleted file mode 100644 index 4573e08..0000000 --- a/go.dev/cmd/internal/html/template/element_string.go +++ /dev/null
@@ -1,16 +0,0 @@ -// Code generated by "stringer -type element"; DO NOT EDIT. - -package template - -import "strconv" - -const _element_name = "elementNoneelementScriptelementStyleelementTextareaelementTitle" - -var _element_index = [...]uint8{0, 11, 24, 36, 51, 63} - -func (i element) String() string { - if i >= element(len(_element_index)-1) { - return "element(" + strconv.FormatInt(int64(i), 10) + ")" - } - return _element_name[_element_index[i]:_element_index[i+1]] -}
diff --git a/go.dev/cmd/internal/html/template/error.go b/go.dev/cmd/internal/html/template/error.go deleted file mode 100644 index c8a041f..0000000 --- a/go.dev/cmd/internal/html/template/error.go +++ /dev/null
@@ -1,234 +0,0 @@ -// Copyright 2011 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package template - -import ( - "fmt" - - "golang.org/x/website/go.dev/cmd/internal/text/template/parse" -) - -// Error describes a problem encountered during template Escaping. -type Error struct { - // ErrorCode describes the kind of error. - ErrorCode ErrorCode - // Node is the node that caused the problem, if known. - // If not nil, it overrides Name and Line. - Node parse.Node - // Name is the name of the template in which the error was encountered. - Name string - // Line is the line number of the error in the template source or 0. - Line int - // Description is a human-readable description of the problem. - Description string -} - -// ErrorCode is a code for a kind of error. -type ErrorCode int - -// We define codes for each error that manifests while escaping templates, but -// escaped templates may also fail at runtime. -// -// Output: "ZgotmplZ" -// Example: -// <img src="{{.X}}"> -// where {{.X}} evaluates to `javascript:...` -// Discussion: -// "ZgotmplZ" is a special value that indicates that unsafe content reached a -// CSS or URL context at runtime. The output of the example will be -// <img src="#ZgotmplZ"> -// If the data comes from a trusted source, use content types to exempt it -// from filtering: URL(`javascript:...`). -const ( - // OK indicates the lack of an error. - OK ErrorCode = iota - - // ErrAmbigContext: "... appears in an ambiguous context within a URL" - // Example: - // <a href=" - // {{if .C}} - // /path/ - // {{else}} - // /search?q= - // {{end}} - // {{.X}} - // "> - // Discussion: - // {{.X}} is in an ambiguous URL context since, depending on {{.C}}, - // it may be either a URL suffix or a query parameter. - // Moving {{.X}} into the condition removes the ambiguity: - // <a href="{{if .C}}/path/{{.X}}{{else}}/search?q={{.X}}"> - ErrAmbigContext - - // ErrBadHTML: "expected space, attr name, or end of tag, but got ...", - // "... in unquoted attr", "... in attribute name" - // Example: - // <a href = /search?q=foo> - // <href=foo> - // <form na<e=...> - // <option selected< - // Discussion: - // This is often due to a typo in an HTML element, but some runes - // are banned in tag names, attribute names, and unquoted attribute - // values because they can tickle parser ambiguities. - // Quoting all attributes is the best policy. - ErrBadHTML - - // ErrBranchEnd: "{{if}} branches end in different contexts" - // Example: - // {{if .C}}<a href="{{end}}{{.X}} - // Discussion: - // Package html/template statically examines each path through an - // {{if}}, {{range}}, or {{with}} to escape any following pipelines. - // The example is ambiguous since {{.X}} might be an HTML text node, - // or a URL prefix in an HTML attribute. The context of {{.X}} is - // used to figure out how to escape it, but that context depends on - // the run-time value of {{.C}} which is not statically known. - // - // The problem is usually something like missing quotes or angle - // brackets, or can be avoided by refactoring to put the two contexts - // into different branches of an if, range or with. If the problem - // is in a {{range}} over a collection that should never be empty, - // adding a dummy {{else}} can help. - ErrBranchEnd - - // ErrEndContext: "... ends in a non-text context: ..." - // Examples: - // <div - // <div title="no close quote> - // <script>f() - // Discussion: - // Executed templates should produce a DocumentFragment of HTML. - // Templates that end without closing tags will trigger this error. - // Templates that should not be used in an HTML context or that - // produce incomplete Fragments should not be executed directly. - // - // {{define "main"}} <script>{{template "helper"}}</script> {{end}} - // {{define "helper"}} document.write(' <div title=" ') {{end}} - // - // "helper" does not produce a valid document fragment, so should - // not be Executed directly. - ErrEndContext - - // ErrNoSuchTemplate: "no such template ..." - // Examples: - // {{define "main"}}<div {{template "attrs"}}>{{end}} - // {{define "attrs"}}href="{{.URL}}"{{end}} - // Discussion: - // Package html/template looks through template calls to compute the - // context. - // Here the {{.URL}} in "attrs" must be treated as a URL when called - // from "main", but you will get this error if "attrs" is not defined - // when "main" is parsed. - ErrNoSuchTemplate - - // ErrOutputContext: "cannot compute output context for template ..." - // Examples: - // {{define "t"}}{{if .T}}{{template "t" .T}}{{end}}{{.H}}",{{end}} - // Discussion: - // A recursive template does not end in the same context in which it - // starts, and a reliable output context cannot be computed. - // Look for typos in the named template. - // If the template should not be called in the named start context, - // look for calls to that template in unexpected contexts. - // Maybe refactor recursive templates to not be recursive. - ErrOutputContext - - // ErrPartialCharset: "unfinished JS regexp charset in ..." - // Example: - // <script>var pattern = /foo[{{.Chars}}]/</script> - // Discussion: - // Package html/template does not support interpolation into regular - // expression literal character sets. - ErrPartialCharset - - // ErrPartialEscape: "unfinished escape sequence in ..." - // Example: - // <script>alert("\{{.X}}")</script> - // Discussion: - // Package html/template does not support actions following a - // backslash. - // This is usually an error and there are better solutions; for - // example - // <script>alert("{{.X}}")</script> - // should work, and if {{.X}} is a partial escape sequence such as - // "xA0", mark the whole sequence as safe content: JSStr(`\xA0`) - ErrPartialEscape - - // ErrRangeLoopReentry: "on range loop re-entry: ..." - // Example: - // <script>var x = [{{range .}}'{{.}},{{end}}]</script> - // Discussion: - // If an iteration through a range would cause it to end in a - // different context than an earlier pass, there is no single context. - // In the example, there is missing a quote, so it is not clear - // whether {{.}} is meant to be inside a JS string or in a JS value - // context. The second iteration would produce something like - // - // <script>var x = ['firstValue,'secondValue]</script> - ErrRangeLoopReentry - - // ErrSlashAmbig: '/' could start a division or regexp. - // Example: - // <script> - // {{if .C}}var x = 1{{end}} - // /-{{.N}}/i.test(x) ? doThis : doThat(); - // </script> - // Discussion: - // The example above could produce `var x = 1/-2/i.test(s)...` - // in which the first '/' is a mathematical division operator or it - // could produce `/-2/i.test(s)` in which the first '/' starts a - // regexp literal. - // Look for missing semicolons inside branches, and maybe add - // parentheses to make it clear which interpretation you intend. - ErrSlashAmbig - - // ErrPredefinedEscaper: "predefined escaper ... disallowed in template" - // Example: - // <div class={{. | html}}>Hello<div> - // Discussion: - // Package html/template already contextually escapes all pipelines to - // produce HTML output safe against code injection. Manually escaping - // pipeline output using the predefined escapers "html" or "urlquery" is - // unnecessary, and may affect the correctness or safety of the escaped - // pipeline output in Go 1.8 and earlier. - // - // In most cases, such as the given example, this error can be resolved by - // simply removing the predefined escaper from the pipeline and letting the - // contextual autoescaper handle the escaping of the pipeline. In other - // instances, where the predefined escaper occurs in the middle of a - // pipeline where subsequent commands expect escaped input, e.g. - // {{.X | html | makeALink}} - // where makeALink does - // return `<a href="`+input+`">link</a>` - // consider refactoring the surrounding template to make use of the - // contextual autoescaper, i.e. - // <a href="{{.X}}">link</a> - // - // To ease migration to Go 1.9 and beyond, "html" and "urlquery" will - // continue to be allowed as the last command in a pipeline. However, if the - // pipeline occurs in an unquoted attribute value context, "html" is - // disallowed. Avoid using "html" and "urlquery" entirely in new templates. - ErrPredefinedEscaper -) - -func (e *Error) Error() string { - switch { - case e.Node != nil: - loc, _ := (*parse.Tree)(nil).ErrorContext(e.Node) - return fmt.Sprintf("golang.org/x/website/go.dev/cmd/internal/html/template:%s: %s", loc, e.Description) - case e.Line != 0: - return fmt.Sprintf("golang.org/x/website/go.dev/cmd/internal/html/template:%s:%d: %s", e.Name, e.Line, e.Description) - case e.Name != "": - return fmt.Sprintf("golang.org/x/website/go.dev/cmd/internal/html/template:%s: %s", e.Name, e.Description) - } - return "golang.org/x/website/go.dev/cmd/internal/html/template: " + e.Description -} - -// errorf creates an error given a format string f and args. -// The template Name still needs to be supplied. -func errorf(k ErrorCode, node parse.Node, line int, f string, args ...interface{}) *Error { - return &Error{k, node, "", line, fmt.Sprintf(f, args...)} -}
diff --git a/go.dev/cmd/internal/html/template/escape.go b/go.dev/cmd/internal/html/template/escape.go deleted file mode 100644 index 959c871..0000000 --- a/go.dev/cmd/internal/html/template/escape.go +++ /dev/null
@@ -1,962 +0,0 @@ -// Copyright 2011 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package template - -import ( - "bytes" - "fmt" - "html" - "io" - - "golang.org/x/website/go.dev/cmd/internal/text/template" - "golang.org/x/website/go.dev/cmd/internal/text/template/parse" -) - -// escapeTemplate rewrites the named template, which must be -// associated with t, to guarantee that the output of any of the named -// templates is properly escaped. If no error is returned, then the named templates have -// been modified. Otherwise the named templates have been rendered -// unusable. -func escapeTemplate(tmpl *Template, node parse.Node, name string) error { - c, _ := tmpl.esc.escapeTree(context{}, node, name, 0) - var err error - if c.err != nil { - err, c.err.Name = c.err, name - } else if c.state != stateText { - err = &Error{ErrEndContext, nil, name, 0, fmt.Sprintf("ends in a non-text context: %v", c)} - } - if err != nil { - // Prevent execution of unsafe templates. - if t := tmpl.set[name]; t != nil { - t.escapeErr = err - t.text.Tree = nil - t.Tree = nil - } - return err - } - tmpl.esc.commit() - if t := tmpl.set[name]; t != nil { - t.escapeErr = escapeOK - t.Tree = t.text.Tree - } - return nil -} - -// evalArgs formats the list of arguments into a string. It is equivalent to -// fmt.Sprint(args...), except that it deferences all pointers. -func evalArgs(args ...interface{}) string { - // Optimization for simple common case of a single string argument. - if len(args) == 1 { - if s, ok := args[0].(string); ok { - return s - } - } - for i, arg := range args { - args[i] = indirectToStringerOrError(arg) - } - return fmt.Sprint(args...) -} - -// funcMap maps command names to functions that render their inputs safe. -var funcMap = template.FuncMap{ - "_html_template_attrescaper": attrEscaper, - "_html_template_commentescaper": commentEscaper, - "_html_template_cssescaper": cssEscaper, - "_html_template_cssvaluefilter": cssValueFilter, - "_html_template_htmlnamefilter": htmlNameFilter, - "_html_template_htmlescaper": htmlEscaper, - "_html_template_jsregexpescaper": jsRegexpEscaper, - "_html_template_jsstrescaper": jsStrEscaper, - "_html_template_jsvalescaper": jsValEscaper, - "_html_template_nospaceescaper": htmlNospaceEscaper, - "_html_template_rcdataescaper": rcdataEscaper, - "_html_template_srcsetescaper": srcsetFilterAndEscaper, - "_html_template_urlescaper": urlEscaper, - "_html_template_urlfilter": urlFilter, - "_html_template_urlnormalizer": urlNormalizer, - "_eval_args_": evalArgs, -} - -// escaper collects type inferences about templates and changes needed to make -// templates injection safe. -type escaper struct { - // ns is the nameSpace that this escaper is associated with. - ns *nameSpace - // output[templateName] is the output context for a templateName that - // has been mangled to include its input context. - output map[string]context - // derived[c.mangle(name)] maps to a template derived from the template - // named name templateName for the start context c. - derived map[string]*template.Template - // called[templateName] is a set of called mangled template names. - called map[string]bool - // xxxNodeEdits are the accumulated edits to apply during commit. - // Such edits are not applied immediately in case a template set - // executes a given template in different escaping contexts. - actionNodeEdits map[*parse.ActionNode][]string - templateNodeEdits map[*parse.TemplateNode]string - textNodeEdits map[*parse.TextNode][]byte - // rangeContext holds context about the current range loop. - rangeContext *rangeContext -} - -// rangeContext holds information about the current range loop. -type rangeContext struct { - outer *rangeContext // outer loop - breaks []context // context at each break action - continues []context // context at each continue action -} - -// makeEscaper creates a blank escaper for the given set. -func makeEscaper(n *nameSpace) escaper { - return escaper{ - n, - map[string]context{}, - map[string]*template.Template{}, - map[string]bool{}, - map[*parse.ActionNode][]string{}, - map[*parse.TemplateNode]string{}, - map[*parse.TextNode][]byte{}, - nil, - } -} - -// filterFailsafe is an innocuous word that is emitted in place of unsafe values -// by sanitizer functions. It is not a keyword in any programming language, -// contains no special characters, is not empty, and when it appears in output -// it is distinct enough that a developer can find the source of the problem -// via a search engine. -const filterFailsafe = "ZgotmplZ" - -// escape escapes a template node. -func (e *escaper) escape(c context, n parse.Node) context { - switch n := n.(type) { - case *parse.ActionNode: - return e.escapeAction(c, n) - case *parse.BreakNode: - c.n = n - e.rangeContext.breaks = append(e.rangeContext.breaks, c) - return context{state: stateDead} - case *parse.CommentNode: - return c - case *parse.ContinueNode: - c.n = n - e.rangeContext.continues = append(e.rangeContext.breaks, c) - return context{state: stateDead} - case *parse.IfNode: - return e.escapeBranch(c, &n.BranchNode, "if") - case *parse.ListNode: - return e.escapeList(c, n) - case *parse.RangeNode: - return e.escapeBranch(c, &n.BranchNode, "range") - case *parse.TemplateNode: - return e.escapeTemplate(c, n) - case *parse.TextNode: - return e.escapeText(c, n) - case *parse.WithNode: - return e.escapeBranch(c, &n.BranchNode, "with") - } - panic("escaping " + n.String() + " is unimplemented") -} - -// escapeAction escapes an action template node. -func (e *escaper) escapeAction(c context, n *parse.ActionNode) context { - if len(n.Pipe.Decl) != 0 { - // A local variable assignment, not an interpolation. - return c - } - c = nudge(c) - // Check for disallowed use of predefined escapers in the pipeline. - for pos, idNode := range n.Pipe.Cmds { - node, ok := idNode.Args[0].(*parse.IdentifierNode) - if !ok { - // A predefined escaper "esc" will never be found as an identifier in a - // Chain or Field node, since: - // - "esc.x ..." is invalid, since predefined escapers return strings, and - // strings do not have methods, keys or fields. - // - "... .esc" is invalid, since predefined escapers are global functions, - // not methods or fields of any types. - // Therefore, it is safe to ignore these two node types. - continue - } - ident := node.Ident - if _, ok := predefinedEscapers[ident]; ok { - if pos < len(n.Pipe.Cmds)-1 || - c.state == stateAttr && c.delim == delimSpaceOrTagEnd && ident == "html" { - return context{ - state: stateError, - err: errorf(ErrPredefinedEscaper, n, n.Line, "predefined escaper %q disallowed in template", ident), - } - } - } - } - s := make([]string, 0, 3) - switch c.state { - case stateError: - return c - case stateURL, stateCSSDqStr, stateCSSSqStr, stateCSSDqURL, stateCSSSqURL, stateCSSURL: - switch c.urlPart { - case urlPartNone: - s = append(s, "_html_template_urlfilter") - fallthrough - case urlPartPreQuery: - switch c.state { - case stateCSSDqStr, stateCSSSqStr: - s = append(s, "_html_template_cssescaper") - default: - s = append(s, "_html_template_urlnormalizer") - } - case urlPartQueryOrFrag: - s = append(s, "_html_template_urlescaper") - case urlPartUnknown: - return context{ - state: stateError, - err: errorf(ErrAmbigContext, n, n.Line, "%s appears in an ambiguous context within a URL", n), - } - default: - panic(c.urlPart.String()) - } - case stateJS: - s = append(s, "_html_template_jsvalescaper") - // A slash after a value starts a div operator. - c.jsCtx = jsCtxDivOp - case stateJSDqStr, stateJSSqStr: - s = append(s, "_html_template_jsstrescaper") - case stateJSRegexp: - s = append(s, "_html_template_jsregexpescaper") - case stateCSS: - s = append(s, "_html_template_cssvaluefilter") - case stateText: - s = append(s, "_html_template_htmlescaper") - case stateRCDATA: - s = append(s, "_html_template_rcdataescaper") - case stateAttr: - // Handled below in delim check. - case stateAttrName, stateTag: - c.state = stateAttrName - s = append(s, "_html_template_htmlnamefilter") - case stateSrcset: - s = append(s, "_html_template_srcsetescaper") - default: - if isComment(c.state) { - s = append(s, "_html_template_commentescaper") - } else { - panic("unexpected state " + c.state.String()) - } - } - switch c.delim { - case delimNone: - // No extra-escaping needed for raw text content. - case delimSpaceOrTagEnd: - s = append(s, "_html_template_nospaceescaper") - default: - s = append(s, "_html_template_attrescaper") - } - e.editActionNode(n, s) - return c -} - -// ensurePipelineContains ensures that the pipeline ends with the commands with -// the identifiers in s in order. If the pipeline ends with a predefined escaper -// (i.e. "html" or "urlquery"), merge it with the identifiers in s. -func ensurePipelineContains(p *parse.PipeNode, s []string) { - if len(s) == 0 { - // Do not rewrite pipeline if we have no escapers to insert. - return - } - // Precondition: p.Cmds contains at most one predefined escaper and the - // escaper will be present at p.Cmds[len(p.Cmds)-1]. This precondition is - // always true because of the checks in escapeAction. - pipelineLen := len(p.Cmds) - if pipelineLen > 0 { - lastCmd := p.Cmds[pipelineLen-1] - if idNode, ok := lastCmd.Args[0].(*parse.IdentifierNode); ok { - if esc := idNode.Ident; predefinedEscapers[esc] { - // Pipeline ends with a predefined escaper. - if len(p.Cmds) == 1 && len(lastCmd.Args) > 1 { - // Special case: pipeline is of the form {{ esc arg1 arg2 ... argN }}, - // where esc is the predefined escaper, and arg1...argN are its arguments. - // Convert this into the equivalent form - // {{ _eval_args_ arg1 arg2 ... argN | esc }}, so that esc can be easily - // merged with the escapers in s. - lastCmd.Args[0] = parse.NewIdentifier("_eval_args_").SetTree(nil).SetPos(lastCmd.Args[0].Position()) - p.Cmds = appendCmd(p.Cmds, newIdentCmd(esc, p.Position())) - pipelineLen++ - } - // If any of the commands in s that we are about to insert is equivalent - // to the predefined escaper, use the predefined escaper instead. - dup := false - for i, escaper := range s { - if escFnsEq(esc, escaper) { - s[i] = idNode.Ident - dup = true - } - } - if dup { - // The predefined escaper will already be inserted along with the - // escapers in s, so do not copy it to the rewritten pipeline. - pipelineLen-- - } - } - } - } - // Rewrite the pipeline, creating the escapers in s at the end of the pipeline. - newCmds := make([]*parse.CommandNode, pipelineLen, pipelineLen+len(s)) - insertedIdents := make(map[string]bool) - for i := 0; i < pipelineLen; i++ { - cmd := p.Cmds[i] - newCmds[i] = cmd - if idNode, ok := cmd.Args[0].(*parse.IdentifierNode); ok { - insertedIdents[normalizeEscFn(idNode.Ident)] = true - } - } - for _, name := range s { - if !insertedIdents[normalizeEscFn(name)] { - // When two templates share an underlying parse tree via the use of - // AddParseTree and one template is executed after the other, this check - // ensures that escapers that were already inserted into the pipeline on - // the first escaping pass do not get inserted again. - newCmds = appendCmd(newCmds, newIdentCmd(name, p.Position())) - } - } - p.Cmds = newCmds -} - -// predefinedEscapers contains template predefined escapers that are equivalent -// to some contextual escapers. Keep in sync with equivEscapers. -var predefinedEscapers = map[string]bool{ - "html": true, - "urlquery": true, -} - -// equivEscapers matches contextual escapers to equivalent predefined -// template escapers. -var equivEscapers = map[string]string{ - // The following pairs of HTML escapers provide equivalent security - // guarantees, since they all escape '\000', '\'', '"', '&', '<', and '>'. - "_html_template_attrescaper": "html", - "_html_template_htmlescaper": "html", - "_html_template_rcdataescaper": "html", - // These two URL escapers produce URLs safe for embedding in a URL query by - // percent-encoding all the reserved characters specified in RFC 3986 Section - // 2.2 - "_html_template_urlescaper": "urlquery", - // These two functions are not actually equivalent; urlquery is stricter as it - // escapes reserved characters (e.g. '#'), while _html_template_urlnormalizer - // does not. It is therefore only safe to replace _html_template_urlnormalizer - // with urlquery (this happens in ensurePipelineContains), but not the otherI've - // way around. We keep this entry around to preserve the behavior of templates - // written before Go 1.9, which might depend on this substitution taking place. - "_html_template_urlnormalizer": "urlquery", -} - -// escFnsEq reports whether the two escaping functions are equivalent. -func escFnsEq(a, b string) bool { - return normalizeEscFn(a) == normalizeEscFn(b) -} - -// normalizeEscFn(a) is equal to normalizeEscFn(b) for any pair of names of -// escaper functions a and b that are equivalent. -func normalizeEscFn(e string) string { - if norm := equivEscapers[e]; norm != "" { - return norm - } - return e -} - -// redundantFuncs[a][b] implies that funcMap[b](funcMap[a](x)) == funcMap[a](x) -// for all x. -var redundantFuncs = map[string]map[string]bool{ - "_html_template_commentescaper": { - "_html_template_attrescaper": true, - "_html_template_nospaceescaper": true, - "_html_template_htmlescaper": true, - }, - "_html_template_cssescaper": { - "_html_template_attrescaper": true, - }, - "_html_template_jsregexpescaper": { - "_html_template_attrescaper": true, - }, - "_html_template_jsstrescaper": { - "_html_template_attrescaper": true, - }, - "_html_template_urlescaper": { - "_html_template_urlnormalizer": true, - }, -} - -// appendCmd appends the given command to the end of the command pipeline -// unless it is redundant with the last command. -func appendCmd(cmds []*parse.CommandNode, cmd *parse.CommandNode) []*parse.CommandNode { - if n := len(cmds); n != 0 { - last, okLast := cmds[n-1].Args[0].(*parse.IdentifierNode) - next, okNext := cmd.Args[0].(*parse.IdentifierNode) - if okLast && okNext && redundantFuncs[last.Ident][next.Ident] { - return cmds - } - } - return append(cmds, cmd) -} - -// newIdentCmd produces a command containing a single identifier node. -func newIdentCmd(identifier string, pos parse.Pos) *parse.CommandNode { - return &parse.CommandNode{ - NodeType: parse.NodeCommand, - Args: []parse.Node{parse.NewIdentifier(identifier).SetTree(nil).SetPos(pos)}, // TODO: SetTree. - } -} - -// nudge returns the context that would result from following empty string -// transitions from the input context. -// For example, parsing: -// `<a href=` -// will end in context{stateBeforeValue, attrURL}, but parsing one extra rune: -// `<a href=x` -// will end in context{stateURL, delimSpaceOrTagEnd, ...}. -// There are two transitions that happen when the 'x' is seen: -// (1) Transition from a before-value state to a start-of-value state without -// consuming any character. -// (2) Consume 'x' and transition past the first value character. -// In this case, nudging produces the context after (1) happens. -func nudge(c context) context { - switch c.state { - case stateTag: - // In `<foo {{.}}`, the action should emit an attribute. - c.state = stateAttrName - case stateBeforeValue: - // In `<foo bar={{.}}`, the action is an undelimited value. - c.state, c.delim, c.attr = attrStartStates[c.attr], delimSpaceOrTagEnd, attrNone - case stateAfterName: - // In `<foo bar {{.}}`, the action is an attribute name. - c.state, c.attr = stateAttrName, attrNone - } - return c -} - -// join joins the two contexts of a branch template node. The result is an -// error context if either of the input contexts are error contexts, or if the -// input contexts differ. -func join(a, b context, node parse.Node, nodeName string) context { - if a.state == stateError { - return a - } - if b.state == stateError { - return b - } - if a.state == stateDead { - return b - } - if b.state == stateDead { - return a - } - if a.eq(b) { - return a - } - - c := a - c.urlPart = b.urlPart - if c.eq(b) { - // The contexts differ only by urlPart. - c.urlPart = urlPartUnknown - return c - } - - c = a - c.jsCtx = b.jsCtx - if c.eq(b) { - // The contexts differ only by jsCtx. - c.jsCtx = jsCtxUnknown - return c - } - - // Allow a nudged context to join with an unnudged one. - // This means that - // <p title={{if .C}}{{.}}{{end}} - // ends in an unquoted value state even though the else branch - // ends in stateBeforeValue. - if c, d := nudge(a), nudge(b); !(c.eq(a) && d.eq(b)) { - if e := join(c, d, node, nodeName); e.state != stateError { - return e - } - } - - return context{ - state: stateError, - err: errorf(ErrBranchEnd, node, 0, "{{%s}} branches end in different contexts: %v, %v", nodeName, a, b), - } -} - -// escapeBranch escapes a branch template node: "if", "range" and "with". -func (e *escaper) escapeBranch(c context, n *parse.BranchNode, nodeName string) context { - if nodeName == "range" { - e.rangeContext = &rangeContext{outer: e.rangeContext} - } - c0 := e.escapeList(c, n.List) - if nodeName == "range" { - if c0.state != stateError { - c0 = joinRange(c0, e.rangeContext) - } - e.rangeContext = e.rangeContext.outer - if c0.state == stateError { - return c0 - } - - // The "true" branch of a "range" node can execute multiple times. - // We check that executing n.List once results in the same context - // as executing n.List twice. - e.rangeContext = &rangeContext{outer: e.rangeContext} - c1, _ := e.escapeListConditionally(c0, n.List, nil) - c0 = join(c0, c1, n, nodeName) - if c0.state == stateError { - e.rangeContext = e.rangeContext.outer - // Make clear that this is a problem on loop re-entry - // since developers tend to overlook that branch when - // debugging templates. - c0.err.Line = n.Line - c0.err.Description = "on range loop re-entry: " + c0.err.Description - return c0 - } - c0 = joinRange(c0, e.rangeContext) - e.rangeContext = e.rangeContext.outer - if c0.state == stateError { - return c0 - } - } - c1 := e.escapeList(c, n.ElseList) - return join(c0, c1, n, nodeName) -} - -func joinRange(c0 context, rc *rangeContext) context { - // Merge contexts at break and continue statements into overall body context. - // In theory we could treat breaks differently from continues, but for now it is - // enough to treat them both as going back to the start of the loop (which may then stop). - for _, c := range rc.breaks { - c0 = join(c0, c, c.n, "range") - if c0.state == stateError { - c0.err.Line = c.n.(*parse.BreakNode).Line - c0.err.Description = "at range loop break: " + c0.err.Description - return c0 - } - } - for _, c := range rc.continues { - c0 = join(c0, c, c.n, "range") - if c0.state == stateError { - c0.err.Line = c.n.(*parse.ContinueNode).Line - c0.err.Description = "at range loop continue: " + c0.err.Description - return c0 - } - } - return c0 -} - -// escapeList escapes a list template node. -func (e *escaper) escapeList(c context, n *parse.ListNode) context { - if n == nil { - return c - } - for _, m := range n.Nodes { - c = e.escape(c, m) - if c.state == stateDead { - break - } - } - return c -} - -// escapeListConditionally escapes a list node but only preserves edits and -// inferences in e if the inferences and output context satisfy filter. -// It returns the best guess at an output context, and the result of the filter -// which is the same as whether e was updated. -func (e *escaper) escapeListConditionally(c context, n *parse.ListNode, filter func(*escaper, context) bool) (context, bool) { - e1 := makeEscaper(e.ns) - e1.rangeContext = e.rangeContext - // Make type inferences available to f. - for k, v := range e.output { - e1.output[k] = v - } - c = e1.escapeList(c, n) - ok := filter != nil && filter(&e1, c) - if ok { - // Copy inferences and edits from e1 back into e. - for k, v := range e1.output { - e.output[k] = v - } - for k, v := range e1.derived { - e.derived[k] = v - } - for k, v := range e1.called { - e.called[k] = v - } - for k, v := range e1.actionNodeEdits { - e.editActionNode(k, v) - } - for k, v := range e1.templateNodeEdits { - e.editTemplateNode(k, v) - } - for k, v := range e1.textNodeEdits { - e.editTextNode(k, v) - } - } - return c, ok -} - -// escapeTemplate escapes a {{template}} call node. -func (e *escaper) escapeTemplate(c context, n *parse.TemplateNode) context { - c, name := e.escapeTree(c, n, n.Name, n.Line) - if name != n.Name { - e.editTemplateNode(n, name) - } - return c -} - -// escapeTree escapes the named template starting in the given context as -// necessary and returns its output context. -func (e *escaper) escapeTree(c context, node parse.Node, name string, line int) (context, string) { - // Mangle the template name with the input context to produce a reliable - // identifier. - dname := c.mangle(name) - e.called[dname] = true - if out, ok := e.output[dname]; ok { - // Already escaped. - return out, dname - } - t := e.template(name) - if t == nil { - // Two cases: The template exists but is empty, or has never been mentioned at - // all. Distinguish the cases in the error messages. - if e.ns.set[name] != nil { - return context{ - state: stateError, - err: errorf(ErrNoSuchTemplate, node, line, "%q is an incomplete or empty template", name), - }, dname - } - return context{ - state: stateError, - err: errorf(ErrNoSuchTemplate, node, line, "no such template %q", name), - }, dname - } - if dname != name { - // Use any template derived during an earlier call to escapeTemplate - // with different top level templates, or clone if necessary. - dt := e.template(dname) - if dt == nil { - dt = template.New(dname) - dt.Tree = &parse.Tree{Name: dname, Root: t.Root.CopyList()} - e.derived[dname] = dt - } - t = dt - } - return e.computeOutCtx(c, t), dname -} - -// computeOutCtx takes a template and its start context and computes the output -// context while storing any inferences in e. -func (e *escaper) computeOutCtx(c context, t *template.Template) context { - // Propagate context over the body. - c1, ok := e.escapeTemplateBody(c, t) - if !ok { - // Look for a fixed point by assuming c1 as the output context. - if c2, ok2 := e.escapeTemplateBody(c1, t); ok2 { - c1, ok = c2, true - } - // Use c1 as the error context if neither assumption worked. - } - if !ok && c1.state != stateError { - return context{ - state: stateError, - err: errorf(ErrOutputContext, t.Tree.Root, 0, "cannot compute output context for template %s", t.Name()), - } - } - return c1 -} - -// escapeTemplateBody escapes the given template assuming the given output -// context, and returns the best guess at the output context and whether the -// assumption was correct. -func (e *escaper) escapeTemplateBody(c context, t *template.Template) (context, bool) { - filter := func(e1 *escaper, c1 context) bool { - if c1.state == stateError { - // Do not update the input escaper, e. - return false - } - if !e1.called[t.Name()] { - // If t is not recursively called, then c1 is an - // accurate output context. - return true - } - // c1 is accurate if it matches our assumed output context. - return c.eq(c1) - } - // We need to assume an output context so that recursive template calls - // take the fast path out of escapeTree instead of infinitely recursing. - // Naively assuming that the input context is the same as the output - // works >90% of the time. - e.output[t.Name()] = c - return e.escapeListConditionally(c, t.Tree.Root, filter) -} - -// delimEnds maps each delim to a string of characters that terminate it. -var delimEnds = [...]string{ - delimDoubleQuote: `"`, - delimSingleQuote: "'", - // Determined empirically by running the below in various browsers. - // var div = document.createElement("DIV"); - // for (var i = 0; i < 0x10000; ++i) { - // div.innerHTML = "<span title=x" + String.fromCharCode(i) + "-bar>"; - // if (div.getElementsByTagName("SPAN")[0].title.indexOf("bar") < 0) - // document.write("<p>U+" + i.toString(16)); - // } - delimSpaceOrTagEnd: " \t\n\f\r>", -} - -var doctypeBytes = []byte("<!DOCTYPE") - -// escapeText escapes a text template node. -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:]) - i1 := i + nread - if c.state == stateText || c.state == stateRCDATA { - end := i1 - if c1.state != c.state { - for j := end - 1; j >= i; j-- { - if s[j] == '<' { - end = j - break - } - } - } - for j := i; j < end; j++ { - if s[j] == '<' && !bytes.HasPrefix(bytes.ToUpper(s[j:]), doctypeBytes) { - b.Write(s[written:j]) - b.WriteString("<") - written = j + 1 - } - } - } else if isComment(c.state) && c.delim == delimNone { - switch c.state { - case stateJSBlockCmt: - // https://es5.github.com/#x7.4: - // "Comments behave like white space and are - // discarded except that, if a MultiLineComment - // contains a line terminator character, then - // the entire comment is considered to be a - // LineTerminator for purposes of parsing by - // the syntactic grammar." - if bytes.ContainsAny(s[written:i1], "\n\r\u2028\u2029") { - b.WriteByte('\n') - } else { - b.WriteByte(' ') - } - case stateCSSBlockCmt: - b.WriteByte(' ') - } - written = i1 - } - if c.state != c1.state && isComment(c1.state) && c1.delim == delimNone { - // Preserve the portion between written and the comment start. - cs := i1 - 2 - if c1.state == stateHTMLCmt { - // "<!--" instead of "/*" or "//" - cs -= 2 - } - b.Write(s[written:cs]) - written = i1 - } - if i == i1 && c.state == c1.state { - panic(fmt.Sprintf("infinite loop from %v to %v on %q..%q", c, c1, s[:i], s[i:])) - } - c, i = c1, i1 - } - - if written != 0 && c.state != stateError { - if !isComment(c.state) || c.delim != delimNone { - b.Write(n.Text[written:]) - } - e.editTextNode(n, b.Bytes()) - } - return c -} - -// contextAfterText starts in context c, consumes some tokens from the front of -// s, then returns the context after those tokens and the unprocessed suffix. -func contextAfterText(c context, s []byte) (context, int) { - if c.delim == delimNone { - c1, i := tSpecialTagEnd(c, s) - if i == 0 { - // A special end tag (`</script>`) has been seen and - // all content preceding it has been consumed. - return c1, 0 - } - // Consider all content up to any end tag. - return transitionFunc[c.state](c, s[:i]) - } - - // We are at the beginning of an attribute value. - - i := bytes.IndexAny(s, delimEnds[c.delim]) - if i == -1 { - i = len(s) - } - if c.delim == delimSpaceOrTagEnd { - // https://www.w3.org/TR/html5/syntax.html#attribute-value-(unquoted)-state - // lists the runes below as error characters. - // Error out because HTML parsers may differ on whether - // "<a id= onclick=f(" ends inside id's or onclick's value, - // "<a class=`foo " ends inside a value, - // "<a style=font:'Arial'" needs open-quote fixup. - // IE treats '`' as a quotation character. - if j := bytes.IndexAny(s[:i], "\"'<=`"); j >= 0 { - return context{ - state: stateError, - err: errorf(ErrBadHTML, nil, 0, "%q in unquoted attr: %q", s[j:j+1], s[:i]), - }, len(s) - } - } - if i == len(s) { - // Remain inside the attribute. - // Decode the value so non-HTML rules can easily handle - // <button onclick="alert("Hi!")"> - // without having to entity decode token boundaries. - for u := []byte(html.UnescapeString(string(s))); len(u) != 0; { - c1, i1 := transitionFunc[c.state](c, u) - c, u = c1, u[i1:] - } - return c, len(s) - } - - element := c.element - - // If this is a non-JS "type" attribute inside "script" tag, do not treat the contents as JS. - if c.state == stateAttr && c.element == elementScript && c.attr == attrScriptType && !isJSType(string(s[:i])) { - element = elementNone - } - - if c.delim != delimSpaceOrTagEnd { - // Consume any quote. - i++ - } - // On exiting an attribute, we discard all state information - // except the state and element. - return context{state: stateTag, element: element}, i -} - -// editActionNode records a change to an action pipeline for later commit. -func (e *escaper) editActionNode(n *parse.ActionNode, cmds []string) { - if _, ok := e.actionNodeEdits[n]; ok { - panic(fmt.Sprintf("node %s shared between templates", n)) - } - e.actionNodeEdits[n] = cmds -} - -// editTemplateNode records a change to a {{template}} callee for later commit. -func (e *escaper) editTemplateNode(n *parse.TemplateNode, callee string) { - if _, ok := e.templateNodeEdits[n]; ok { - panic(fmt.Sprintf("node %s shared between templates", n)) - } - e.templateNodeEdits[n] = callee -} - -// editTextNode records a change to a text node for later commit. -func (e *escaper) editTextNode(n *parse.TextNode, text []byte) { - if _, ok := e.textNodeEdits[n]; ok { - panic(fmt.Sprintf("node %s shared between templates", n)) - } - e.textNodeEdits[n] = text -} - -// commit applies changes to actions and template calls needed to contextually -// autoescape content and adds any derived templates to the set. -func (e *escaper) commit() { - for name := range e.output { - e.template(name).Funcs(funcMap) - } - // Any template from the name space associated with this escaper can be used - // to add derived templates to the underlying text/template name space. - tmpl := e.arbitraryTemplate() - for _, t := range e.derived { - if _, err := tmpl.text.AddParseTree(t.Name(), t.Tree); err != nil { - panic("error adding derived template") - } - } - for n, s := range e.actionNodeEdits { - ensurePipelineContains(n.Pipe, s) - } - for n, name := range e.templateNodeEdits { - n.Name = name - } - for n, s := range e.textNodeEdits { - n.Text = s - } - // Reset state that is specific to this commit so that the same changes are - // not re-applied to the template on subsequent calls to commit. - e.called = make(map[string]bool) - e.actionNodeEdits = make(map[*parse.ActionNode][]string) - e.templateNodeEdits = make(map[*parse.TemplateNode]string) - e.textNodeEdits = make(map[*parse.TextNode][]byte) -} - -// template returns the named template given a mangled template name. -func (e *escaper) template(name string) *template.Template { - // Any template from the name space associated with this escaper can be used - // to look up templates in the underlying text/template name space. - t := e.arbitraryTemplate().text.Lookup(name) - if t == nil { - t = e.derived[name] - } - return t -} - -// arbitraryTemplate returns an arbitrary template from the name space -// associated with e and panics if no templates are found. -func (e *escaper) arbitraryTemplate() *Template { - for _, t := range e.ns.set { - return t - } - panic("no templates in name space") -} - -// Forwarding functions so that clients need only import this package -// to reach the general escaping functions of text/template. - -// HTMLEscape writes to w the escaped HTML equivalent of the plain text data b. -func HTMLEscape(w io.Writer, b []byte) { - template.HTMLEscape(w, b) -} - -// HTMLEscapeString returns the escaped HTML equivalent of the plain text data s. -func HTMLEscapeString(s string) string { - return template.HTMLEscapeString(s) -} - -// HTMLEscaper returns the escaped HTML equivalent of the textual -// representation of its arguments. -func HTMLEscaper(args ...interface{}) string { - return template.HTMLEscaper(args...) -} - -// JSEscape writes to w the escaped JavaScript equivalent of the plain text data b. -func JSEscape(w io.Writer, b []byte) { - template.JSEscape(w, b) -} - -// JSEscapeString returns the escaped JavaScript equivalent of the plain text data s. -func JSEscapeString(s string) string { - return template.JSEscapeString(s) -} - -// JSEscaper returns the escaped JavaScript equivalent of the textual -// representation of its arguments. -func JSEscaper(args ...interface{}) string { - return template.JSEscaper(args...) -} - -// URLQueryEscaper returns the escaped value of the textual representation of -// its arguments in a form suitable for embedding in a URL query. -func URLQueryEscaper(args ...interface{}) string { - return template.URLQueryEscaper(args...) -}
diff --git a/go.dev/cmd/internal/html/template/escape_test.go b/go.dev/cmd/internal/html/template/escape_test.go deleted file mode 100644 index 8152ca2..0000000 --- a/go.dev/cmd/internal/html/template/escape_test.go +++ /dev/null
@@ -1,1994 +0,0 @@ -// Copyright 2011 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package template - -import ( - "bytes" - "encoding/json" - "fmt" - "os" - "strings" - "testing" - - "golang.org/x/website/go.dev/cmd/internal/text/template" - "golang.org/x/website/go.dev/cmd/internal/text/template/parse" -) - -type badMarshaler struct{} - -func (x *badMarshaler) MarshalJSON() ([]byte, error) { - // Keys in valid JSON must be double quoted as must all strings. - return []byte("{ foo: 'not quite valid JSON' }"), nil -} - -type goodMarshaler struct{} - -func (x *goodMarshaler) MarshalJSON() ([]byte, error) { - return []byte(`{ "<foo>": "O'Reilly" }`), nil -} - -func TestEscape(t *testing.T) { - data := struct { - F, T bool - C, G, H string - A, E []string - B, M json.Marshaler - N int - U interface{} // untyped nil - Z *int // typed nil - W HTML - }{ - F: false, - T: true, - C: "<Cincinnati>", - G: "<Goodbye>", - H: "<Hello>", - A: []string{"<a>", "<b>"}, - E: []string{}, - N: 42, - B: &badMarshaler{}, - M: &goodMarshaler{}, - U: nil, - Z: nil, - W: HTML(`¡<b class="foo">Hello</b>, <textarea>O'World</textarea>!`), - } - pdata := &data - - tests := []struct { - name string - input string - output string - }{ - { - "if", - "{{if .T}}Hello{{end}}, {{.C}}!", - "Hello, <Cincinnati>!", - }, - { - "else", - "{{if .F}}{{.H}}{{else}}{{.G}}{{end}}!", - "<Goodbye>!", - }, - { - "overescaping1", - "Hello, {{.C | html}}!", - "Hello, <Cincinnati>!", - }, - { - "overescaping2", - "Hello, {{html .C}}!", - "Hello, <Cincinnati>!", - }, - { - "overescaping3", - "{{with .C}}{{$msg := .}}Hello, {{$msg}}!{{end}}", - "Hello, <Cincinnati>!", - }, - { - "assignment", - "{{if $x := .H}}{{$x}}{{end}}", - "<Hello>", - }, - { - "withBody", - "{{with .H}}{{.}}{{end}}", - "<Hello>", - }, - { - "withElse", - "{{with .E}}{{.}}{{else}}{{.H}}{{end}}", - "<Hello>", - }, - { - "rangeBody", - "{{range .A}}{{.}}{{end}}", - "<a><b>", - }, - { - "rangeElse", - "{{range .E}}{{.}}{{else}}{{.H}}{{end}}", - "<Hello>", - }, - { - "nonStringValue", - "{{.T}}", - "true", - }, - { - "untypedNilValue", - "{{.U}}", - "", - }, - { - "typedNilValue", - "{{.Z}}", - "<nil>", - }, - { - "constant", - `<a href="/search?q={{"'a<b'"}}">`, - `<a href="/search?q=%27a%3cb%27">`, - }, - { - "multipleAttrs", - "<a b=1 c={{.H}}>", - "<a b=1 c=<Hello>>", - }, - { - "urlStartRel", - `<a href='{{"/foo/bar?a=b&c=d"}}'>`, - `<a href='/foo/bar?a=b&c=d'>`, - }, - { - "urlStartAbsOk", - `<a href='{{"http://example.com/foo/bar?a=b&c=d"}}'>`, - `<a href='http://example.com/foo/bar?a=b&c=d'>`, - }, - { - "protocolRelativeURLStart", - `<a href='{{"//example.com:8000/foo/bar?a=b&c=d"}}'>`, - `<a href='//example.com:8000/foo/bar?a=b&c=d'>`, - }, - { - "pathRelativeURLStart", - `<a href="{{"/javascript:80/foo/bar"}}">`, - `<a href="/javascript:80/foo/bar">`, - }, - { - "dangerousURLStart", - `<a href='{{"javascript:alert(%22pwned%22)"}}'>`, - `<a href='#ZgotmplZ'>`, - }, - { - "dangerousURLStart2", - `<a href=' {{"javascript:alert(%22pwned%22)"}}'>`, - `<a href=' #ZgotmplZ'>`, - }, - { - "nonHierURL", - `<a href={{"mailto:Muhammed \"The Greatest\" Ali <m.ali@example.com>"}}>`, - `<a href=mailto:Muhammed%20%22The%20Greatest%22%20Ali%20%3cm.ali@example.com%3e>`, - }, - { - "urlPath", - `<a href='http://{{"javascript:80"}}/foo'>`, - `<a href='http://javascript:80/foo'>`, - }, - { - "urlQuery", - `<a href='/search?q={{.H}}'>`, - `<a href='/search?q=%3cHello%3e'>`, - }, - { - "urlFragment", - `<a href='/faq#{{.H}}'>`, - `<a href='/faq#%3cHello%3e'>`, - }, - { - "urlBranch", - `<a href="{{if .F}}/foo?a=b{{else}}/bar{{end}}">`, - `<a href="/bar">`, - }, - { - "urlBranchConflictMoot", - `<a href="{{if .T}}/foo?a={{else}}/bar#{{end}}{{.C}}">`, - `<a href="/foo?a=%3cCincinnati%3e">`, - }, - { - "jsStrValue", - "<button onclick='alert({{.H}})'>", - `<button onclick='alert("\u003cHello\u003e")'>`, - }, - { - "jsNumericValue", - "<button onclick='alert({{.N}})'>", - `<button onclick='alert( 42 )'>`, - }, - { - "jsBoolValue", - "<button onclick='alert({{.T}})'>", - `<button onclick='alert( true )'>`, - }, - { - "jsNilValueTyped", - "<button onclick='alert(typeof{{.Z}})'>", - `<button onclick='alert(typeof null )'>`, - }, - { - "jsNilValueUntyped", - "<button onclick='alert(typeof{{.U}})'>", - `<button onclick='alert(typeof null )'>`, - }, - { - "jsObjValue", - "<button onclick='alert({{.A}})'>", - `<button onclick='alert(["\u003ca\u003e","\u003cb\u003e"])'>`, - }, - { - "jsObjValueScript", - "<script>alert({{.A}})</script>", - `<script>alert(["\u003ca\u003e","\u003cb\u003e"])</script>`, - }, - { - "jsObjValueNotOverEscaped", - "<button onclick='alert({{.A | html}})'>", - `<button onclick='alert(["\u003ca\u003e","\u003cb\u003e"])'>`, - }, - { - "jsStr", - "<button onclick='alert("{{.H}}")'>", - `<button onclick='alert("\u003cHello\u003e")'>`, - }, - { - "badMarshaler", - `<button onclick='alert(1/{{.B}}in numbers)'>`, - `<button onclick='alert(1/ /* json: error calling MarshalJSON for type *template.badMarshaler: invalid character 'f' looking for beginning of object key string */null in numbers)'>`, - }, - { - "jsMarshaler", - `<button onclick='alert({{.M}})'>`, - `<button onclick='alert({"\u003cfoo\u003e":"O'Reilly"})'>`, - }, - { - "jsStrNotUnderEscaped", - "<button onclick='alert({{.C | urlquery}})'>", - // URL escaped, then quoted for JS. - `<button onclick='alert("%3CCincinnati%3E")'>`, - }, - { - "jsRe", - `<button onclick='alert(/{{"foo+bar"}}/.test(""))'>`, - `<button onclick='alert(/foo\u002bbar/.test(""))'>`, - }, - { - "jsReBlank", - `<script>alert(/{{""}}/.test(""));</script>`, - `<script>alert(/(?:)/.test(""));</script>`, - }, - { - "jsReAmbigOk", - `<script>{{if true}}var x = 1{{end}}</script>`, - // The {if} ends in an ambiguous jsCtx but there is - // no slash following so we shouldn't care. - `<script>var x = 1</script>`, - }, - { - "styleBidiKeywordPassed", - `<p style="dir: {{"ltr"}}">`, - `<p style="dir: ltr">`, - }, - { - "styleBidiPropNamePassed", - `<p style="border-{{"left"}}: 0; border-{{"right"}}: 1in">`, - `<p style="border-left: 0; border-right: 1in">`, - }, - { - "styleExpressionBlocked", - `<p style="width: {{"expression(alert(1337))"}}">`, - `<p style="width: ZgotmplZ">`, - }, - { - "styleTagSelectorPassed", - `<style>{{"p"}} { color: pink }</style>`, - `<style>p { color: pink }</style>`, - }, - { - "styleIDPassed", - `<style>p{{"#my-ID"}} { font: Arial }</style>`, - `<style>p#my-ID { font: Arial }</style>`, - }, - { - "styleClassPassed", - `<style>p{{".my_class"}} { font: Arial }</style>`, - `<style>p.my_class { font: Arial }</style>`, - }, - { - "styleQuantityPassed", - `<a style="left: {{"2em"}}; top: {{0}}">`, - `<a style="left: 2em; top: 0">`, - }, - { - "stylePctPassed", - `<table style=width:{{"100%"}}>`, - `<table style=width:100%>`, - }, - { - "styleColorPassed", - `<p style="color: {{"#8ff"}}; background: {{"#000"}}">`, - `<p style="color: #8ff; background: #000">`, - }, - { - "styleObfuscatedExpressionBlocked", - `<p style="width: {{" e\\78preS\x00Sio/**/n(alert(1337))"}}">`, - `<p style="width: ZgotmplZ">`, - }, - { - "styleMozBindingBlocked", - `<p style="{{"-moz-binding(alert(1337))"}}: ...">`, - `<p style="ZgotmplZ: ...">`, - }, - { - "styleObfuscatedMozBindingBlocked", - `<p style="{{" -mo\\7a-B\x00I/**/nding(alert(1337))"}}: ...">`, - `<p style="ZgotmplZ: ...">`, - }, - { - "styleFontNameString", - `<p style='font-family: "{{"Times New Roman"}}"'>`, - `<p style='font-family: "Times New Roman"'>`, - }, - { - "styleFontNameString", - `<p style='font-family: "{{"Times New Roman"}}", "{{"sans-serif"}}"'>`, - `<p style='font-family: "Times New Roman", "sans-serif"'>`, - }, - { - "styleFontNameUnquoted", - `<p style='font-family: {{"Times New Roman"}}'>`, - `<p style='font-family: Times New Roman'>`, - }, - { - "styleURLQueryEncoded", - `<p style="background: url(/img?name={{"O'Reilly Animal(1)<2>.png"}})">`, - `<p style="background: url(/img?name=O%27Reilly%20Animal%281%29%3c2%3e.png)">`, - }, - { - "styleQuotedURLQueryEncoded", - `<p style="background: url('/img?name={{"O'Reilly Animal(1)<2>.png"}}')">`, - `<p style="background: url('/img?name=O%27Reilly%20Animal%281%29%3c2%3e.png')">`, - }, - { - "styleStrQueryEncoded", - `<p style="background: '/img?name={{"O'Reilly Animal(1)<2>.png"}}'">`, - `<p style="background: '/img?name=O%27Reilly%20Animal%281%29%3c2%3e.png'">`, - }, - { - "styleURLBadProtocolBlocked", - `<a style="background: url('{{"javascript:alert(1337)"}}')">`, - `<a style="background: url('#ZgotmplZ')">`, - }, - { - "styleStrBadProtocolBlocked", - `<a style="background: '{{"vbscript:alert(1337)"}}'">`, - `<a style="background: '#ZgotmplZ'">`, - }, - { - "styleStrEncodedProtocolEncoded", - `<a style="background: '{{"javascript\\3a alert(1337)"}}'">`, - // The CSS string 'javascript\\3a alert(1337)' does not contain a colon. - `<a style="background: 'javascript\\3a alert\28 1337\29 '">`, - }, - { - "styleURLGoodProtocolPassed", - `<a style="background: url('{{"http://oreilly.com/O'Reilly Animals(1)<2>;{}.html"}}')">`, - `<a style="background: url('http://oreilly.com/O%27Reilly%20Animals%281%29%3c2%3e;%7b%7d.html')">`, - }, - { - "styleStrGoodProtocolPassed", - `<a style="background: '{{"http://oreilly.com/O'Reilly Animals(1)<2>;{}.html"}}'">`, - `<a style="background: 'http\3a\2f\2foreilly.com\2fO\27Reilly Animals\28 1\29\3c 2\3e\3b\7b\7d.html'">`, - }, - { - "styleURLEncodedForHTMLInAttr", - `<a style="background: url('{{"/search?img=foo&size=icon"}}')">`, - `<a style="background: url('/search?img=foo&size=icon')">`, - }, - { - "styleURLNotEncodedForHTMLInCdata", - `<style>body { background: url('{{"/search?img=foo&size=icon"}}') }</style>`, - `<style>body { background: url('/search?img=foo&size=icon') }</style>`, - }, - { - "styleURLMixedCase", - `<p style="background: URL(#{{.H}})">`, - `<p style="background: URL(#%3cHello%3e)">`, - }, - { - "stylePropertyPairPassed", - `<a style='{{"color: red"}}'>`, - `<a style='color: red'>`, - }, - { - "styleStrSpecialsEncoded", - `<a style="font-family: '{{"/**/'\";:// \\"}}', "{{"/**/'\";:// \\"}}"">`, - `<a style="font-family: '\2f**\2f\27\22\3b\3a\2f\2f \\', "\2f**\2f\27\22\3b\3a\2f\2f \\"">`, - }, - { - "styleURLSpecialsEncoded", - `<a style="border-image: url({{"/**/'\";:// \\"}}), url("{{"/**/'\";:// \\"}}"), url('{{"/**/'\";:// \\"}}'), 'http://www.example.com/?q={{"/**/'\";:// \\"}}''">`, - `<a style="border-image: url(/**/%27%22;://%20%5c), url("/**/%27%22;://%20%5c"), url('/**/%27%22;://%20%5c'), 'http://www.example.com/?q=%2f%2a%2a%2f%27%22%3b%3a%2f%2f%20%5c''">`, - }, - { - "HTML comment", - "<b>Hello, <!-- name of world -->{{.C}}</b>", - "<b>Hello, <Cincinnati></b>", - }, - { - "HTML comment not first < in text node.", - "<<!-- -->!--", - "<!--", - }, - { - "HTML normalization 1", - "a < b", - "a < b", - }, - { - "HTML normalization 2", - "a << b", - "a << b", - }, - { - "HTML normalization 3", - "a<<!-- --><!-- -->b", - "a<b", - }, - { - "HTML doctype not normalized", - "<!DOCTYPE html>Hello, World!", - "<!DOCTYPE html>Hello, World!", - }, - { - "HTML doctype not case-insensitive", - "<!doCtYPE htMl>Hello, World!", - "<!doCtYPE htMl>Hello, World!", - }, - { - "No doctype injection", - `<!{{"DOCTYPE"}}`, - "<!DOCTYPE", - }, - { - "Split HTML comment", - "<b>Hello, <!-- name of {{if .T}}city -->{{.C}}{{else}}world -->{{.W}}{{end}}</b>", - "<b>Hello, <Cincinnati></b>", - }, - { - "JS line comment", - "<script>for (;;) { if (c()) break// foo not a label\n" + - "foo({{.T}});}</script>", - "<script>for (;;) { if (c()) break\n" + - "foo( true );}</script>", - }, - { - "JS multiline block comment", - "<script>for (;;) { if (c()) break/* foo not a label\n" + - " */foo({{.T}});}</script>", - // Newline separates break from call. If newline - // removed, then break will consume label leaving - // code invalid. - "<script>for (;;) { if (c()) break\n" + - "foo( true );}</script>", - }, - { - "JS single-line block comment", - "<script>for (;;) {\n" + - "if (c()) break/* foo a label */foo;" + - "x({{.T}});}</script>", - // Newline separates break from call. If newline - // removed, then break will consume label leaving - // code invalid. - "<script>for (;;) {\n" + - "if (c()) break foo;" + - "x( true );}</script>", - }, - { - "JS block comment flush with mathematical division", - "<script>var a/*b*//c\nd</script>", - "<script>var a /c\nd</script>", - }, - { - "JS mixed comments", - "<script>var a/*b*///c\nd</script>", - "<script>var a \nd</script>", - }, - { - "CSS comments", - "<style>p// paragraph\n" + - `{border: 1px/* color */{{"#00f"}}}</style>`, - "<style>p\n" + - "{border: 1px #00f}</style>", - }, - { - "JS attr block comment", - `<a onclick="f(""); /* alert({{.H}}) */">`, - // Attribute comment tests should pass if the comments - // are successfully elided. - `<a onclick="f(""); /* alert() */">`, - }, - { - "JS attr line comment", - `<a onclick="// alert({{.G}})">`, - `<a onclick="// alert()">`, - }, - { - "CSS attr block comment", - `<a style="/* color: {{.H}} */">`, - `<a style="/* color: */">`, - }, - { - "CSS attr line comment", - `<a style="// color: {{.G}}">`, - `<a style="// color: ">`, - }, - { - "HTML substitution commented out", - "<p><!-- {{.H}} --></p>", - "<p></p>", - }, - { - "Comment ends flush with start", - "<!--{{.}}--><script>/*{{.}}*///{{.}}\n</script><style>/*{{.}}*///{{.}}\n</style><a onclick='/*{{.}}*///{{.}}' style='/*{{.}}*///{{.}}'>", - "<script> \n</script><style> \n</style><a onclick='/**///' style='/**///'>", - }, - { - "typed HTML in text", - `{{.W}}`, - `¡<b class="foo">Hello</b>, <textarea>O'World</textarea>!`, - }, - { - "typed HTML in attribute", - `<div title="{{.W}}">`, - `<div title="¡Hello, O'World!">`, - }, - { - "typed HTML in script", - `<button onclick="alert({{.W}})">`, - `<button onclick="alert("\u0026iexcl;\u003cb class=\"foo\"\u003eHello\u003c/b\u003e, \u003ctextarea\u003eO'World\u003c/textarea\u003e!")">`, - }, - { - "typed HTML in RCDATA", - `<textarea>{{.W}}</textarea>`, - `<textarea>¡<b class="foo">Hello</b>, <textarea>O'World</textarea>!</textarea>`, - }, - { - "range in textarea", - "<textarea>{{range .A}}{{.}}{{end}}</textarea>", - "<textarea><a><b></textarea>", - }, - { - "No tag injection", - `{{"10$"}}<{{"script src,evil.org/pwnd.js"}}...`, - `10$<script src,evil.org/pwnd.js...`, - }, - { - "No comment injection", - `<{{"!--"}}`, - `<!--`, - }, - { - "No RCDATA end tag injection", - `<textarea><{{"/textarea "}}...</textarea>`, - `<textarea></textarea ...</textarea>`, - }, - { - "optional attrs", - `<img class="{{"iconClass"}}"` + - `{{if .T}} id="{{"<iconId>"}}"{{end}}` + - // Double quotes inside if/else. - ` src=` + - `{{if .T}}"?{{"<iconPath>"}}"` + - `{{else}}"images/cleardot.gif"{{end}}` + - // Missing space before title, but it is not a - // part of the src attribute. - `{{if .T}}title="{{"<title>"}}"{{end}}` + - // Quotes outside if/else. - ` alt="` + - `{{if .T}}{{"<alt>"}}` + - `{{else}}{{if .F}}{{"<title>"}}{{end}}` + - `{{end}}"` + - `>`, - `<img class="iconClass" id="<iconId>" src="?%3ciconPath%3e"title="<title>" alt="<alt>">`, - }, - { - "conditional valueless attr name", - `<input{{if .T}} checked{{end}} name=n>`, - `<input checked name=n>`, - }, - { - "conditional dynamic valueless attr name 1", - `<input{{if .T}} {{"checked"}}{{end}} name=n>`, - `<input checked name=n>`, - }, - { - "conditional dynamic valueless attr name 2", - `<input {{if .T}}{{"checked"}} {{end}}name=n>`, - `<input checked name=n>`, - }, - { - "dynamic attribute name", - `<img on{{"load"}}="alert({{"loaded"}})">`, - // Treated as JS since quotes are inserted. - `<img onload="alert("loaded")">`, - }, - { - "bad dynamic attribute name 1", - // Allow checked, selected, disabled, but not JS or - // CSS attributes. - `<input {{"onchange"}}="{{"doEvil()"}}">`, - `<input ZgotmplZ="doEvil()">`, - }, - { - "bad dynamic attribute name 2", - `<div {{"sTyle"}}="{{"color: expression(alert(1337))"}}">`, - `<div ZgotmplZ="color: expression(alert(1337))">`, - }, - { - "bad dynamic attribute name 3", - // Allow title or alt, but not a URL. - `<img {{"src"}}="{{"javascript:doEvil()"}}">`, - `<img ZgotmplZ="javascript:doEvil()">`, - }, - { - "bad dynamic attribute name 4", - // Structure preservation requires values to associate - // with a consistent attribute. - `<input checked {{""}}="Whose value am I?">`, - `<input checked ZgotmplZ="Whose value am I?">`, - }, - { - "dynamic element name", - `<h{{3}}><table><t{{"head"}}>...</h{{3}}>`, - `<h3><table><thead>...</h3>`, - }, - { - "bad dynamic element name", - // Dynamic element names are typically used to switch - // between (thead, tfoot, tbody), (ul, ol), (th, td), - // and other replaceable sets. - // We do not currently easily support (ul, ol). - // If we do change to support that, this test should - // catch failures to filter out special tag names which - // would violate the structure preservation property -- - // if any special tag name could be substituted, then - // the content could be raw text/RCDATA for some inputs - // and regular HTML content for others. - `<{{"script"}}>{{"doEvil()"}}</{{"script"}}>`, - `<script>doEvil()</script>`, - }, - { - "srcset bad URL in second position", - `<img srcset="{{"/not-an-image#,javascript:alert(1)"}}">`, - // The second URL is also filtered. - `<img srcset="/not-an-image#,#ZgotmplZ">`, - }, - { - "srcset buffer growth", - `<img srcset={{",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,"}}>`, - `<img srcset=,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,>`, - }, - } - - for _, test := range tests { - tmpl := New(test.name) - tmpl = Must(tmpl.Parse(test.input)) - // Check for bug 6459: Tree field was not set in Parse. - if tmpl.Tree != tmpl.text.Tree { - t.Errorf("%s: tree not set properly", test.name) - continue - } - b := new(bytes.Buffer) - if err := tmpl.Execute(b, data); err != nil { - t.Errorf("%s: template execution failed: %s", test.name, err) - continue - } - if w, g := test.output, b.String(); w != g { - t.Errorf("%s: escaped output: want\n\t%q\ngot\n\t%q", test.name, w, g) - continue - } - b.Reset() - if err := tmpl.Execute(b, pdata); err != nil { - t.Errorf("%s: template execution failed for pointer: %s", test.name, err) - continue - } - if w, g := test.output, b.String(); w != g { - t.Errorf("%s: escaped output for pointer: want\n\t%q\ngot\n\t%q", test.name, w, g) - continue - } - if tmpl.Tree != tmpl.text.Tree { - t.Errorf("%s: tree mismatch", test.name) - continue - } - } -} - -func TestEscapeMap(t *testing.T) { - data := map[string]string{ - "html": `<h1>Hi!</h1>`, - "urlquery": `http://www.foo.com/index.html?title=main`, - } - for _, test := range [...]struct { - desc, input, output string - }{ - // covering issue 20323 - { - "field with predefined escaper name 1", - `{{.html | print}}`, - `<h1>Hi!</h1>`, - }, - // covering issue 20323 - { - "field with predefined escaper name 2", - `{{.urlquery | print}}`, - `http://www.foo.com/index.html?title=main`, - }, - } { - tmpl := Must(New("").Parse(test.input)) - b := new(bytes.Buffer) - if err := tmpl.Execute(b, data); err != nil { - t.Errorf("%s: template execution failed: %s", test.desc, err) - continue - } - if w, g := test.output, b.String(); w != g { - t.Errorf("%s: escaped output: want\n\t%q\ngot\n\t%q", test.desc, w, g) - continue - } - } -} - -func TestEscapeSet(t *testing.T) { - type dataItem struct { - Children []*dataItem - X string - } - - data := dataItem{ - Children: []*dataItem{ - {X: "foo"}, - {X: "<bar>"}, - { - Children: []*dataItem{ - {X: "baz"}, - }, - }, - }, - } - - tests := []struct { - inputs map[string]string - want string - }{ - // The trivial set. - { - map[string]string{ - "main": ``, - }, - ``, - }, - // A template called in the start context. - { - map[string]string{ - "main": `Hello, {{template "helper"}}!`, - // Not a valid top level HTML template. - // "<b" is not a full tag. - "helper": `{{"<World>"}}`, - }, - `Hello, <World>!`, - }, - // A template called in a context other than the start. - { - map[string]string{ - "main": `<a onclick='a = {{template "helper"}};'>`, - // Not a valid top level HTML template. - // "<b" is not a full tag. - "helper": `{{"<a>"}}<b`, - }, - `<a onclick='a = "\u003ca\u003e"<b;'>`, - }, - // A recursive template that ends in its start context. - { - map[string]string{ - "main": `{{range .Children}}{{template "main" .}}{{else}}{{.X}} {{end}}`, - }, - `foo <bar> baz `, - }, - // A recursive helper template that ends in its start context. - { - map[string]string{ - "main": `{{template "helper" .}}`, - "helper": `{{if .Children}}<ul>{{range .Children}}<li>{{template "main" .}}</li>{{end}}</ul>{{else}}{{.X}}{{end}}`, - }, - `<ul><li>foo</li><li><bar></li><li><ul><li>baz</li></ul></li></ul>`, - }, - // Co-recursive templates that end in its start context. - { - map[string]string{ - "main": `<blockquote>{{range .Children}}{{template "helper" .}}{{end}}</blockquote>`, - "helper": `{{if .Children}}{{template "main" .}}{{else}}{{.X}}<br>{{end}}`, - }, - `<blockquote>foo<br><bar><br><blockquote>baz<br></blockquote></blockquote>`, - }, - // A template that is called in two different contexts. - { - map[string]string{ - "main": `<button onclick="title='{{template "helper"}}'; ...">{{template "helper"}}</button>`, - "helper": `{{11}} of {{"<100>"}}`, - }, - `<button onclick="title='11 of \u003c100\u003e'; ...">11 of <100></button>`, - }, - // A non-recursive template that ends in a different context. - // helper starts in jsCtxRegexp and ends in jsCtxDivOp. - { - map[string]string{ - "main": `<script>var x={{template "helper"}}/{{"42"}};</script>`, - "helper": "{{126}}", - }, - `<script>var x= 126 /"42";</script>`, - }, - // A recursive template that ends in a similar context. - { - map[string]string{ - "main": `<script>var x=[{{template "countdown" 4}}];</script>`, - "countdown": `{{.}}{{if .}},{{template "countdown" . | pred}}{{end}}`, - }, - `<script>var x=[ 4 , 3 , 2 , 1 , 0 ];</script>`, - }, - // A recursive template that ends in a different context. - /* - { - map[string]string{ - "main": `<a href="/foo{{template "helper" .}}">`, - "helper": `{{if .Children}}{{range .Children}}{{template "helper" .}}{{end}}{{else}}?x={{.X}}{{end}}`, - }, - `<a href="/foo?x=foo?x=%3cbar%3e?x=baz">`, - }, - */ - } - - // pred is a template function that returns the predecessor of a - // natural number for testing recursive templates. - fns := FuncMap{"pred": func(a ...interface{}) (interface{}, error) { - if len(a) == 1 { - if i, _ := a[0].(int); i > 0 { - return i - 1, nil - } - } - return nil, fmt.Errorf("undefined pred(%v)", a) - }} - - for _, test := range tests { - source := "" - for name, body := range test.inputs { - source += fmt.Sprintf("{{define %q}}%s{{end}} ", name, body) - } - tmpl, err := New("root").Funcs(fns).Parse(source) - if err != nil { - t.Errorf("error parsing %q: %v", source, err) - continue - } - var b bytes.Buffer - - if err := tmpl.ExecuteTemplate(&b, "main", data); err != nil { - t.Errorf("%q executing %v", err.Error(), tmpl.Lookup("main")) - continue - } - if got := b.String(); test.want != got { - t.Errorf("want\n\t%q\ngot\n\t%q", test.want, got) - } - } - -} - -func TestErrors(t *testing.T) { - tests := []struct { - input string - err string - }{ - // Non-error cases. - { - "{{if .Cond}}<a>{{else}}<b>{{end}}", - "", - }, - { - "{{if .Cond}}<a>{{end}}", - "", - }, - { - "{{if .Cond}}{{else}}<b>{{end}}", - "", - }, - { - "{{with .Cond}}<div>{{end}}", - "", - }, - { - "{{range .Items}}<a>{{end}}", - "", - }, - { - "<a href='/foo?{{range .Items}}&{{.K}}={{.V}}{{end}}'>", - "", - }, - { - "{{range .Items}}<a{{if .X}}{{end}}>{{end}}", - "", - }, - { - "{{range .Items}}<a{{if .X}}{{end}}>{{continue}}{{end}}", - "", - }, - { - "{{range .Items}}<a{{if .X}}{{end}}>{{break}}{{end}}", - "", - }, - { - "{{range .Items}}<a{{if .X}}{{end}}>{{if .X}}{{break}}{{end}}{{end}}", - "", - }, - // Error cases. - { - "{{if .Cond}}<a{{end}}", - "z:1:5: {{if}} branches", - }, - { - "{{if .Cond}}\n{{else}}\n<a{{end}}", - "z:1:5: {{if}} branches", - }, - { - // Missing quote in the else branch. - `{{if .Cond}}<a href="foo">{{else}}<a href="bar>{{end}}`, - "z:1:5: {{if}} branches", - }, - { - // Different kind of attribute: href implies a URL. - "<a {{if .Cond}}href='{{else}}title='{{end}}{{.X}}'>", - "z:1:8: {{if}} branches", - }, - { - "\n{{with .X}}<a{{end}}", - "z:2:7: {{with}} branches", - }, - { - "\n{{with .X}}<a>{{else}}<a{{end}}", - "z:2:7: {{with}} branches", - }, - { - "{{range .Items}}<a{{end}}", - `z:1: on range loop re-entry: "<" in attribute name: "<a"`, - }, - { - "\n{{range .Items}} x='<a{{end}}", - "z:2:8: on range loop re-entry: {{range}} branches", - }, - { - "{{range .Items}}<a{{if .X}}{{break}}{{end}}>{{end}}", - "z:1:29: at range loop break: {{range}} branches end in different contexts", - }, - { - "{{range .Items}}<a{{if .X}}{{continue}}{{end}}>{{end}}", - "z:1:29: at range loop continue: {{range}} branches end in different contexts", - }, - { - "<a b=1 c={{.H}}", - "z: ends in a non-text context: {stateAttr delimSpaceOrTagEnd", - }, - { - "<script>foo();", - "z: ends in a non-text context: {stateJS", - }, - { - `<a href="{{if .F}}/foo?a={{else}}/bar/{{end}}{{.H}}">`, - "z:1:47: {{.H}} appears in an ambiguous context within a URL", - }, - { - `<a onclick="alert('Hello \`, - `unfinished escape sequence in JS string: "Hello \\"`, - }, - { - `<a onclick='alert("Hello\, World\`, - `unfinished escape sequence in JS string: "Hello\\, World\\"`, - }, - { - `<a onclick='alert(/x+\`, - `unfinished escape sequence in JS string: "x+\\"`, - }, - { - `<a onclick="/foo[\]/`, - `unfinished JS regexp charset: "foo[\\]/"`, - }, - { - // It is ambiguous whether 1.5 should be 1\.5 or 1.5. - // Either `var x = 1/- 1.5 /i.test(x)` - // where `i.test(x)` is a method call of reference i, - // or `/-1\.5/i.test(x)` which is a method call on a - // case insensitive regular expression. - `<script>{{if false}}var x = 1{{end}}/-{{"1.5"}}/i.test(x)</script>`, - `'/' could start a division or regexp: "/-"`, - }, - { - `{{template "foo"}}`, - "z:1:11: no such template \"foo\"", - }, - { - `<div{{template "y"}}>` + - // Illegal starting in stateTag but not in stateText. - `{{define "y"}} foo<b{{end}}`, - `"<" in attribute name: " foo<b"`, - }, - { - `<script>reverseList = [{{template "t"}}]</script>` + - // Missing " after recursive call. - `{{define "t"}}{{if .Tail}}{{template "t" .Tail}}{{end}}{{.Head}}",{{end}}`, - `: cannot compute output context for template t$htmltemplate_stateJS_elementScript`, - }, - { - `<input type=button value=onclick=>`, - `html/template:z: "=" in unquoted attr: "onclick="`, - }, - { - `<input type=button value= onclick=>`, - `html/template:z: "=" in unquoted attr: "onclick="`, - }, - { - `<input type=button value= 1+1=2>`, - `html/template:z: "=" in unquoted attr: "1+1=2"`, - }, - { - "<a class=`foo>", - "golang.org/x/website/go.dev/cmd/internal/html/template:z: \"`\" in unquoted attr: \"`foo\"", - }, - { - `<a style=font:'Arial'>`, - `html/template:z: "'" in unquoted attr: "font:'Arial'"`, - }, - { - `<a=foo>`, - `: expected space, attr name, or end of tag, but got "=foo>"`, - }, - { - `Hello, {{. | urlquery | print}}!`, - // urlquery is disallowed if it is not the last command in the pipeline. - `predefined escaper "urlquery" disallowed in template`, - }, - { - `Hello, {{. | html | print}}!`, - // html is disallowed if it is not the last command in the pipeline. - `predefined escaper "html" disallowed in template`, - }, - { - `Hello, {{html . | print}}!`, - // A direct call to html is disallowed if it is not the last command in the pipeline. - `predefined escaper "html" disallowed in template`, - }, - { - `<div class={{. | html}}>Hello<div>`, - // html is disallowed in a pipeline that is in an unquoted attribute context, - // even if it is the last command in the pipeline. - `predefined escaper "html" disallowed in template`, - }, - { - `Hello, {{. | urlquery | html}}!`, - // html is allowed since it is the last command in the pipeline, but urlquery is not. - `predefined escaper "urlquery" disallowed in template`, - }, - } - for _, test := range tests { - buf := new(bytes.Buffer) - tmpl, err := New("z").Parse(test.input) - if err != nil { - t.Errorf("input=%q: unexpected parse error %s\n", test.input, err) - continue - } - err = tmpl.Execute(buf, nil) - var got string - if err != nil { - got = err.Error() - } - if test.err == "" { - if got != "" { - t.Errorf("input=%q: unexpected error %q", test.input, got) - } - continue - } - if !strings.Contains(got, test.err) { - t.Errorf("input=%q: error\n\t%q\ndoes not contain expected string\n\t%q", test.input, got, test.err) - continue - } - // Check that we get the same error if we call Execute again. - if err := tmpl.Execute(buf, nil); err == nil || err.Error() != got { - t.Errorf("input=%q: unexpected error on second call %q", test.input, err) - - } - } -} - -func TestEscapeText(t *testing.T) { - tests := []struct { - input string - output context - }{ - { - ``, - context{}, - }, - { - `Hello, World!`, - context{}, - }, - { - // An orphaned "<" is OK. - `I <3 Ponies!`, - context{}, - }, - { - `<a`, - context{state: stateTag}, - }, - { - `<a `, - context{state: stateTag}, - }, - { - `<a>`, - context{state: stateText}, - }, - { - `<a href`, - context{state: stateAttrName, attr: attrURL}, - }, - { - `<a on`, - context{state: stateAttrName, attr: attrScript}, - }, - { - `<a href `, - context{state: stateAfterName, attr: attrURL}, - }, - { - `<a style = `, - context{state: stateBeforeValue, attr: attrStyle}, - }, - { - `<a href=`, - context{state: stateBeforeValue, attr: attrURL}, - }, - { - `<a href=x`, - context{state: stateURL, delim: delimSpaceOrTagEnd, urlPart: urlPartPreQuery, attr: attrURL}, - }, - { - `<a href=x `, - context{state: stateTag}, - }, - { - `<a href=>`, - context{state: stateText}, - }, - { - `<a href=x>`, - context{state: stateText}, - }, - { - `<a href ='`, - context{state: stateURL, delim: delimSingleQuote, attr: attrURL}, - }, - { - `<a href=''`, - context{state: stateTag}, - }, - { - `<a href= "`, - context{state: stateURL, delim: delimDoubleQuote, attr: attrURL}, - }, - { - `<a href=""`, - context{state: stateTag}, - }, - { - `<a title="`, - context{state: stateAttr, delim: delimDoubleQuote}, - }, - { - `<a HREF='http:`, - context{state: stateURL, delim: delimSingleQuote, urlPart: urlPartPreQuery, attr: attrURL}, - }, - { - `<a Href='/`, - context{state: stateURL, delim: delimSingleQuote, urlPart: urlPartPreQuery, attr: attrURL}, - }, - { - `<a href='"`, - context{state: stateURL, delim: delimSingleQuote, urlPart: urlPartPreQuery, attr: attrURL}, - }, - { - `<a href="'`, - context{state: stateURL, delim: delimDoubleQuote, urlPart: urlPartPreQuery, attr: attrURL}, - }, - { - `<a href=''`, - context{state: stateURL, delim: delimSingleQuote, urlPart: urlPartPreQuery, attr: attrURL}, - }, - { - `<a href=""`, - context{state: stateURL, delim: delimDoubleQuote, urlPart: urlPartPreQuery, attr: attrURL}, - }, - { - `<a href=""`, - context{state: stateURL, delim: delimDoubleQuote, urlPart: urlPartPreQuery, attr: attrURL}, - }, - { - `<a href="`, - context{state: stateURL, delim: delimSpaceOrTagEnd, urlPart: urlPartPreQuery, attr: attrURL}, - }, - { - `<img alt="1">`, - context{state: stateText}, - }, - { - `<img alt="1>"`, - context{state: stateTag}, - }, - { - `<img alt="1>">`, - context{state: stateText}, - }, - { - `<input checked type="checkbox"`, - context{state: stateTag}, - }, - { - `<a onclick="`, - context{state: stateJS, delim: delimDoubleQuote, attr: attrScript}, - }, - { - `<a onclick="//foo`, - context{state: stateJSLineCmt, delim: delimDoubleQuote, attr: attrScript}, - }, - { - "<a onclick='//\n", - context{state: stateJS, delim: delimSingleQuote, attr: attrScript}, - }, - { - "<a onclick='//\r\n", - context{state: stateJS, delim: delimSingleQuote, attr: attrScript}, - }, - { - "<a onclick='//\u2028", - context{state: stateJS, delim: delimSingleQuote, attr: attrScript}, - }, - { - `<a onclick="/*`, - context{state: stateJSBlockCmt, delim: delimDoubleQuote, attr: attrScript}, - }, - { - `<a onclick="/*/`, - context{state: stateJSBlockCmt, delim: delimDoubleQuote, attr: attrScript}, - }, - { - `<a onclick="/**/`, - context{state: stateJS, delim: delimDoubleQuote, attr: attrScript}, - }, - { - `<a onkeypress=""`, - context{state: stateJSDqStr, delim: delimDoubleQuote, attr: attrScript}, - }, - { - `<a onclick='"foo"`, - context{state: stateJS, delim: delimSingleQuote, jsCtx: jsCtxDivOp, attr: attrScript}, - }, - { - `<a onclick='foo'`, - context{state: stateJS, delim: delimSpaceOrTagEnd, jsCtx: jsCtxDivOp, attr: attrScript}, - }, - { - `<a onclick='foo`, - context{state: stateJSSqStr, delim: delimSpaceOrTagEnd, attr: attrScript}, - }, - { - `<a onclick=""foo'`, - context{state: stateJSDqStr, delim: delimDoubleQuote, attr: attrScript}, - }, - { - `<a onclick="'foo"`, - context{state: stateJSSqStr, delim: delimDoubleQuote, attr: attrScript}, - }, - { - `<A ONCLICK="'`, - context{state: stateJSSqStr, delim: delimDoubleQuote, attr: attrScript}, - }, - { - `<a onclick="/`, - context{state: stateJSRegexp, delim: delimDoubleQuote, attr: attrScript}, - }, - { - `<a onclick="'foo'`, - context{state: stateJS, delim: delimDoubleQuote, jsCtx: jsCtxDivOp, attr: attrScript}, - }, - { - `<a onclick="'foo\'`, - context{state: stateJSSqStr, delim: delimDoubleQuote, attr: attrScript}, - }, - { - `<a onclick="'foo\'`, - context{state: stateJSSqStr, delim: delimDoubleQuote, attr: attrScript}, - }, - { - `<a onclick="/foo/`, - context{state: stateJS, delim: delimDoubleQuote, jsCtx: jsCtxDivOp, attr: attrScript}, - }, - { - `<script>/foo/ /=`, - context{state: stateJS, element: elementScript}, - }, - { - `<a onclick="1 /foo`, - context{state: stateJS, delim: delimDoubleQuote, jsCtx: jsCtxDivOp, attr: attrScript}, - }, - { - `<a onclick="1 /*c*/ /foo`, - context{state: stateJS, delim: delimDoubleQuote, jsCtx: jsCtxDivOp, attr: attrScript}, - }, - { - `<a onclick="/foo[/]`, - context{state: stateJSRegexp, delim: delimDoubleQuote, attr: attrScript}, - }, - { - `<a onclick="/foo\/`, - context{state: stateJSRegexp, delim: delimDoubleQuote, attr: attrScript}, - }, - { - `<a onclick="/foo/`, - context{state: stateJS, delim: delimDoubleQuote, jsCtx: jsCtxDivOp, attr: attrScript}, - }, - { - `<input checked style="`, - context{state: stateCSS, delim: delimDoubleQuote, attr: attrStyle}, - }, - { - `<a style="//`, - context{state: stateCSSLineCmt, delim: delimDoubleQuote, attr: attrStyle}, - }, - { - `<a style="//</script>`, - context{state: stateCSSLineCmt, delim: delimDoubleQuote, attr: attrStyle}, - }, - { - "<a style='//\n", - context{state: stateCSS, delim: delimSingleQuote, attr: attrStyle}, - }, - { - "<a style='//\r", - context{state: stateCSS, delim: delimSingleQuote, attr: attrStyle}, - }, - { - `<a style="/*`, - context{state: stateCSSBlockCmt, delim: delimDoubleQuote, attr: attrStyle}, - }, - { - `<a style="/*/`, - context{state: stateCSSBlockCmt, delim: delimDoubleQuote, attr: attrStyle}, - }, - { - `<a style="/**/`, - context{state: stateCSS, delim: delimDoubleQuote, attr: attrStyle}, - }, - { - `<a style="background: '`, - context{state: stateCSSSqStr, delim: delimDoubleQuote, attr: attrStyle}, - }, - { - `<a style="background: "`, - context{state: stateCSSDqStr, delim: delimDoubleQuote, attr: attrStyle}, - }, - { - `<a style="background: '/foo?img=`, - context{state: stateCSSSqStr, delim: delimDoubleQuote, urlPart: urlPartQueryOrFrag, attr: attrStyle}, - }, - { - `<a style="background: '/`, - context{state: stateCSSSqStr, delim: delimDoubleQuote, urlPart: urlPartPreQuery, attr: attrStyle}, - }, - { - `<a style="background: url("/`, - context{state: stateCSSDqURL, delim: delimDoubleQuote, urlPart: urlPartPreQuery, attr: attrStyle}, - }, - { - `<a style="background: url('/`, - context{state: stateCSSSqURL, delim: delimDoubleQuote, urlPart: urlPartPreQuery, attr: attrStyle}, - }, - { - `<a style="background: url('/)`, - context{state: stateCSSSqURL, delim: delimDoubleQuote, urlPart: urlPartPreQuery, attr: attrStyle}, - }, - { - `<a style="background: url('/ `, - context{state: stateCSSSqURL, delim: delimDoubleQuote, urlPart: urlPartPreQuery, attr: attrStyle}, - }, - { - `<a style="background: url(/`, - context{state: stateCSSURL, delim: delimDoubleQuote, urlPart: urlPartPreQuery, attr: attrStyle}, - }, - { - `<a style="background: url( `, - context{state: stateCSSURL, delim: delimDoubleQuote, attr: attrStyle}, - }, - { - `<a style="background: url( /image?name=`, - context{state: stateCSSURL, delim: delimDoubleQuote, urlPart: urlPartQueryOrFrag, attr: attrStyle}, - }, - { - `<a style="background: url(x)`, - context{state: stateCSS, delim: delimDoubleQuote, attr: attrStyle}, - }, - { - `<a style="background: url('x'`, - context{state: stateCSS, delim: delimDoubleQuote, attr: attrStyle}, - }, - { - `<a style="background: url( x `, - context{state: stateCSS, delim: delimDoubleQuote, attr: attrStyle}, - }, - { - `<!-- foo`, - context{state: stateHTMLCmt}, - }, - { - `<!-->`, - context{state: stateHTMLCmt}, - }, - { - `<!--->`, - context{state: stateHTMLCmt}, - }, - { - `<!-- foo -->`, - context{state: stateText}, - }, - { - `<script`, - context{state: stateTag, element: elementScript}, - }, - { - `<script `, - context{state: stateTag, element: elementScript}, - }, - { - `<script src="foo.js" `, - context{state: stateTag, element: elementScript}, - }, - { - `<script src='foo.js' `, - context{state: stateTag, element: elementScript}, - }, - { - `<script type=text/javascript `, - context{state: stateTag, element: elementScript}, - }, - { - `<script>`, - context{state: stateJS, jsCtx: jsCtxRegexp, element: elementScript}, - }, - { - `<script>foo`, - context{state: stateJS, jsCtx: jsCtxDivOp, element: elementScript}, - }, - { - `<script>foo</script>`, - context{state: stateText}, - }, - { - `<script>foo</script><!--`, - context{state: stateHTMLCmt}, - }, - { - `<script>document.write("<p>foo</p>");`, - context{state: stateJS, element: elementScript}, - }, - { - `<script>document.write("<p>foo<\/script>");`, - context{state: stateJS, element: elementScript}, - }, - { - `<script>document.write("<script>alert(1)</script>");`, - context{state: stateText}, - }, - { - `<script type="golang.org/x/website/go.dev/cmd/internal/text/template">`, - context{state: stateText}, - }, - // covering issue 19968 - { - `<script type="TEXT/JAVASCRIPT">`, - context{state: stateJS, element: elementScript}, - }, - // covering issue 19965 - { - `<script TYPE="golang.org/x/website/go.dev/cmd/internal/text/template">`, - context{state: stateText}, - }, - { - `<script type="notjs">`, - context{state: stateText}, - }, - { - `<Script>`, - context{state: stateJS, element: elementScript}, - }, - { - `<SCRIPT>foo`, - context{state: stateJS, jsCtx: jsCtxDivOp, element: elementScript}, - }, - { - `<textarea>value`, - context{state: stateRCDATA, element: elementTextarea}, - }, - { - `<textarea>value</TEXTAREA>`, - context{state: stateText}, - }, - { - `<textarea name=html><b`, - context{state: stateRCDATA, element: elementTextarea}, - }, - { - `<title>value`, - context{state: stateRCDATA, element: elementTitle}, - }, - { - `<style>value`, - context{state: stateCSS, element: elementStyle}, - }, - { - `<a xlink:href`, - context{state: stateAttrName, attr: attrURL}, - }, - { - `<a xmlns`, - context{state: stateAttrName, attr: attrURL}, - }, - { - `<a xmlns:foo`, - context{state: stateAttrName, attr: attrURL}, - }, - { - `<a xmlnsxyz`, - context{state: stateAttrName}, - }, - { - `<a data-url`, - context{state: stateAttrName, attr: attrURL}, - }, - { - `<a data-iconUri`, - context{state: stateAttrName, attr: attrURL}, - }, - { - `<a data-urlItem`, - context{state: stateAttrName, attr: attrURL}, - }, - { - `<a g:`, - context{state: stateAttrName}, - }, - { - `<a g:url`, - context{state: stateAttrName, attr: attrURL}, - }, - { - `<a g:iconUri`, - context{state: stateAttrName, attr: attrURL}, - }, - { - `<a g:urlItem`, - context{state: stateAttrName, attr: attrURL}, - }, - { - `<a g:value`, - context{state: stateAttrName}, - }, - { - `<a svg:style='`, - context{state: stateCSS, delim: delimSingleQuote, attr: attrStyle}, - }, - { - `<svg:font-face`, - context{state: stateTag}, - }, - { - `<svg:a svg:onclick="`, - context{state: stateJS, delim: delimDoubleQuote, attr: attrScript}, - }, - { - `<svg:a svg:onclick="x()">`, - context{}, - }, - } - - for _, test := range tests { - b, e := []byte(test.input), makeEscaper(nil) - c := e.escapeText(context{}, &parse.TextNode{NodeType: parse.NodeText, Text: b}) - if !test.output.eq(c) { - t.Errorf("input %q: want context\n\t%v\ngot\n\t%v", test.input, test.output, c) - continue - } - if test.input != string(b) { - t.Errorf("input %q: text node was modified: want %q got %q", test.input, test.input, b) - continue - } - } -} - -func TestEnsurePipelineContains(t *testing.T) { - tests := []struct { - input, output string - ids []string - }{ - { - "{{.X}}", - ".X", - []string{}, - }, - { - "{{.X | html}}", - ".X | html", - []string{}, - }, - { - "{{.X}}", - ".X | html", - []string{"html"}, - }, - { - "{{html .X}}", - "_eval_args_ .X | html | urlquery", - []string{"html", "urlquery"}, - }, - { - "{{html .X .Y .Z}}", - "_eval_args_ .X .Y .Z | html | urlquery", - []string{"html", "urlquery"}, - }, - { - "{{.X | print}}", - ".X | print | urlquery", - []string{"urlquery"}, - }, - { - "{{.X | print | urlquery}}", - ".X | print | urlquery", - []string{"urlquery"}, - }, - { - "{{.X | urlquery}}", - ".X | html | urlquery", - []string{"html", "urlquery"}, - }, - { - "{{.X | print 2 | .f 3}}", - ".X | print 2 | .f 3 | urlquery | html", - []string{"urlquery", "html"}, - }, - { - // covering issue 10801 - "{{.X | println.x }}", - ".X | println.x | urlquery | html", - []string{"urlquery", "html"}, - }, - { - // covering issue 10801 - "{{.X | (print 12 | println).x }}", - ".X | (print 12 | println).x | urlquery | html", - []string{"urlquery", "html"}, - }, - // The following test cases ensure that the merging of internal escapers - // with the predefined "html" and "urlquery" escapers is correct. - { - "{{.X | urlquery}}", - ".X | _html_template_urlfilter | urlquery", - []string{"_html_template_urlfilter", "_html_template_urlnormalizer"}, - }, - { - "{{.X | urlquery}}", - ".X | urlquery | _html_template_urlfilter | _html_template_cssescaper", - []string{"_html_template_urlfilter", "_html_template_cssescaper"}, - }, - { - "{{.X | urlquery}}", - ".X | urlquery", - []string{"_html_template_urlnormalizer"}, - }, - { - "{{.X | urlquery}}", - ".X | urlquery", - []string{"_html_template_urlescaper"}, - }, - { - "{{.X | html}}", - ".X | html", - []string{"_html_template_htmlescaper"}, - }, - { - "{{.X | html}}", - ".X | html", - []string{"_html_template_rcdataescaper"}, - }, - } - for i, test := range tests { - tmpl := template.Must(template.New("test").Parse(test.input)) - action, ok := (tmpl.Tree.Root.Nodes[0].(*parse.ActionNode)) - if !ok { - t.Errorf("First node is not an action: %s", test.input) - continue - } - pipe := action.Pipe - originalIDs := make([]string, len(test.ids)) - copy(originalIDs, test.ids) - ensurePipelineContains(pipe, test.ids) - got := pipe.String() - if got != test.output { - t.Errorf("#%d: %s, %v: want\n\t%s\ngot\n\t%s", i, test.input, originalIDs, test.output, got) - } - } -} - -func TestEscapeMalformedPipelines(t *testing.T) { - tests := []string{ - "{{ 0 | $ }}", - "{{ 0 | $ | urlquery }}", - "{{ 0 | (nil) }}", - "{{ 0 | (nil) | html }}", - } - for _, test := range tests { - var b bytes.Buffer - tmpl, err := New("test").Parse(test) - if err != nil { - t.Errorf("failed to parse set: %q", err) - } - err = tmpl.Execute(&b, nil) - if err == nil { - t.Errorf("Expected error for %q", test) - } - } -} - -func TestEscapeErrorsNotIgnorable(t *testing.T) { - var b bytes.Buffer - tmpl, _ := New("dangerous").Parse("<a") - err := tmpl.Execute(&b, nil) - if err == nil { - t.Errorf("Expected error") - } else if b.Len() != 0 { - t.Errorf("Emitted output despite escaping failure") - } -} - -func TestEscapeSetErrorsNotIgnorable(t *testing.T) { - var b bytes.Buffer - tmpl, err := New("root").Parse(`{{define "t"}}<a{{end}}`) - if err != nil { - t.Errorf("failed to parse set: %q", err) - } - err = tmpl.ExecuteTemplate(&b, "t", nil) - if err == nil { - t.Errorf("Expected error") - } else if b.Len() != 0 { - t.Errorf("Emitted output despite escaping failure") - } -} - -func TestRedundantFuncs(t *testing.T) { - inputs := []interface{}{ - "\x00\x01\x02\x03\x04\x05\x06\x07\x08\t\n\x0b\x0c\r\x0e\x0f" + - "\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f" + - ` !"#$%&'()*+,-./` + - `0123456789:;<=>?` + - `@ABCDEFGHIJKLMNO` + - `PQRSTUVWXYZ[\]^_` + - "`abcdefghijklmno" + - "pqrstuvwxyz{|}~\x7f" + - "\u00A0\u0100\u2028\u2029\ufeff\ufdec\ufffd\uffff\U0001D11E" + - "&%22\\", - CSS(`a[href =~ "//example.com"]#foo`), - HTML(`Hello, <b>World</b> &tc!`), - HTMLAttr(` dir="ltr"`), - JS(`c && alert("Hello, World!");`), - JSStr(`Hello, World & O'Reilly\x21`), - URL(`greeting=H%69&addressee=(World)`), - } - - for n0, m := range redundantFuncs { - f0 := funcMap[n0].(func(...interface{}) string) - for n1 := range m { - f1 := funcMap[n1].(func(...interface{}) string) - for _, input := range inputs { - want := f0(input) - if got := f1(want); want != got { - t.Errorf("%s %s with %T %q: want\n\t%q,\ngot\n\t%q", n0, n1, input, input, want, got) - } - } - } - } -} - -func TestIndirectPrint(t *testing.T) { - a := 3 - ap := &a - b := "hello" - bp := &b - bpp := &bp - tmpl := Must(New("t").Parse(`{{.}}`)) - var buf bytes.Buffer - err := tmpl.Execute(&buf, ap) - if err != nil { - t.Errorf("Unexpected error: %s", err) - } else if buf.String() != "3" { - t.Errorf(`Expected "3"; got %q`, buf.String()) - } - buf.Reset() - err = tmpl.Execute(&buf, bpp) - if err != nil { - t.Errorf("Unexpected error: %s", err) - } else if buf.String() != "hello" { - t.Errorf(`Expected "hello"; got %q`, buf.String()) - } -} - -// This is a test for issue 3272. -func TestEmptyTemplateHTML(t *testing.T) { - page := Must(New("page").ParseFiles(os.DevNull)) - if err := page.ExecuteTemplate(os.Stdout, "page", "nothing"); err == nil { - t.Fatal("expected error") - } -} - -type Issue7379 int - -func (Issue7379) SomeMethod(x int) string { - return fmt.Sprintf("<%d>", x) -} - -// This is a test for issue 7379: type assertion error caused panic, and then -// the code to handle the panic breaks escaping. It's hard to see the second -// problem once the first is fixed, but its fix is trivial so we let that go. See -// the discussion for issue 7379. -func TestPipeToMethodIsEscaped(t *testing.T) { - tmpl := Must(New("x").Parse("<html>{{0 | .SomeMethod}}</html>\n")) - tryExec := func() string { - defer func() { - panicValue := recover() - if panicValue != nil { - t.Errorf("panicked: %v\n", panicValue) - } - }() - var b bytes.Buffer - tmpl.Execute(&b, Issue7379(0)) - return b.String() - } - for i := 0; i < 3; i++ { - str := tryExec() - const expect = "<html><0></html>\n" - if str != expect { - t.Errorf("expected %q got %q", expect, str) - } - } -} - -// Unlike text/template, html/template crashed if given an incomplete -// template, that is, a template that had been named but not given any content. -// This is issue #10204. -func TestErrorOnUndefined(t *testing.T) { - tmpl := New("undefined") - - err := tmpl.Execute(nil, nil) - if err == nil { - t.Error("expected error") - } else if !strings.Contains(err.Error(), "incomplete") { - t.Errorf("expected error about incomplete template; got %s", err) - } -} - -// This covers issue #20842. -func TestIdempotentExecute(t *testing.T) { - tmpl := Must(New(""). - Parse(`{{define "main"}}<body>{{template "hello"}}</body>{{end}}`)) - Must(tmpl. - Parse(`{{define "hello"}}Hello, {{"Ladies & Gentlemen!"}}{{end}}`)) - got := new(bytes.Buffer) - var err error - // Ensure that "hello" produces the same output when executed twice. - want := "Hello, Ladies & Gentlemen!" - for i := 0; i < 2; i++ { - err = tmpl.ExecuteTemplate(got, "hello", nil) - if err != nil { - t.Errorf("unexpected error: %s", err) - } - if got.String() != want { - t.Errorf("after executing template \"hello\", got:\n\t%q\nwant:\n\t%q\n", got.String(), want) - } - got.Reset() - } - // Ensure that the implicit re-execution of "hello" during the execution of - // "main" does not cause the output of "hello" to change. - err = tmpl.ExecuteTemplate(got, "main", nil) - if err != nil { - t.Errorf("unexpected error: %s", err) - } - // If the HTML escaper is added again to the action {{"Ladies & Gentlemen!"}}, - // we would expected to see the ampersand overescaped to "&amp;". - want = "<body>Hello, Ladies & Gentlemen!</body>" - if got.String() != want { - t.Errorf("after executing template \"main\", got:\n\t%q\nwant:\n\t%q\n", got.String(), want) - } -} - -func BenchmarkEscapedExecute(b *testing.B) { - tmpl := Must(New("t").Parse(`<a onclick="alert('{{.}}')">{{.}}</a>`)) - var buf bytes.Buffer - b.ResetTimer() - for i := 0; i < b.N; i++ { - tmpl.Execute(&buf, "foo & 'bar' & baz") - buf.Reset() - } -} - -// Covers issue 22780. -func TestOrphanedTemplate(t *testing.T) { - t1 := Must(New("foo").Parse(`<a href="{{.}}">link1</a>`)) - t2 := Must(t1.New("foo").Parse(`bar`)) - - var b bytes.Buffer - const wantError = `template: "foo" is an incomplete or empty template` - if err := t1.Execute(&b, "javascript:alert(1)"); err == nil { - t.Fatal("expected error executing t1") - } else if gotError := err.Error(); gotError != wantError { - t.Fatalf("got t1 execution error:\n\t%s\nwant:\n\t%s", gotError, wantError) - } - b.Reset() - if err := t2.Execute(&b, nil); err != nil { - t.Fatalf("error executing t2: %s", err) - } - const want = "bar" - if got := b.String(); got != want { - t.Fatalf("t2 rendered %q, want %q", got, want) - } -} - -// Covers issue 21844. -func TestAliasedParseTreeDoesNotOverescape(t *testing.T) { - const ( - tmplText = `{{.}}` - data = `<baz>` - want = `<baz>` - ) - // Templates "foo" and "bar" both alias the same underlying parse tree. - tpl := Must(New("foo").Parse(tmplText)) - if _, err := tpl.AddParseTree("bar", tpl.Tree); err != nil { - t.Fatalf("AddParseTree error: %v", err) - } - var b1, b2 bytes.Buffer - if err := tpl.ExecuteTemplate(&b1, "foo", data); err != nil { - t.Fatalf(`ExecuteTemplate failed for "foo": %v`, err) - } - if err := tpl.ExecuteTemplate(&b2, "bar", data); err != nil { - t.Fatalf(`ExecuteTemplate failed for "foo": %v`, err) - } - got1, got2 := b1.String(), b2.String() - if got1 != want { - t.Fatalf(`Template "foo" rendered %q, want %q`, got1, want) - } - if got1 != got2 { - t.Fatalf(`Template "foo" and "bar" rendered %q and %q respectively, expected equal values`, got1, got2) - } -}
diff --git a/go.dev/cmd/internal/html/template/example_test.go b/go.dev/cmd/internal/html/template/example_test.go deleted file mode 100644 index 3ec1b60..0000000 --- a/go.dev/cmd/internal/html/template/example_test.go +++ /dev/null
@@ -1,182 +0,0 @@ -// Copyright 2015 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package template_test - -import ( - "fmt" - "log" - "os" - "strings" - - "golang.org/x/website/go.dev/cmd/internal/html/template" -) - -func Example() { - const tpl = ` -<!DOCTYPE html> -<html> - <head> - <meta charset="UTF-8"> - <title>{{.Title}}</title> - </head> - <body> - {{range .Items}}<div>{{ . }}</div>{{else}}<div><strong>no rows</strong></div>{{end}} - </body> -</html>` - - check := func(err error) { - if err != nil { - log.Fatal(err) - } - } - t, err := template.New("webpage").Parse(tpl) - check(err) - - data := struct { - Title string - Items []string - }{ - Title: "My page", - Items: []string{ - "My photos", - "My blog", - }, - } - - err = t.Execute(os.Stdout, data) - check(err) - - noItems := struct { - Title string - Items []string - }{ - Title: "My another page", - Items: []string{}, - } - - err = t.Execute(os.Stdout, noItems) - check(err) - - // Output: - // <!DOCTYPE html> - // <html> - // <head> - // <meta charset="UTF-8"> - // <title>My page</title> - // </head> - // <body> - // <div>My photos</div><div>My blog</div> - // </body> - // </html> - // <!DOCTYPE html> - // <html> - // <head> - // <meta charset="UTF-8"> - // <title>My another page</title> - // </head> - // <body> - // <div><strong>no rows</strong></div> - // </body> - // </html> - -} - -func Example_autoescaping() { - check := func(err error) { - if err != nil { - log.Fatal(err) - } - } - t, err := template.New("foo").Parse(`{{define "T"}}Hello, {{.}}!{{end}}`) - check(err) - err = t.ExecuteTemplate(os.Stdout, "T", "<script>alert('you have been pwned')</script>") - check(err) - // Output: - // Hello, <script>alert('you have been pwned')</script>! -} - -func Example_escape() { - const s = `"Fran & Freddie's Diner" <tasty@example.com>` - v := []interface{}{`"Fran & Freddie's Diner"`, ' ', `<tasty@example.com>`} - - fmt.Println(template.HTMLEscapeString(s)) - template.HTMLEscape(os.Stdout, []byte(s)) - fmt.Fprintln(os.Stdout, "") - fmt.Println(template.HTMLEscaper(v...)) - - fmt.Println(template.JSEscapeString(s)) - template.JSEscape(os.Stdout, []byte(s)) - fmt.Fprintln(os.Stdout, "") - fmt.Println(template.JSEscaper(v...)) - - fmt.Println(template.URLQueryEscaper(v...)) - - // Output: - // "Fran & Freddie's Diner" <tasty@example.com> - // "Fran & Freddie's Diner" <tasty@example.com> - // "Fran & Freddie's Diner"32<tasty@example.com> - // \"Fran \u0026 Freddie\'s Diner\" \u003Ctasty@example.com\u003E - // \"Fran \u0026 Freddie\'s Diner\" \u003Ctasty@example.com\u003E - // \"Fran \u0026 Freddie\'s Diner\"32\u003Ctasty@example.com\u003E - // %22Fran+%26+Freddie%27s+Diner%2232%3Ctasty%40example.com%3E - -} - -func ExampleTemplate_Delims() { - const text = "<<.Greeting>> {{.Name}}" - - data := struct { - Greeting string - Name string - }{ - Greeting: "Hello", - Name: "Joe", - } - - t := template.Must(template.New("tpl").Delims("<<", ">>").Parse(text)) - - err := t.Execute(os.Stdout, data) - if err != nil { - log.Fatal(err) - } - - // Output: - // Hello {{.Name}} -} - -// The following example is duplicated in text/template; keep them in sync. - -func ExampleTemplate_block() { - const ( - master = `Names:{{block "list" .}}{{"\n"}}{{range .}}{{println "-" .}}{{end}}{{end}}` - overlay = `{{define "list"}} {{join . ", "}}{{end}} ` - ) - var ( - funcs = template.FuncMap{"join": strings.Join} - guardians = []string{"Gamora", "Groot", "Nebula", "Rocket", "Star-Lord"} - ) - masterTmpl, err := template.New("master").Funcs(funcs).Parse(master) - if err != nil { - log.Fatal(err) - } - overlayTmpl, err := template.Must(masterTmpl.Clone()).Parse(overlay) - if err != nil { - log.Fatal(err) - } - if err := masterTmpl.Execute(os.Stdout, guardians); err != nil { - log.Fatal(err) - } - if err := overlayTmpl.Execute(os.Stdout, guardians); err != nil { - log.Fatal(err) - } - // Output: - // Names: - // - Gamora - // - Groot - // - Nebula - // - Rocket - // - Star-Lord - // Names: Gamora, Groot, Nebula, Rocket, Star-Lord -}
diff --git a/go.dev/cmd/internal/html/template/examplefiles_test.go b/go.dev/cmd/internal/html/template/examplefiles_test.go deleted file mode 100644 index 1ff585d..0000000 --- a/go.dev/cmd/internal/html/template/examplefiles_test.go +++ /dev/null
@@ -1,227 +0,0 @@ -// Copyright 2016 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package template_test - -import ( - "io" - "io/ioutil" - "log" - "os" - "path/filepath" - - "golang.org/x/website/go.dev/cmd/internal/text/template" -) - -// templateFile defines the contents of a template to be stored in a file, for testing. -type templateFile struct { - name string - contents string -} - -func createTestDir(files []templateFile) string { - dir, err := ioutil.TempDir("", "template") - if err != nil { - log.Fatal(err) - } - for _, file := range files { - f, err := os.Create(filepath.Join(dir, file.name)) - if err != nil { - log.Fatal(err) - } - defer f.Close() - _, err = io.WriteString(f, file.contents) - if err != nil { - log.Fatal(err) - } - } - return dir -} - -// The following example is duplicated in text/template; keep them in sync. - -// Here we demonstrate loading a set of templates from a directory. -func ExampleTemplate_glob() { - // Here we create a temporary directory and populate it with our sample - // template definition files; usually the template files would already - // exist in some location known to the program. - dir := createTestDir([]templateFile{ - // T0.tmpl is a plain template file that just invokes T1. - {"T0.tmpl", `T0 invokes T1: ({{template "T1"}})`}, - // T1.tmpl defines a template, T1 that invokes T2. - {"T1.tmpl", `{{define "T1"}}T1 invokes T2: ({{template "T2"}}){{end}}`}, - // T2.tmpl defines a template T2. - {"T2.tmpl", `{{define "T2"}}This is T2{{end}}`}, - }) - // Clean up after the test; another quirk of running as an example. - defer os.RemoveAll(dir) - - // pattern is the glob pattern used to find all the template files. - pattern := filepath.Join(dir, "*.tmpl") - - // Here starts the example proper. - // T0.tmpl is the first name matched, so it becomes the starting template, - // the value returned by ParseGlob. - tmpl := template.Must(template.ParseGlob(pattern)) - - err := tmpl.Execute(os.Stdout, nil) - if err != nil { - log.Fatalf("template execution: %s", err) - } - // Output: - // T0 invokes T1: (T1 invokes T2: (This is T2)) -} - -// Here we demonstrate loading a set of templates from files in different directories -func ExampleTemplate_parsefiles() { - // Here we create different temporary directories and populate them with our sample - // template definition files; usually the template files would already - // exist in some location known to the program. - dir1 := createTestDir([]templateFile{ - // T1.tmpl is a plain template file that just invokes T2. - {"T1.tmpl", `T1 invokes T2: ({{template "T2"}})`}, - }) - - dir2 := createTestDir([]templateFile{ - // T2.tmpl defines a template T2. - {"T2.tmpl", `{{define "T2"}}This is T2{{end}}`}, - }) - - // Clean up after the test; another quirk of running as an example. - defer func(dirs ...string) { - for _, dir := range dirs { - os.RemoveAll(dir) - } - }(dir1, dir2) - - // Here starts the example proper. - // Let's just parse only dir1/T0 and dir2/T2 - paths := []string{ - filepath.Join(dir1, "T1.tmpl"), - filepath.Join(dir2, "T2.tmpl"), - } - tmpl := template.Must(template.ParseFiles(paths...)) - - err := tmpl.Execute(os.Stdout, nil) - if err != nil { - log.Fatalf("template execution: %s", err) - } - // Output: - // T1 invokes T2: (This is T2) -} - -// The following example is duplicated in text/template; keep them in sync. - -// This example demonstrates one way to share some templates -// and use them in different contexts. In this variant we add multiple driver -// templates by hand to an existing bundle of templates. -func ExampleTemplate_helpers() { - // Here we create a temporary directory and populate it with our sample - // template definition files; usually the template files would already - // exist in some location known to the program. - dir := createTestDir([]templateFile{ - // T1.tmpl defines a template, T1 that invokes T2. - {"T1.tmpl", `{{define "T1"}}T1 invokes T2: ({{template "T2"}}){{end}}`}, - // T2.tmpl defines a template T2. - {"T2.tmpl", `{{define "T2"}}This is T2{{end}}`}, - }) - // Clean up after the test; another quirk of running as an example. - defer os.RemoveAll(dir) - - // pattern is the glob pattern used to find all the template files. - pattern := filepath.Join(dir, "*.tmpl") - - // Here starts the example proper. - // Load the helpers. - templates := template.Must(template.ParseGlob(pattern)) - // Add one driver template to the bunch; we do this with an explicit template definition. - _, err := templates.Parse("{{define `driver1`}}Driver 1 calls T1: ({{template `T1`}})\n{{end}}") - if err != nil { - log.Fatal("parsing driver1: ", err) - } - // Add another driver template. - _, err = templates.Parse("{{define `driver2`}}Driver 2 calls T2: ({{template `T2`}})\n{{end}}") - if err != nil { - log.Fatal("parsing driver2: ", err) - } - // We load all the templates before execution. This package does not require - // that behavior but html/template's escaping does, so it's a good habit. - err = templates.ExecuteTemplate(os.Stdout, "driver1", nil) - if err != nil { - log.Fatalf("driver1 execution: %s", err) - } - err = templates.ExecuteTemplate(os.Stdout, "driver2", nil) - if err != nil { - log.Fatalf("driver2 execution: %s", err) - } - // Output: - // Driver 1 calls T1: (T1 invokes T2: (This is T2)) - // Driver 2 calls T2: (This is T2) -} - -// The following example is duplicated in text/template; keep them in sync. - -// This example demonstrates how to use one group of driver -// templates with distinct sets of helper templates. -func ExampleTemplate_share() { - // Here we create a temporary directory and populate it with our sample - // template definition files; usually the template files would already - // exist in some location known to the program. - dir := createTestDir([]templateFile{ - // T0.tmpl is a plain template file that just invokes T1. - {"T0.tmpl", "T0 ({{.}} version) invokes T1: ({{template `T1`}})\n"}, - // T1.tmpl defines a template, T1 that invokes T2. Note T2 is not defined - {"T1.tmpl", `{{define "T1"}}T1 invokes T2: ({{template "T2"}}){{end}}`}, - }) - // Clean up after the test; another quirk of running as an example. - defer os.RemoveAll(dir) - - // pattern is the glob pattern used to find all the template files. - pattern := filepath.Join(dir, "*.tmpl") - - // Here starts the example proper. - // Load the drivers. - drivers := template.Must(template.ParseGlob(pattern)) - - // We must define an implementation of the T2 template. First we clone - // the drivers, then add a definition of T2 to the template name space. - - // 1. Clone the helper set to create a new name space from which to run them. - first, err := drivers.Clone() - if err != nil { - log.Fatal("cloning helpers: ", err) - } - // 2. Define T2, version A, and parse it. - _, err = first.Parse("{{define `T2`}}T2, version A{{end}}") - if err != nil { - log.Fatal("parsing T2: ", err) - } - - // Now repeat the whole thing, using a different version of T2. - // 1. Clone the drivers. - second, err := drivers.Clone() - if err != nil { - log.Fatal("cloning drivers: ", err) - } - // 2. Define T2, version B, and parse it. - _, err = second.Parse("{{define `T2`}}T2, version B{{end}}") - if err != nil { - log.Fatal("parsing T2: ", err) - } - - // Execute the templates in the reverse order to verify the - // first is unaffected by the second. - err = second.ExecuteTemplate(os.Stdout, "T0.tmpl", "second") - if err != nil { - log.Fatalf("second execution: %s", err) - } - err = first.ExecuteTemplate(os.Stdout, "T0.tmpl", "first") - if err != nil { - log.Fatalf("first: execution: %s", err) - } - - // Output: - // T0 (second version) invokes T1: (T1 invokes T2: (T2, version B)) - // T0 (first version) invokes T1: (T1 invokes T2: (T2, version A)) -}
diff --git a/go.dev/cmd/internal/html/template/exec_test.go b/go.dev/cmd/internal/html/template/exec_test.go deleted file mode 100644 index 6bd824c..0000000 --- a/go.dev/cmd/internal/html/template/exec_test.go +++ /dev/null
@@ -1,1834 +0,0 @@ -// Copyright 2011 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -// Tests for template execution, copied from text/template. - -package template - -import ( - "bytes" - "errors" - "flag" - "fmt" - "io/ioutil" - "reflect" - "strings" - "sync" - "testing" - - "golang.org/x/website/go.dev/cmd/internal/text/template" -) - -var debug = flag.Bool("debug", false, "show the errors produced by the tests") - -// T has lots of interesting pieces to use to test execution. -type T struct { - // Basics - True bool - I int - U16 uint16 - X, S string - FloatZero float64 - ComplexZero complex128 - // Nested structs. - U *U - // Struct with String method. - V0 V - V1, V2 *V - // Struct with Error method. - W0 W - W1, W2 *W - // Slices - SI []int - SICap []int - SIEmpty []int - SB []bool - // Arrays - AI [3]int - // Maps - MSI map[string]int - MSIone map[string]int // one element, for deterministic output - MSIEmpty map[string]int - MXI map[interface{}]int - MII map[int]int - MI32S map[int32]string - MI64S map[int64]string - MUI32S map[uint32]string - MUI64S map[uint64]string - MI8S map[int8]string - MUI8S map[uint8]string - SMSI []map[string]int - // Empty interfaces; used to see if we can dig inside one. - Empty0 interface{} // nil - Empty1 interface{} - Empty2 interface{} - Empty3 interface{} - Empty4 interface{} - // Non-empty interfaces. - NonEmptyInterface I - NonEmptyInterfacePtS *I - NonEmptyInterfaceNil I - NonEmptyInterfaceTypedNil I - // Stringer. - Str fmt.Stringer - Err error - // Pointers - PI *int - PS *string - PSI *[]int - NIL *int - // Function (not method) - BinaryFunc func(string, string) string - VariadicFunc func(...string) string - VariadicFuncInt func(int, ...string) string - NilOKFunc func(*int) bool - ErrFunc func() (string, error) - PanicFunc func() string - // Template to test evaluation of templates. - Tmpl *Template - // Unexported field; cannot be accessed by template. - unexported int -} - -type S []string - -func (S) Method0() string { - return "M0" -} - -type U struct { - V string -} - -type V struct { - j int -} - -func (v *V) String() string { - if v == nil { - return "nilV" - } - return fmt.Sprintf("<%d>", v.j) -} - -type W struct { - k int -} - -func (w *W) Error() string { - if w == nil { - return "nilW" - } - return fmt.Sprintf("[%d]", w.k) -} - -var siVal = I(S{"a", "b"}) - -var tVal = &T{ - True: true, - I: 17, - U16: 16, - X: "x", - S: "xyz", - U: &U{"v"}, - V0: V{6666}, - V1: &V{7777}, // leave V2 as nil - W0: W{888}, - W1: &W{999}, // leave W2 as nil - SI: []int{3, 4, 5}, - SICap: make([]int, 5, 10), - AI: [3]int{3, 4, 5}, - SB: []bool{true, false}, - MSI: map[string]int{"one": 1, "two": 2, "three": 3}, - MSIone: map[string]int{"one": 1}, - MXI: map[interface{}]int{"one": 1}, - MII: map[int]int{1: 1}, - MI32S: map[int32]string{1: "one", 2: "two"}, - MI64S: map[int64]string{2: "i642", 3: "i643"}, - MUI32S: map[uint32]string{2: "u322", 3: "u323"}, - MUI64S: map[uint64]string{2: "ui642", 3: "ui643"}, - MI8S: map[int8]string{2: "i82", 3: "i83"}, - MUI8S: map[uint8]string{2: "u82", 3: "u83"}, - SMSI: []map[string]int{ - {"one": 1, "two": 2}, - {"eleven": 11, "twelve": 12}, - }, - Empty1: 3, - Empty2: "empty2", - Empty3: []int{7, 8}, - Empty4: &U{"UinEmpty"}, - NonEmptyInterface: &T{X: "x"}, - NonEmptyInterfacePtS: &siVal, - NonEmptyInterfaceTypedNil: (*T)(nil), - Str: bytes.NewBuffer([]byte("foozle")), - Err: errors.New("erroozle"), - PI: newInt(23), - PS: newString("a string"), - PSI: newIntSlice(21, 22, 23), - BinaryFunc: func(a, b string) string { return fmt.Sprintf("[%s=%s]", a, b) }, - VariadicFunc: func(s ...string) string { return fmt.Sprint("<", strings.Join(s, "+"), ">") }, - VariadicFuncInt: func(a int, s ...string) string { return fmt.Sprint(a, "=<", strings.Join(s, "+"), ">") }, - NilOKFunc: func(s *int) bool { return s == nil }, - ErrFunc: func() (string, error) { return "bla", nil }, - PanicFunc: func() string { panic("test panic") }, - Tmpl: Must(New("x").Parse("test template")), // "x" is the value of .X -} - -var tSliceOfNil = []*T{nil} - -// A non-empty interface. -type I interface { - Method0() string -} - -var iVal I = tVal - -// Helpers for creation. -func newInt(n int) *int { - return &n -} - -func newString(s string) *string { - return &s -} - -func newIntSlice(n ...int) *[]int { - p := new([]int) - *p = make([]int, len(n)) - copy(*p, n) - return p -} - -// Simple methods with and without arguments. -func (t *T) Method0() string { - return "M0" -} - -func (t *T) Method1(a int) int { - return a -} - -func (t *T) Method2(a uint16, b string) string { - return fmt.Sprintf("Method2: %d %s", a, b) -} - -func (t *T) Method3(v interface{}) string { - return fmt.Sprintf("Method3: %v", v) -} - -func (t *T) Copy() *T { - n := new(T) - *n = *t - return n -} - -func (t *T) MAdd(a int, b []int) []int { - v := make([]int, len(b)) - for i, x := range b { - v[i] = x + a - } - return v -} - -var myError = errors.New("my error") - -// MyError returns a value and an error according to its argument. -func (t *T) MyError(error bool) (bool, error) { - if error { - return true, myError - } - return false, nil -} - -// A few methods to test chaining. -func (t *T) GetU() *U { - return t.U -} - -func (u *U) TrueFalse(b bool) string { - if b { - return "true" - } - return "" -} - -func typeOf(arg interface{}) string { - return fmt.Sprintf("%T", arg) -} - -type execTest struct { - name string - input string - output string - data interface{} - ok bool -} - -// bigInt and bigUint are hex string representing numbers either side -// of the max int boundary. -// We do it this way so the test doesn't depend on ints being 32 bits. -var ( - bigInt = fmt.Sprintf("0x%x", int(1<<uint(reflect.TypeOf(0).Bits()-1)-1)) - bigUint = fmt.Sprintf("0x%x", uint(1<<uint(reflect.TypeOf(0).Bits()-1))) -) - -var execTests = []execTest{ - // Trivial cases. - {"empty", "", "", nil, true}, - {"text", "some text", "some text", nil, true}, - {"nil action", "{{nil}}", "", nil, false}, - - // Ideal constants. - {"ideal int", "{{typeOf 3}}", "int", 0, true}, - {"ideal float", "{{typeOf 1.0}}", "float64", 0, true}, - {"ideal exp float", "{{typeOf 1e1}}", "float64", 0, true}, - {"ideal complex", "{{typeOf 1i}}", "complex128", 0, true}, - {"ideal int", "{{typeOf " + bigInt + "}}", "int", 0, true}, - {"ideal too big", "{{typeOf " + bigUint + "}}", "", 0, false}, - {"ideal nil without type", "{{nil}}", "", 0, false}, - - // Fields of structs. - {".X", "-{{.X}}-", "-x-", tVal, true}, - {".U.V", "-{{.U.V}}-", "-v-", tVal, true}, - {".unexported", "{{.unexported}}", "", tVal, false}, - - // Fields on maps. - {"map .one", "{{.MSI.one}}", "1", tVal, true}, - {"map .two", "{{.MSI.two}}", "2", tVal, true}, - {"map .NO", "{{.MSI.NO}}", "", tVal, true}, // NOTE: <no value> in text/template - {"map .one interface", "{{.MXI.one}}", "1", tVal, true}, - {"map .WRONG args", "{{.MSI.one 1}}", "", tVal, false}, - {"map .WRONG type", "{{.MII.one}}", "", tVal, false}, - - // Dots of all kinds to test basic evaluation. - {"dot int", "<{{.}}>", "<13>", 13, true}, - {"dot uint", "<{{.}}>", "<14>", uint(14), true}, - {"dot float", "<{{.}}>", "<15.1>", 15.1, true}, - {"dot bool", "<{{.}}>", "<true>", true, true}, - {"dot complex", "<{{.}}>", "<(16.2-17i)>", 16.2 - 17i, true}, - {"dot string", "<{{.}}>", "<hello>", "hello", true}, - {"dot slice", "<{{.}}>", "<[-1 -2 -3]>", []int{-1, -2, -3}, true}, - {"dot map", "<{{.}}>", "<map[two:22]>", map[string]int{"two": 22}, true}, - {"dot struct", "<{{.}}>", "<{7 seven}>", struct { - a int - b string - }{7, "seven"}, true}, - - // Variables. - {"$ int", "{{$}}", "123", 123, true}, - {"$.I", "{{$.I}}", "17", tVal, true}, - {"$.U.V", "{{$.U.V}}", "v", tVal, true}, - {"declare in action", "{{$x := $.U.V}}{{$x}}", "v", tVal, true}, - {"simple assignment", "{{$x := 2}}{{$x = 3}}{{$x}}", "3", tVal, true}, - {"nested assignment", - "{{$x := 2}}{{if true}}{{$x = 3}}{{end}}{{$x}}", - "3", tVal, true}, - {"nested assignment changes the last declaration", - "{{$x := 1}}{{if true}}{{$x := 2}}{{if true}}{{$x = 3}}{{end}}{{end}}{{$x}}", - "1", tVal, true}, - - // Type with String method. - {"V{6666}.String()", "-{{.V0}}-", "-{6666}-", tVal, true}, // NOTE: -<6666>- in text/template - {"&V{7777}.String()", "-{{.V1}}-", "-<7777>-", tVal, true}, - {"(*V)(nil).String()", "-{{.V2}}-", "-nilV-", tVal, true}, - - // Type with Error method. - {"W{888}.Error()", "-{{.W0}}-", "-{888}-", tVal, true}, // NOTE: -[888] in text/template - {"&W{999}.Error()", "-{{.W1}}-", "-[999]-", tVal, true}, - {"(*W)(nil).Error()", "-{{.W2}}-", "-nilW-", tVal, true}, - - // Pointers. - {"*int", "{{.PI}}", "23", tVal, true}, - {"*string", "{{.PS}}", "a string", tVal, true}, - {"*[]int", "{{.PSI}}", "[21 22 23]", tVal, true}, - {"*[]int[1]", "{{index .PSI 1}}", "22", tVal, true}, - {"NIL", "{{.NIL}}", "<nil>", tVal, true}, - - // Empty interfaces holding values. - {"empty nil", "{{.Empty0}}", "", tVal, true}, // NOTE: <no value> in text/template - {"empty with int", "{{.Empty1}}", "3", tVal, true}, - {"empty with string", "{{.Empty2}}", "empty2", tVal, true}, - {"empty with slice", "{{.Empty3}}", "[7 8]", tVal, true}, - {"empty with struct", "{{.Empty4}}", "{UinEmpty}", tVal, true}, - {"empty with struct, field", "{{.Empty4.V}}", "UinEmpty", tVal, true}, - - // Edge cases with <no value> with an interface value - {"field on interface", "{{.foo}}", "", nil, true}, // NOTE: <no value> in text/template - {"field on parenthesized interface", "{{(.).foo}}", "", nil, true}, // NOTE: <no value> in text/template - - // Issue 31810: Parenthesized first element of pipeline with arguments. - // See also TestIssue31810. - {"unparenthesized non-function", "{{1 2}}", "", nil, false}, - {"parenthesized non-function", "{{(1) 2}}", "", nil, false}, - {"parenthesized non-function with no args", "{{(1)}}", "1", nil, true}, // This is fine. - - // Method calls. - {".Method0", "-{{.Method0}}-", "-M0-", tVal, true}, - {".Method1(1234)", "-{{.Method1 1234}}-", "-1234-", tVal, true}, - {".Method1(.I)", "-{{.Method1 .I}}-", "-17-", tVal, true}, - {".Method2(3, .X)", "-{{.Method2 3 .X}}-", "-Method2: 3 x-", tVal, true}, - {".Method2(.U16, `str`)", "-{{.Method2 .U16 `str`}}-", "-Method2: 16 str-", tVal, true}, - {".Method2(.U16, $x)", "{{if $x := .X}}-{{.Method2 .U16 $x}}{{end}}-", "-Method2: 16 x-", tVal, true}, - {".Method3(nil constant)", "-{{.Method3 nil}}-", "-Method3: <nil>-", tVal, true}, - {".Method3(nil value)", "-{{.Method3 .MXI.unset}}-", "-Method3: <nil>-", tVal, true}, - {"method on var", "{{if $x := .}}-{{$x.Method2 .U16 $x.X}}{{end}}-", "-Method2: 16 x-", tVal, true}, - {"method on chained var", - "{{range .MSIone}}{{if $.U.TrueFalse $.True}}{{$.U.TrueFalse $.True}}{{else}}WRONG{{end}}{{end}}", - "true", tVal, true}, - {"chained method", - "{{range .MSIone}}{{if $.GetU.TrueFalse $.True}}{{$.U.TrueFalse $.True}}{{else}}WRONG{{end}}{{end}}", - "true", tVal, true}, - {"chained method on variable", - "{{with $x := .}}{{with .SI}}{{$.GetU.TrueFalse $.True}}{{end}}{{end}}", - "true", tVal, true}, - {".NilOKFunc not nil", "{{call .NilOKFunc .PI}}", "false", tVal, true}, - {".NilOKFunc nil", "{{call .NilOKFunc nil}}", "true", tVal, true}, - {"method on nil value from slice", "-{{range .}}{{.Method1 1234}}{{end}}-", "-1234-", tSliceOfNil, true}, - {"method on typed nil interface value", "{{.NonEmptyInterfaceTypedNil.Method0}}", "M0", tVal, true}, - - // Function call builtin. - {".BinaryFunc", "{{call .BinaryFunc `1` `2`}}", "[1=2]", tVal, true}, - {".VariadicFunc0", "{{call .VariadicFunc}}", "<>", tVal, true}, - {".VariadicFunc2", "{{call .VariadicFunc `he` `llo`}}", "<he+llo>", tVal, true}, - {".VariadicFuncInt", "{{call .VariadicFuncInt 33 `he` `llo`}}", "33=<he+llo>", tVal, true}, - {"if .BinaryFunc call", "{{ if .BinaryFunc}}{{call .BinaryFunc `1` `2`}}{{end}}", "[1=2]", tVal, true}, - {"if not .BinaryFunc call", "{{ if not .BinaryFunc}}{{call .BinaryFunc `1` `2`}}{{else}}No{{end}}", "No", tVal, true}, - {"Interface Call", `{{stringer .S}}`, "foozle", map[string]interface{}{"S": bytes.NewBufferString("foozle")}, true}, - {".ErrFunc", "{{call .ErrFunc}}", "bla", tVal, true}, - {"call nil", "{{call nil}}", "", tVal, false}, - - // Erroneous function calls (check args). - {".BinaryFuncTooFew", "{{call .BinaryFunc `1`}}", "", tVal, false}, - {".BinaryFuncTooMany", "{{call .BinaryFunc `1` `2` `3`}}", "", tVal, false}, - {".BinaryFuncBad0", "{{call .BinaryFunc 1 3}}", "", tVal, false}, - {".BinaryFuncBad1", "{{call .BinaryFunc `1` 3}}", "", tVal, false}, - {".VariadicFuncBad0", "{{call .VariadicFunc 3}}", "", tVal, false}, - {".VariadicFuncIntBad0", "{{call .VariadicFuncInt}}", "", tVal, false}, - {".VariadicFuncIntBad`", "{{call .VariadicFuncInt `x`}}", "", tVal, false}, - {".VariadicFuncNilBad", "{{call .VariadicFunc nil}}", "", tVal, false}, - - // Pipelines. - {"pipeline", "-{{.Method0 | .Method2 .U16}}-", "-Method2: 16 M0-", tVal, true}, - {"pipeline func", "-{{call .VariadicFunc `llo` | call .VariadicFunc `he` }}-", "-<he+<llo>>-", tVal, true}, - - // Nil values aren't missing arguments. - {"nil pipeline", "{{ .Empty0 | call .NilOKFunc }}", "true", tVal, true}, - {"nil call arg", "{{ call .NilOKFunc .Empty0 }}", "true", tVal, true}, - {"bad nil pipeline", "{{ .Empty0 | .VariadicFunc }}", "", tVal, false}, - - // Parenthesized expressions - {"parens in pipeline", "{{printf `%d %d %d` (1) (2 | add 3) (add 4 (add 5 6))}}", "1 5 15", tVal, true}, - - // Parenthesized expressions with field accesses - {"parens: $ in paren", "{{($).X}}", "x", tVal, true}, - {"parens: $.GetU in paren", "{{($.GetU).V}}", "v", tVal, true}, - {"parens: $ in paren in pipe", "{{($ | echo).X}}", "x", tVal, true}, - {"parens: spaces and args", `{{(makemap "up" "down" "left" "right").left}}`, "right", tVal, true}, - - // If. - {"if true", "{{if true}}TRUE{{end}}", "TRUE", tVal, true}, - {"if false", "{{if false}}TRUE{{else}}FALSE{{end}}", "FALSE", tVal, true}, - {"if nil", "{{if nil}}TRUE{{end}}", "", tVal, false}, - {"if on typed nil interface value", "{{if .NonEmptyInterfaceTypedNil}}TRUE{{ end }}", "", tVal, true}, - {"if 1", "{{if 1}}NON-ZERO{{else}}ZERO{{end}}", "NON-ZERO", tVal, true}, - {"if 0", "{{if 0}}NON-ZERO{{else}}ZERO{{end}}", "ZERO", tVal, true}, - {"if 1.5", "{{if 1.5}}NON-ZERO{{else}}ZERO{{end}}", "NON-ZERO", tVal, true}, - {"if 0.0", "{{if .FloatZero}}NON-ZERO{{else}}ZERO{{end}}", "ZERO", tVal, true}, - {"if 1.5i", "{{if 1.5i}}NON-ZERO{{else}}ZERO{{end}}", "NON-ZERO", tVal, true}, - {"if 0.0i", "{{if .ComplexZero}}NON-ZERO{{else}}ZERO{{end}}", "ZERO", tVal, true}, - {"if emptystring", "{{if ``}}NON-EMPTY{{else}}EMPTY{{end}}", "EMPTY", tVal, true}, - {"if string", "{{if `notempty`}}NON-EMPTY{{else}}EMPTY{{end}}", "NON-EMPTY", tVal, true}, - {"if emptyslice", "{{if .SIEmpty}}NON-EMPTY{{else}}EMPTY{{end}}", "EMPTY", tVal, true}, - {"if slice", "{{if .SI}}NON-EMPTY{{else}}EMPTY{{end}}", "NON-EMPTY", tVal, true}, - {"if emptymap", "{{if .MSIEmpty}}NON-EMPTY{{else}}EMPTY{{end}}", "EMPTY", tVal, true}, - {"if map", "{{if .MSI}}NON-EMPTY{{else}}EMPTY{{end}}", "NON-EMPTY", tVal, true}, - {"if map unset", "{{if .MXI.none}}NON-ZERO{{else}}ZERO{{end}}", "ZERO", tVal, true}, - {"if map not unset", "{{if not .MXI.none}}ZERO{{else}}NON-ZERO{{end}}", "ZERO", tVal, true}, - {"if $x with $y int", "{{if $x := true}}{{with $y := .I}}{{$x}},{{$y}}{{end}}{{end}}", "true,17", tVal, true}, - {"if $x with $x int", "{{if $x := true}}{{with $x := .I}}{{$x}},{{end}}{{$x}}{{end}}", "17,true", tVal, true}, - {"if else if", "{{if false}}FALSE{{else if true}}TRUE{{end}}", "TRUE", tVal, true}, - {"if else chain", "{{if eq 1 3}}1{{else if eq 2 3}}2{{else if eq 3 3}}3{{end}}", "3", tVal, true}, - - // Print etc. - {"print", `{{print "hello, print"}}`, "hello, print", tVal, true}, - {"print 123", `{{print 1 2 3}}`, "1 2 3", tVal, true}, - {"print nil", `{{print nil}}`, "<nil>", tVal, true}, - {"println", `{{println 1 2 3}}`, "1 2 3\n", tVal, true}, - {"printf int", `{{printf "%04x" 127}}`, "007f", tVal, true}, - {"printf float", `{{printf "%g" 3.5}}`, "3.5", tVal, true}, - {"printf complex", `{{printf "%g" 1+7i}}`, "(1+7i)", tVal, true}, - {"printf string", `{{printf "%s" "hello"}}`, "hello", tVal, true}, - {"printf function", `{{printf "%#q" zeroArgs}}`, "`zeroArgs`", tVal, true}, - {"printf field", `{{printf "%s" .U.V}}`, "v", tVal, true}, - {"printf method", `{{printf "%s" .Method0}}`, "M0", tVal, true}, - {"printf dot", `{{with .I}}{{printf "%d" .}}{{end}}`, "17", tVal, true}, - {"printf var", `{{with $x := .I}}{{printf "%d" $x}}{{end}}`, "17", tVal, true}, - {"printf lots", `{{printf "%d %s %g %s" 127 "hello" 7-3i .Method0}}`, "127 hello (7-3i) M0", tVal, true}, - - // HTML. - {"html", `{{html "<script>alert(\"XSS\");</script>"}}`, - "<script>alert("XSS");</script>", nil, true}, - {"html pipeline", `{{printf "<script>alert(\"XSS\");</script>" | html}}`, - "<script>alert("XSS");</script>", nil, true}, - {"html", `{{html .PS}}`, "a string", tVal, true}, - {"html typed nil", `{{html .NIL}}`, "<nil>", tVal, true}, - {"html untyped nil", `{{html .Empty0}}`, "<nil>", tVal, true}, // NOTE: "<no value>" in text/template - - // JavaScript. - {"js", `{{js .}}`, `It\'d be nice.`, `It'd be nice.`, true}, - - // URL query. - {"urlquery", `{{"http://www.example.org/"|urlquery}}`, "http%3A%2F%2Fwww.example.org%2F", nil, true}, - - // Booleans - {"not", "{{not true}} {{not false}}", "false true", nil, true}, - {"and", "{{and false 0}} {{and 1 0}} {{and 0 true}} {{and 1 1}}", "false 0 0 1", nil, true}, - {"or", "{{or 0 0}} {{or 1 0}} {{or 0 true}} {{or 1 1}}", "0 1 true 1", nil, true}, - {"boolean if", "{{if and true 1 `hi`}}TRUE{{else}}FALSE{{end}}", "TRUE", tVal, true}, - {"boolean if not", "{{if and true 1 `hi` | not}}TRUE{{else}}FALSE{{end}}", "FALSE", nil, true}, - - // Indexing. - {"slice[0]", "{{index .SI 0}}", "3", tVal, true}, - {"slice[1]", "{{index .SI 1}}", "4", tVal, true}, - {"slice[HUGE]", "{{index .SI 10}}", "", tVal, false}, - {"slice[WRONG]", "{{index .SI `hello`}}", "", tVal, false}, - {"slice[nil]", "{{index .SI nil}}", "", tVal, false}, - {"map[one]", "{{index .MSI `one`}}", "1", tVal, true}, - {"map[two]", "{{index .MSI `two`}}", "2", tVal, true}, - {"map[NO]", "{{index .MSI `XXX`}}", "0", tVal, true}, - {"map[nil]", "{{index .MSI nil}}", "", tVal, false}, - {"map[``]", "{{index .MSI ``}}", "0", tVal, true}, - {"map[WRONG]", "{{index .MSI 10}}", "", tVal, false}, - {"double index", "{{index .SMSI 1 `eleven`}}", "11", tVal, true}, - {"nil[1]", "{{index nil 1}}", "", tVal, false}, - {"map MI64S", "{{index .MI64S 2}}", "i642", tVal, true}, - {"map MI32S", "{{index .MI32S 2}}", "two", tVal, true}, - {"map MUI64S", "{{index .MUI64S 3}}", "ui643", tVal, true}, - {"map MI8S", "{{index .MI8S 3}}", "i83", tVal, true}, - {"map MUI8S", "{{index .MUI8S 2}}", "u82", tVal, true}, - {"index of an interface field", "{{index .Empty3 0}}", "7", tVal, true}, - - // Slicing. - {"slice[:]", "{{slice .SI}}", "[3 4 5]", tVal, true}, - {"slice[1:]", "{{slice .SI 1}}", "[4 5]", tVal, true}, - {"slice[1:2]", "{{slice .SI 1 2}}", "[4]", tVal, true}, - {"slice[-1:]", "{{slice .SI -1}}", "", tVal, false}, - {"slice[1:-2]", "{{slice .SI 1 -2}}", "", tVal, false}, - {"slice[1:2:-1]", "{{slice .SI 1 2 -1}}", "", tVal, false}, - {"slice[2:1]", "{{slice .SI 2 1}}", "", tVal, false}, - {"slice[2:2:1]", "{{slice .SI 2 2 1}}", "", tVal, false}, - {"out of range", "{{slice .SI 4 5}}", "", tVal, false}, - {"out of range", "{{slice .SI 2 2 5}}", "", tVal, false}, - {"len(s) < indexes < cap(s)", "{{slice .SICap 6 10}}", "[0 0 0 0]", tVal, true}, - {"len(s) < indexes < cap(s)", "{{slice .SICap 6 10 10}}", "[0 0 0 0]", tVal, true}, - {"indexes > cap(s)", "{{slice .SICap 10 11}}", "", tVal, false}, - {"indexes > cap(s)", "{{slice .SICap 6 10 11}}", "", tVal, false}, - {"array[:]", "{{slice .AI}}", "[3 4 5]", tVal, true}, - {"array[1:]", "{{slice .AI 1}}", "[4 5]", tVal, true}, - {"array[1:2]", "{{slice .AI 1 2}}", "[4]", tVal, true}, - {"string[:]", "{{slice .S}}", "xyz", tVal, true}, - {"string[0:1]", "{{slice .S 0 1}}", "x", tVal, true}, - {"string[1:]", "{{slice .S 1}}", "yz", tVal, true}, - {"string[1:2]", "{{slice .S 1 2}}", "y", tVal, true}, - {"out of range", "{{slice .S 1 5}}", "", tVal, false}, - {"3-index slice of string", "{{slice .S 1 2 2}}", "", tVal, false}, - {"slice of an interface field", "{{slice .Empty3 0 1}}", "[7]", tVal, true}, - - // Len. - {"slice", "{{len .SI}}", "3", tVal, true}, - {"map", "{{len .MSI }}", "3", tVal, true}, - {"len of int", "{{len 3}}", "", tVal, false}, - {"len of nothing", "{{len .Empty0}}", "", tVal, false}, - {"len of an interface field", "{{len .Empty3}}", "2", tVal, true}, - - // With. - {"with true", "{{with true}}{{.}}{{end}}", "true", tVal, true}, - {"with false", "{{with false}}{{.}}{{else}}FALSE{{end}}", "FALSE", tVal, true}, - {"with 1", "{{with 1}}{{.}}{{else}}ZERO{{end}}", "1", tVal, true}, - {"with 0", "{{with 0}}{{.}}{{else}}ZERO{{end}}", "ZERO", tVal, true}, - {"with 1.5", "{{with 1.5}}{{.}}{{else}}ZERO{{end}}", "1.5", tVal, true}, - {"with 0.0", "{{with .FloatZero}}{{.}}{{else}}ZERO{{end}}", "ZERO", tVal, true}, - {"with 1.5i", "{{with 1.5i}}{{.}}{{else}}ZERO{{end}}", "(0+1.5i)", tVal, true}, - {"with 0.0i", "{{with .ComplexZero}}{{.}}{{else}}ZERO{{end}}", "ZERO", tVal, true}, - {"with emptystring", "{{with ``}}{{.}}{{else}}EMPTY{{end}}", "EMPTY", tVal, true}, - {"with string", "{{with `notempty`}}{{.}}{{else}}EMPTY{{end}}", "notempty", tVal, true}, - {"with emptyslice", "{{with .SIEmpty}}{{.}}{{else}}EMPTY{{end}}", "EMPTY", tVal, true}, - {"with slice", "{{with .SI}}{{.}}{{else}}EMPTY{{end}}", "[3 4 5]", tVal, true}, - {"with emptymap", "{{with .MSIEmpty}}{{.}}{{else}}EMPTY{{end}}", "EMPTY", tVal, true}, - {"with map", "{{with .MSIone}}{{.}}{{else}}EMPTY{{end}}", "map[one:1]", tVal, true}, - {"with empty interface, struct field", "{{with .Empty4}}{{.V}}{{end}}", "UinEmpty", tVal, true}, - {"with $x int", "{{with $x := .I}}{{$x}}{{end}}", "17", tVal, true}, - {"with $x struct.U.V", "{{with $x := $}}{{$x.U.V}}{{end}}", "v", tVal, true}, - {"with variable and action", "{{with $x := $}}{{$y := $.U.V}}{{$y}}{{end}}", "v", tVal, true}, - {"with on typed nil interface value", "{{with .NonEmptyInterfaceTypedNil}}TRUE{{ end }}", "", tVal, true}, - - // Range. - {"range []int", "{{range .SI}}-{{.}}-{{end}}", "-3--4--5-", tVal, true}, - {"range empty no else", "{{range .SIEmpty}}-{{.}}-{{end}}", "", tVal, true}, - {"range []int else", "{{range .SI}}-{{.}}-{{else}}EMPTY{{end}}", "-3--4--5-", tVal, true}, - {"range empty else", "{{range .SIEmpty}}-{{.}}-{{else}}EMPTY{{end}}", "EMPTY", tVal, true}, - {"range []int break else", "{{range .SI}}-{{.}}-{{break}}NOTREACHED{{else}}EMPTY{{end}}", "-3-", tVal, true}, - {"range []int continue else", "{{range .SI}}-{{.}}-{{continue}}NOTREACHED{{else}}EMPTY{{end}}", "-3--4--5-", tVal, true}, - {"range []bool", "{{range .SB}}-{{.}}-{{end}}", "-true--false-", tVal, true}, - {"range []int method", "{{range .SI | .MAdd .I}}-{{.}}-{{end}}", "-20--21--22-", tVal, true}, - {"range map", "{{range .MSI}}-{{.}}-{{end}}", "-1--3--2-", tVal, true}, - {"range empty map no else", "{{range .MSIEmpty}}-{{.}}-{{end}}", "", tVal, true}, - {"range map else", "{{range .MSI}}-{{.}}-{{else}}EMPTY{{end}}", "-1--3--2-", tVal, true}, - {"range empty map else", "{{range .MSIEmpty}}-{{.}}-{{else}}EMPTY{{end}}", "EMPTY", tVal, true}, - {"range empty interface", "{{range .Empty3}}-{{.}}-{{else}}EMPTY{{end}}", "-7--8-", tVal, true}, - {"range empty nil", "{{range .Empty0}}-{{.}}-{{end}}", "", tVal, true}, - {"range $x SI", "{{range $x := .SI}}<{{$x}}>{{end}}", "<3><4><5>", tVal, true}, - {"range $x $y SI", "{{range $x, $y := .SI}}<{{$x}}={{$y}}>{{end}}", "<0=3><1=4><2=5>", tVal, true}, - {"range $x MSIone", "{{range $x := .MSIone}}<{{$x}}>{{end}}", "<1>", tVal, true}, - {"range $x $y MSIone", "{{range $x, $y := .MSIone}}<{{$x}}={{$y}}>{{end}}", "<one=1>", tVal, true}, - {"range $x PSI", "{{range $x := .PSI}}<{{$x}}>{{end}}", "<21><22><23>", tVal, true}, - {"declare in range", "{{range $x := .PSI}}<{{$foo:=$x}}{{$x}}>{{end}}", "<21><22><23>", tVal, true}, - {"range count", `{{range $i, $x := count 5}}[{{$i}}]{{$x}}{{end}}`, "[0]a[1]b[2]c[3]d[4]e", tVal, true}, - {"range nil count", `{{range $i, $x := count 0}}{{else}}empty{{end}}`, "empty", tVal, true}, - - // Cute examples. - {"or as if true", `{{or .SI "slice is empty"}}`, "[3 4 5]", tVal, true}, - {"or as if false", `{{or .SIEmpty "slice is empty"}}`, "slice is empty", tVal, true}, - - // Error handling. - {"error method, error", "{{.MyError true}}", "", tVal, false}, - {"error method, no error", "{{.MyError false}}", "false", tVal, true}, - - // Numbers - {"decimal", "{{print 1234}}", "1234", tVal, true}, - {"decimal _", "{{print 12_34}}", "1234", tVal, true}, - {"binary", "{{print 0b101}}", "5", tVal, true}, - {"binary _", "{{print 0b_1_0_1}}", "5", tVal, true}, - {"BINARY", "{{print 0B101}}", "5", tVal, true}, - {"octal0", "{{print 0377}}", "255", tVal, true}, - {"octal", "{{print 0o377}}", "255", tVal, true}, - {"octal _", "{{print 0o_3_7_7}}", "255", tVal, true}, - {"OCTAL", "{{print 0O377}}", "255", tVal, true}, - {"hex", "{{print 0x123}}", "291", tVal, true}, - {"hex _", "{{print 0x1_23}}", "291", tVal, true}, - {"HEX", "{{print 0X123ABC}}", "1194684", tVal, true}, - {"float", "{{print 123.4}}", "123.4", tVal, true}, - {"float _", "{{print 0_0_1_2_3.4}}", "123.4", tVal, true}, - {"hex float", "{{print +0x1.ep+2}}", "7.5", tVal, true}, - {"hex float _", "{{print +0x_1.e_0p+0_2}}", "7.5", tVal, true}, - {"HEX float", "{{print +0X1.EP+2}}", "7.5", tVal, true}, - {"print multi", "{{print 1_2_3_4 7.5_00_00_00}}", "1234 7.5", tVal, true}, - {"print multi2", "{{print 1234 0x0_1.e_0p+02}}", "1234 7.5", tVal, true}, - - // Fixed bugs. - // Must separate dot and receiver; otherwise args are evaluated with dot set to variable. - {"bug0", "{{range .MSIone}}{{if $.Method1 .}}X{{end}}{{end}}", "X", tVal, true}, - // Do not loop endlessly in indirect for non-empty interfaces. - // The bug appears with *interface only; looped forever. - {"bug1", "{{.Method0}}", "M0", &iVal, true}, - // Was taking address of interface field, so method set was empty. - {"bug2", "{{$.NonEmptyInterface.Method0}}", "M0", tVal, true}, - // Struct values were not legal in with - mere oversight. - {"bug3", "{{with $}}{{.Method0}}{{end}}", "M0", tVal, true}, - // Nil interface values in if. - {"bug4", "{{if .Empty0}}non-nil{{else}}nil{{end}}", "nil", tVal, true}, - // Stringer. - {"bug5", "{{.Str}}", "foozle", tVal, true}, - {"bug5a", "{{.Err}}", "erroozle", tVal, true}, - // Args need to be indirected and dereferenced sometimes. - {"bug6a", "{{vfunc .V0 .V1}}", "vfunc", tVal, true}, - {"bug6b", "{{vfunc .V0 .V0}}", "vfunc", tVal, true}, - {"bug6c", "{{vfunc .V1 .V0}}", "vfunc", tVal, true}, - {"bug6d", "{{vfunc .V1 .V1}}", "vfunc", tVal, true}, - // Legal parse but illegal execution: non-function should have no arguments. - {"bug7a", "{{3 2}}", "", tVal, false}, - {"bug7b", "{{$x := 1}}{{$x 2}}", "", tVal, false}, - {"bug7c", "{{$x := 1}}{{3 | $x}}", "", tVal, false}, - // Pipelined arg was not being type-checked. - {"bug8a", "{{3|oneArg}}", "", tVal, false}, - {"bug8b", "{{4|dddArg 3}}", "", tVal, false}, - // A bug was introduced that broke map lookups for lower-case names. - {"bug9", "{{.cause}}", "neglect", map[string]string{"cause": "neglect"}, true}, - // Field chain starting with function did not work. - {"bug10", "{{mapOfThree.three}}-{{(mapOfThree).three}}", "3-3", 0, true}, - // Dereferencing nil pointer while evaluating function arguments should not panic. Issue 7333. - {"bug11", "{{valueString .PS}}", "", T{}, false}, - // 0xef gave constant type float64. Issue 8622. - {"bug12xe", "{{printf `%T` 0xef}}", "int", T{}, true}, - {"bug12xE", "{{printf `%T` 0xEE}}", "int", T{}, true}, - {"bug12Xe", "{{printf `%T` 0Xef}}", "int", T{}, true}, - {"bug12XE", "{{printf `%T` 0XEE}}", "int", T{}, true}, - // Chained nodes did not work as arguments. Issue 8473. - {"bug13", "{{print (.Copy).I}}", "17", tVal, true}, - // Didn't protect against nil or literal values in field chains. - {"bug14a", "{{(nil).True}}", "", tVal, false}, - {"bug14b", "{{$x := nil}}{{$x.anything}}", "", tVal, false}, - {"bug14c", `{{$x := (1.0)}}{{$y := ("hello")}}{{$x.anything}}{{$y.true}}`, "", tVal, false}, - // Didn't call validateType on function results. Issue 10800. - {"bug15", "{{valueString returnInt}}", "", tVal, false}, - // Variadic function corner cases. Issue 10946. - {"bug16a", "{{true|printf}}", "", tVal, false}, - {"bug16b", "{{1|printf}}", "", tVal, false}, - {"bug16c", "{{1.1|printf}}", "", tVal, false}, - {"bug16d", "{{'x'|printf}}", "", tVal, false}, - {"bug16e", "{{0i|printf}}", "", tVal, false}, - {"bug16f", "{{true|twoArgs \"xxx\"}}", "", tVal, false}, - {"bug16g", "{{\"aaa\" |twoArgs \"bbb\"}}", "twoArgs=bbbaaa", tVal, true}, - {"bug16h", "{{1|oneArg}}", "", tVal, false}, - {"bug16i", "{{\"aaa\"|oneArg}}", "oneArg=aaa", tVal, true}, - {"bug16j", "{{1+2i|printf \"%v\"}}", "(1+2i)", tVal, true}, - {"bug16k", "{{\"aaa\"|printf }}", "aaa", tVal, true}, - {"bug17a", "{{.NonEmptyInterface.X}}", "x", tVal, true}, - {"bug17b", "-{{.NonEmptyInterface.Method1 1234}}-", "-1234-", tVal, true}, - {"bug17c", "{{len .NonEmptyInterfacePtS}}", "2", tVal, true}, - {"bug17d", "{{index .NonEmptyInterfacePtS 0}}", "a", tVal, true}, - {"bug17e", "{{range .NonEmptyInterfacePtS}}-{{.}}-{{end}}", "-a--b-", tVal, true}, - - // More variadic function corner cases. Some runes would get evaluated - // as constant floats instead of ints. Issue 34483. - {"bug18a", "{{eq . '.'}}", "true", '.', true}, - {"bug18b", "{{eq . 'e'}}", "true", 'e', true}, - {"bug18c", "{{eq . 'P'}}", "true", 'P', true}, -} - -func zeroArgs() string { - return "zeroArgs" -} - -func oneArg(a string) string { - return "oneArg=" + a -} - -func twoArgs(a, b string) string { - return "twoArgs=" + a + b -} - -func dddArg(a int, b ...string) string { - return fmt.Sprintln(a, b) -} - -// count returns a channel that will deliver n sequential 1-letter strings starting at "a" -func count(n int) chan string { - if n == 0 { - return nil - } - c := make(chan string) - go func() { - for i := 0; i < n; i++ { - c <- "abcdefghijklmnop"[i : i+1] - } - close(c) - }() - return c -} - -// vfunc takes a *V and a V -func vfunc(V, *V) string { - return "vfunc" -} - -// valueString takes a string, not a pointer. -func valueString(v string) string { - return "value is ignored" -} - -// returnInt returns an int -func returnInt() int { - return 7 -} - -func add(args ...int) int { - sum := 0 - for _, x := range args { - sum += x - } - return sum -} - -func echo(arg interface{}) interface{} { - return arg -} - -func makemap(arg ...string) map[string]string { - if len(arg)%2 != 0 { - panic("bad makemap") - } - m := make(map[string]string) - for i := 0; i < len(arg); i += 2 { - m[arg[i]] = arg[i+1] - } - return m -} - -func stringer(s fmt.Stringer) string { - return s.String() -} - -func mapOfThree() interface{} { - return map[string]int{"three": 3} -} - -func testExecute(execTests []execTest, template *Template, t *testing.T) { - b := new(bytes.Buffer) - funcs := FuncMap{ - "add": add, - "count": count, - "dddArg": dddArg, - "echo": echo, - "makemap": makemap, - "mapOfThree": mapOfThree, - "oneArg": oneArg, - "returnInt": returnInt, - "stringer": stringer, - "twoArgs": twoArgs, - "typeOf": typeOf, - "valueString": valueString, - "vfunc": vfunc, - "zeroArgs": zeroArgs, - } - for _, test := range execTests { - var tmpl *Template - var err error - if template == nil { - tmpl, err = New(test.name).Funcs(funcs).Parse(test.input) - } else { - tmpl, err = template.Clone() - if err != nil { - t.Errorf("%s: clone error: %s", test.name, err) - continue - } - tmpl, err = tmpl.New(test.name).Funcs(funcs).Parse(test.input) - } - if err != nil { - t.Errorf("%s: parse error: %s", test.name, err) - continue - } - b.Reset() - err = tmpl.Execute(b, test.data) - switch { - case !test.ok && err == nil: - t.Errorf("%s: expected error; got none", test.name) - continue - case test.ok && err != nil: - t.Errorf("%s: unexpected execute error: %s", test.name, err) - continue - case !test.ok && err != nil: - // expected error, got one - if *debug { - fmt.Printf("%s: %s\n\t%s\n", test.name, test.input, err) - } - } - result := b.String() - if result != test.output { - t.Errorf("%s: expected\n\t%q\ngot\n\t%q", test.name, test.output, result) - } - } -} - -func TestExecute(t *testing.T) { - testExecute(execTests, nil, t) -} - -var delimPairs = []string{ - "", "", // default - "{{", "}}", // same as default - "|", "|", // same - "(日)", "(本)", // peculiar -} - -func TestDelims(t *testing.T) { - const hello = "Hello, world" - var value = struct{ Str string }{hello} - for i := 0; i < len(delimPairs); i += 2 { - text := ".Str" - left := delimPairs[i+0] - trueLeft := left - right := delimPairs[i+1] - trueRight := right - if left == "" { // default case - trueLeft = "{{" - } - if right == "" { // default case - trueRight = "}}" - } - text = trueLeft + text + trueRight - // Now add a comment - text += trueLeft + "/*comment*/" + trueRight - // Now add an action containing a string. - text += trueLeft + `"` + trueLeft + `"` + trueRight - // At this point text looks like `{{.Str}}{{/*comment*/}}{{"{{"}}`. - tmpl, err := New("delims").Delims(left, right).Parse(text) - if err != nil { - t.Fatalf("delim %q text %q parse err %s", left, text, err) - } - var b = new(bytes.Buffer) - err = tmpl.Execute(b, value) - if err != nil { - t.Fatalf("delim %q exec err %s", left, err) - } - if b.String() != hello+trueLeft { - t.Errorf("expected %q got %q", hello+trueLeft, b.String()) - } - } -} - -// Check that an error from a method flows back to the top. -func TestExecuteError(t *testing.T) { - b := new(bytes.Buffer) - tmpl := New("error") - _, err := tmpl.Parse("{{.MyError true}}") - if err != nil { - t.Fatalf("parse error: %s", err) - } - err = tmpl.Execute(b, tVal) - if err == nil { - t.Errorf("expected error; got none") - } else if !strings.Contains(err.Error(), myError.Error()) { - if *debug { - fmt.Printf("test execute error: %s\n", err) - } - t.Errorf("expected myError; got %s", err) - } -} - -const execErrorText = `line 1 -line 2 -line 3 -{{template "one" .}} -{{define "one"}}{{template "two" .}}{{end}} -{{define "two"}}{{template "three" .}}{{end}} -{{define "three"}}{{index "hi" $}}{{end}}` - -// Check that an error from a nested template contains all the relevant information. -func TestExecError(t *testing.T) { - tmpl, err := New("top").Parse(execErrorText) - if err != nil { - t.Fatal("parse error:", err) - } - var b bytes.Buffer - err = tmpl.Execute(&b, 5) // 5 is out of range indexing "hi" - if err == nil { - t.Fatal("expected error") - } - const want = `template: top:7:20: executing "three" at <index "hi" $>: error calling index: index out of range: 5` - got := err.Error() - if got != want { - t.Errorf("expected\n%q\ngot\n%q", want, got) - } -} - -func TestJSEscaping(t *testing.T) { - testCases := []struct { - in, exp string - }{ - {`a`, `a`}, - {`'foo`, `\'foo`}, - {`Go "jump" \`, `Go \"jump\" \\`}, - {`Yukihiro says "今日は世界"`, `Yukihiro says \"今日は世界\"`}, - {"unprintable \uFDFF", `unprintable \uFDFF`}, - {`<html>`, `\u003Chtml\u003E`}, - {`no = in attributes`, `no \u003D in attributes`}, - {`' does not become HTML entity`, `\u0026#x27; does not become HTML entity`}, - } - for _, tc := range testCases { - s := JSEscapeString(tc.in) - if s != tc.exp { - t.Errorf("JS escaping [%s] got [%s] want [%s]", tc.in, s, tc.exp) - } - } -} - -// A nice example: walk a binary tree. - -type Tree struct { - Val int - Left, Right *Tree -} - -// Use different delimiters to test Set.Delims. -// Also test the trimming of leading and trailing spaces. -const treeTemplate = ` - (- define "tree" -) - [ - (- .Val -) - (- with .Left -) - (template "tree" . -) - (- end -) - (- with .Right -) - (- template "tree" . -) - (- end -) - ] - (- end -) -` - -func TestTree(t *testing.T) { - var tree = &Tree{ - 1, - &Tree{ - 2, &Tree{ - 3, - &Tree{ - 4, nil, nil, - }, - nil, - }, - &Tree{ - 5, - &Tree{ - 6, nil, nil, - }, - nil, - }, - }, - &Tree{ - 7, - &Tree{ - 8, - &Tree{ - 9, nil, nil, - }, - nil, - }, - &Tree{ - 10, - &Tree{ - 11, nil, nil, - }, - nil, - }, - }, - } - tmpl, err := New("root").Delims("(", ")").Parse(treeTemplate) - if err != nil { - t.Fatal("parse error:", err) - } - var b bytes.Buffer - const expect = "[1[2[3[4]][5[6]]][7[8[9]][10[11]]]]" - // First by looking up the template. - err = tmpl.Lookup("tree").Execute(&b, tree) - if err != nil { - t.Fatal("exec error:", err) - } - result := b.String() - if result != expect { - t.Errorf("expected %q got %q", expect, result) - } - // Then direct to execution. - b.Reset() - err = tmpl.ExecuteTemplate(&b, "tree", tree) - if err != nil { - t.Fatal("exec error:", err) - } - result = b.String() - if result != expect { - t.Errorf("expected %q got %q", expect, result) - } -} - -func TestExecuteOnNewTemplate(t *testing.T) { - // This is issue 3872. - New("Name").Templates() - // This is issue 11379. - // new(Template).Templates() // TODO: crashes - // new(Template).Parse("") // TODO: crashes - // new(Template).New("abc").Parse("") // TODO: crashes - // new(Template).Execute(nil, nil) // TODO: crashes; returns an error (but does not crash) - // new(Template).ExecuteTemplate(nil, "XXX", nil) // TODO: crashes; returns an error (but does not crash) -} - -const testTemplates = `{{define "one"}}one{{end}}{{define "two"}}two{{end}}` - -func TestMessageForExecuteEmpty(t *testing.T) { - // Test a truly empty template. - tmpl := New("empty") - var b bytes.Buffer - err := tmpl.Execute(&b, 0) - if err == nil { - t.Fatal("expected initial error") - } - got := err.Error() - want := `template: "empty" is an incomplete or empty template` // NOTE: text/template has extra "empty: " in message - if got != want { - t.Errorf("expected error %s got %s", want, got) - } - - // Add a non-empty template to check that the error is helpful. - tmpl = New("empty") - tests, err := New("").Parse(testTemplates) - if err != nil { - t.Fatal(err) - } - tmpl.AddParseTree("secondary", tests.Tree) - err = tmpl.Execute(&b, 0) - if err == nil { - t.Fatal("expected second error") - } - got = err.Error() - if got != want { - t.Errorf("expected error %s got %s", want, got) - } - // Make sure we can execute the secondary. - err = tmpl.ExecuteTemplate(&b, "secondary", 0) - if err != nil { - t.Fatal(err) - } -} - -func TestFinalForPrintf(t *testing.T) { - tmpl, err := New("").Parse(`{{"x" | printf}}`) - if err != nil { - t.Fatal(err) - } - var b bytes.Buffer - err = tmpl.Execute(&b, 0) - if err != nil { - t.Fatal(err) - } -} - -type cmpTest struct { - expr string - truth string - ok bool -} - -var cmpTests = []cmpTest{ - {"eq true true", "true", true}, - {"eq true false", "false", true}, - {"eq 1+2i 1+2i", "true", true}, - {"eq 1+2i 1+3i", "false", true}, - {"eq 1.5 1.5", "true", true}, - {"eq 1.5 2.5", "false", true}, - {"eq 1 1", "true", true}, - {"eq 1 2", "false", true}, - {"eq `xy` `xy`", "true", true}, - {"eq `xy` `xyz`", "false", true}, - {"eq .Uthree .Uthree", "true", true}, - {"eq .Uthree .Ufour", "false", true}, - {"eq 3 4 5 6 3", "true", true}, - {"eq 3 4 5 6 7", "false", true}, - {"ne true true", "false", true}, - {"ne true false", "true", true}, - {"ne 1+2i 1+2i", "false", true}, - {"ne 1+2i 1+3i", "true", true}, - {"ne 1.5 1.5", "false", true}, - {"ne 1.5 2.5", "true", true}, - {"ne 1 1", "false", true}, - {"ne 1 2", "true", true}, - {"ne `xy` `xy`", "false", true}, - {"ne `xy` `xyz`", "true", true}, - {"ne .Uthree .Uthree", "false", true}, - {"ne .Uthree .Ufour", "true", true}, - {"lt 1.5 1.5", "false", true}, - {"lt 1.5 2.5", "true", true}, - {"lt 1 1", "false", true}, - {"lt 1 2", "true", true}, - {"lt `xy` `xy`", "false", true}, - {"lt `xy` `xyz`", "true", true}, - {"lt .Uthree .Uthree", "false", true}, - {"lt .Uthree .Ufour", "true", true}, - {"le 1.5 1.5", "true", true}, - {"le 1.5 2.5", "true", true}, - {"le 2.5 1.5", "false", true}, - {"le 1 1", "true", true}, - {"le 1 2", "true", true}, - {"le 2 1", "false", true}, - {"le `xy` `xy`", "true", true}, - {"le `xy` `xyz`", "true", true}, - {"le `xyz` `xy`", "false", true}, - {"le .Uthree .Uthree", "true", true}, - {"le .Uthree .Ufour", "true", true}, - {"le .Ufour .Uthree", "false", true}, - {"gt 1.5 1.5", "false", true}, - {"gt 1.5 2.5", "false", true}, - {"gt 1 1", "false", true}, - {"gt 2 1", "true", true}, - {"gt 1 2", "false", true}, - {"gt `xy` `xy`", "false", true}, - {"gt `xy` `xyz`", "false", true}, - {"gt .Uthree .Uthree", "false", true}, - {"gt .Uthree .Ufour", "false", true}, - {"gt .Ufour .Uthree", "true", true}, - {"ge 1.5 1.5", "true", true}, - {"ge 1.5 2.5", "false", true}, - {"ge 2.5 1.5", "true", true}, - {"ge 1 1", "true", true}, - {"ge 1 2", "false", true}, - {"ge 2 1", "true", true}, - {"ge `xy` `xy`", "true", true}, - {"ge `xy` `xyz`", "false", true}, - {"ge `xyz` `xy`", "true", true}, - {"ge .Uthree .Uthree", "true", true}, - {"ge .Uthree .Ufour", "false", true}, - {"ge .Ufour .Uthree", "true", true}, - // Mixing signed and unsigned integers. - {"eq .Uthree .Three", "true", true}, - {"eq .Three .Uthree", "true", true}, - {"le .Uthree .Three", "true", true}, - {"le .Three .Uthree", "true", true}, - {"ge .Uthree .Three", "true", true}, - {"ge .Three .Uthree", "true", true}, - {"lt .Uthree .Three", "false", true}, - {"lt .Three .Uthree", "false", true}, - {"gt .Uthree .Three", "false", true}, - {"gt .Three .Uthree", "false", true}, - {"eq .Ufour .Three", "false", true}, - {"lt .Ufour .Three", "false", true}, - {"gt .Ufour .Three", "true", true}, - {"eq .NegOne .Uthree", "false", true}, - {"eq .Uthree .NegOne", "false", true}, - {"ne .NegOne .Uthree", "true", true}, - {"ne .Uthree .NegOne", "true", true}, - {"lt .NegOne .Uthree", "true", true}, - {"lt .Uthree .NegOne", "false", true}, - {"le .NegOne .Uthree", "true", true}, - {"le .Uthree .NegOne", "false", true}, - {"gt .NegOne .Uthree", "false", true}, - {"gt .Uthree .NegOne", "true", true}, - {"ge .NegOne .Uthree", "false", true}, - {"ge .Uthree .NegOne", "true", true}, - {"eq (index `x` 0) 'x'", "true", true}, // The example that triggered this rule. - {"eq (index `x` 0) 'y'", "false", true}, - {"eq .V1 .V2", "true", true}, - {"eq .Ptr .Ptr", "true", true}, - {"eq .Ptr .NilPtr", "false", true}, - {"eq .NilPtr .NilPtr", "true", true}, - {"eq .Iface1 .Iface1", "true", true}, - {"eq .Iface1 .Iface2", "false", true}, - {"eq .Iface2 .Iface2", "true", true}, - // Errors - {"eq `xy` 1", "", false}, // Different types. - {"eq 2 2.0", "", false}, // Different types. - {"lt true true", "", false}, // Unordered types. - {"lt 1+0i 1+0i", "", false}, // Unordered types. - {"eq .Ptr 1", "", false}, // Incompatible types. - {"eq .Ptr .NegOne", "", false}, // Incompatible types. - {"eq .Map .Map", "", false}, // Uncomparable types. - {"eq .Map .V1", "", false}, // Uncomparable types. -} - -func TestComparison(t *testing.T) { - b := new(bytes.Buffer) - var cmpStruct = struct { - Uthree, Ufour uint - NegOne, Three int - Ptr, NilPtr *int - Map map[int]int - V1, V2 V - Iface1, Iface2 fmt.Stringer - }{ - Uthree: 3, - Ufour: 4, - NegOne: -1, - Three: 3, - Ptr: new(int), - Iface1: b, - } - for _, test := range cmpTests { - text := fmt.Sprintf("{{if %s}}true{{else}}false{{end}}", test.expr) - tmpl, err := New("empty").Parse(text) - if err != nil { - t.Fatalf("%q: %s", test.expr, err) - } - b.Reset() - err = tmpl.Execute(b, &cmpStruct) - if test.ok && err != nil { - t.Errorf("%s errored incorrectly: %s", test.expr, err) - continue - } - if !test.ok && err == nil { - t.Errorf("%s did not error", test.expr) - continue - } - if b.String() != test.truth { - t.Errorf("%s: want %s; got %s", test.expr, test.truth, b.String()) - } - } -} - -func TestMissingMapKey(t *testing.T) { - data := map[string]int{ - "x": 99, - } - tmpl, err := New("t1").Parse("{{.x}} {{.y}}") - if err != nil { - t.Fatal(err) - } - var b bytes.Buffer - // By default, just get "<no value>" // NOTE: not in html/template, get empty string - err = tmpl.Execute(&b, data) - if err != nil { - t.Fatal(err) - } - want := "99 " - got := b.String() - if got != want { - t.Errorf("got %q; expected %q", got, want) - } - // Same if we set the option explicitly to the default. - tmpl.Option("missingkey=default") - b.Reset() - err = tmpl.Execute(&b, data) - if err != nil { - t.Fatal("default:", err) - } - got = b.String() - if got != want { - t.Errorf("got %q; expected %q", got, want) - } - // Next we ask for a zero value - tmpl.Option("missingkey=zero") - b.Reset() - err = tmpl.Execute(&b, data) - if err != nil { - t.Fatal("zero:", err) - } - want = "99 0" - got = b.String() - if got != want { - t.Errorf("got %q; expected %q", got, want) - } - // Now we ask for an error. - tmpl.Option("missingkey=error") - err = tmpl.Execute(&b, data) - if err == nil { - t.Errorf("expected error; got none") - } - // same Option, but now a nil interface: ask for an error - err = tmpl.Execute(&b, nil) - t.Log(err) - if err == nil { - t.Errorf("expected error for nil-interface; got none") - } -} - -// Test that the error message for multiline unterminated string -// refers to the line number of the opening quote. -func TestUnterminatedStringError(t *testing.T) { - _, err := New("X").Parse("hello\n\n{{`unterminated\n\n\n\n}}\n some more\n\n") - if err == nil { - t.Fatal("expected error") - } - str := err.Error() - if !strings.Contains(str, "X:3: unterminated raw quoted string") { - t.Fatalf("unexpected error: %s", str) - } -} - -const alwaysErrorText = "always be failing" - -var alwaysError = errors.New(alwaysErrorText) - -type ErrorWriter int - -func (e ErrorWriter) Write(p []byte) (int, error) { - return 0, alwaysError -} - -func TestExecuteGivesExecError(t *testing.T) { - // First, a non-execution error shouldn't be an ExecError. - tmpl, err := New("X").Parse("hello") - if err != nil { - t.Fatal(err) - } - err = tmpl.Execute(ErrorWriter(0), 0) - if err == nil { - t.Fatal("expected error; got none") - } - if err.Error() != alwaysErrorText { - t.Errorf("expected %q error; got %q", alwaysErrorText, err) - } - // This one should be an ExecError. - tmpl, err = New("X").Parse("hello, {{.X.Y}}") - if err != nil { - t.Fatal(err) - } - err = tmpl.Execute(ioutil.Discard, 0) - if err == nil { - t.Fatal("expected error; got none") - } - eerr, ok := err.(template.ExecError) - if !ok { - t.Fatalf("did not expect ExecError %s", eerr) - } - expect := "field X in type int" - if !strings.Contains(err.Error(), expect) { - t.Errorf("expected %q; got %q", expect, err) - } -} - -func funcNameTestFunc() int { - return 0 -} - -func TestGoodFuncNames(t *testing.T) { - names := []string{ - "_", - "a", - "a1", - "a1", - "Ӵ", - } - for _, name := range names { - tmpl := New("X").Funcs( - FuncMap{ - name: funcNameTestFunc, - }, - ) - if tmpl == nil { - t.Fatalf("nil result for %q", name) - } - } -} - -func TestBadFuncNames(t *testing.T) { - names := []string{ - "", - "2", - "a-b", - } - for _, name := range names { - testBadFuncName(name, t) - } -} - -func testBadFuncName(name string, t *testing.T) { - t.Helper() - defer func() { - recover() - }() - New("X").Funcs( - FuncMap{ - name: funcNameTestFunc, - }, - ) - // If we get here, the name did not cause a panic, which is how Funcs - // reports an error. - t.Errorf("%q succeeded incorrectly as function name", name) -} - -func TestBlock(t *testing.T) { - const ( - input = `a({{block "inner" .}}bar({{.}})baz{{end}})b` - want = `a(bar(hello)baz)b` - overlay = `{{define "inner"}}foo({{.}})bar{{end}}` - want2 = `a(foo(goodbye)bar)b` - ) - tmpl, err := New("outer").Parse(input) - if err != nil { - t.Fatal(err) - } - tmpl2, err := Must(tmpl.Clone()).Parse(overlay) - if err != nil { - t.Fatal(err) - } - - var buf bytes.Buffer - if err := tmpl.Execute(&buf, "hello"); err != nil { - t.Fatal(err) - } - if got := buf.String(); got != want { - t.Errorf("got %q, want %q", got, want) - } - - buf.Reset() - if err := tmpl2.Execute(&buf, "goodbye"); err != nil { - t.Fatal(err) - } - if got := buf.String(); got != want2 { - t.Errorf("got %q, want %q", got, want2) - } -} - -func TestEvalFieldErrors(t *testing.T) { - tests := []struct { - name, src string - value interface{} - want string - }{ - { - // Check that calling an invalid field on nil pointer - // prints a field error instead of a distracting nil - // pointer error. https://golang.org/issue/15125 - "MissingFieldOnNil", - "{{.MissingField}}", - (*T)(nil), - "can't evaluate field MissingField in type *template.T", - }, - { - "MissingFieldOnNonNil", - "{{.MissingField}}", - &T{}, - "can't evaluate field MissingField in type *template.T", - }, - { - "ExistingFieldOnNil", - "{{.X}}", - (*T)(nil), - "nil pointer evaluating *template.T.X", - }, - { - "MissingKeyOnNilMap", - "{{.MissingKey}}", - (*map[string]string)(nil), - "nil pointer evaluating *map[string]string.MissingKey", - }, - { - "MissingKeyOnNilMapPtr", - "{{.MissingKey}}", - (*map[string]string)(nil), - "nil pointer evaluating *map[string]string.MissingKey", - }, - { - "MissingKeyOnMapPtrToNil", - "{{.MissingKey}}", - &map[string]string{}, - "<nil>", - }, - } - for _, tc := range tests { - t.Run(tc.name, func(t *testing.T) { - tmpl := Must(New("tmpl").Parse(tc.src)) - err := tmpl.Execute(ioutil.Discard, tc.value) - got := "<nil>" - if err != nil { - got = err.Error() - } - if !strings.HasSuffix(got, tc.want) { - t.Fatalf("got error %q, want %q", got, tc.want) - } - }) - } -} - -func TestMaxExecDepth(t *testing.T) { - if testing.Short() { - t.Skip("skipping in -short mode") - } - tmpl := Must(New("tmpl").Parse(`{{template "tmpl" .}}`)) - err := tmpl.Execute(ioutil.Discard, nil) - got := "<nil>" - if err != nil { - got = err.Error() - } - const want = "exceeded maximum template depth" - if !strings.Contains(got, want) { - t.Errorf("got error %q; want %q", got, want) - } -} - -func TestAddrOfIndex(t *testing.T) { - // golang.org/issue/14916. - // Before index worked on reflect.Values, the .String could not be - // found on the (incorrectly unaddressable) V value, - // in contrast to range, which worked fine. - // Also testing that passing a reflect.Value to tmpl.Execute works. - texts := []string{ - `{{range .}}{{.String}}{{end}}`, - `{{with index . 0}}{{.String}}{{end}}`, - } - for _, text := range texts { - tmpl := Must(New("tmpl").Parse(text)) - var buf bytes.Buffer - err := tmpl.Execute(&buf, reflect.ValueOf([]V{{1}})) - if err != nil { - t.Fatalf("%s: Execute: %v", text, err) - } - if buf.String() != "<1>" { - t.Fatalf("%s: template output = %q, want %q", text, &buf, "<1>") - } - } -} - -func TestInterfaceValues(t *testing.T) { - // golang.org/issue/17714. - // Before index worked on reflect.Values, interface values - // were always implicitly promoted to the underlying value, - // except that nil interfaces were promoted to the zero reflect.Value. - // Eliminating a round trip to interface{} and back to reflect.Value - // eliminated this promotion, breaking these cases. - tests := []struct { - text string - out string - }{ - {`{{index .Nil 1}}`, "ERROR: index of untyped nil"}, - {`{{index .Slice 2}}`, "2"}, - {`{{index .Slice .Two}}`, "2"}, - {`{{call .Nil 1}}`, "ERROR: call of nil"}, - {`{{call .PlusOne 1}}`, "2"}, - {`{{call .PlusOne .One}}`, "2"}, - {`{{and (index .Slice 0) true}}`, "0"}, - {`{{and .Zero true}}`, "0"}, - {`{{and (index .Slice 1) false}}`, "false"}, - {`{{and .One false}}`, "false"}, - {`{{or (index .Slice 0) false}}`, "false"}, - {`{{or .Zero false}}`, "false"}, - {`{{or (index .Slice 1) true}}`, "1"}, - {`{{or .One true}}`, "1"}, - {`{{not (index .Slice 0)}}`, "true"}, - {`{{not .Zero}}`, "true"}, - {`{{not (index .Slice 1)}}`, "false"}, - {`{{not .One}}`, "false"}, - {`{{eq (index .Slice 0) .Zero}}`, "true"}, - {`{{eq (index .Slice 1) .One}}`, "true"}, - {`{{ne (index .Slice 0) .Zero}}`, "false"}, - {`{{ne (index .Slice 1) .One}}`, "false"}, - {`{{ge (index .Slice 0) .One}}`, "false"}, - {`{{ge (index .Slice 1) .Zero}}`, "true"}, - {`{{gt (index .Slice 0) .One}}`, "false"}, - {`{{gt (index .Slice 1) .Zero}}`, "true"}, - {`{{le (index .Slice 0) .One}}`, "true"}, - {`{{le (index .Slice 1) .Zero}}`, "false"}, - {`{{lt (index .Slice 0) .One}}`, "true"}, - {`{{lt (index .Slice 1) .Zero}}`, "false"}, - } - - for _, tt := range tests { - tmpl := Must(New("tmpl").Parse(tt.text)) - var buf bytes.Buffer - err := tmpl.Execute(&buf, map[string]interface{}{ - "PlusOne": func(n int) int { - return n + 1 - }, - "Slice": []int{0, 1, 2, 3}, - "One": 1, - "Two": 2, - "Nil": nil, - "Zero": 0, - }) - if strings.HasPrefix(tt.out, "ERROR:") { - e := strings.TrimSpace(strings.TrimPrefix(tt.out, "ERROR:")) - if err == nil || !strings.Contains(err.Error(), e) { - t.Errorf("%s: Execute: %v, want error %q", tt.text, err, e) - } - continue - } - if err != nil { - t.Errorf("%s: Execute: %v", tt.text, err) - continue - } - if buf.String() != tt.out { - t.Errorf("%s: template output = %q, want %q", tt.text, &buf, tt.out) - } - } -} - -// Check that panics during calls are recovered and returned as errors. -func TestExecutePanicDuringCall(t *testing.T) { - funcs := map[string]interface{}{ - "doPanic": func() string { - panic("custom panic string") - }, - } - tests := []struct { - name string - input string - data interface{} - wantErr string - }{ - { - "direct func call panics", - "{{doPanic}}", (*T)(nil), - `template: t:1:2: executing "t" at <doPanic>: error calling doPanic: custom panic string`, - }, - { - "indirect func call panics", - "{{call doPanic}}", (*T)(nil), - `template: t:1:7: executing "t" at <doPanic>: error calling doPanic: custom panic string`, - }, - { - "direct method call panics", - "{{.GetU}}", (*T)(nil), - `template: t:1:2: executing "t" at <.GetU>: error calling GetU: runtime error: invalid memory address or nil pointer dereference`, - }, - { - "indirect method call panics", - "{{call .GetU}}", (*T)(nil), - `template: t:1:7: executing "t" at <.GetU>: error calling GetU: runtime error: invalid memory address or nil pointer dereference`, - }, - { - "func field call panics", - "{{call .PanicFunc}}", tVal, - `template: t:1:2: executing "t" at <call .PanicFunc>: error calling call: test panic`, - }, - { - "method call on nil interface", - "{{.NonEmptyInterfaceNil.Method0}}", tVal, - `template: t:1:23: executing "t" at <.NonEmptyInterfaceNil.Method0>: nil pointer evaluating template.I.Method0`, - }, - } - for _, tc := range tests { - b := new(bytes.Buffer) - tmpl, err := New("t").Funcs(funcs).Parse(tc.input) - if err != nil { - t.Fatalf("parse error: %s", err) - } - err = tmpl.Execute(b, tc.data) - if err == nil { - t.Errorf("%s: expected error; got none", tc.name) - } else if !strings.Contains(err.Error(), tc.wantErr) { - if *debug { - fmt.Printf("%s: test execute error: %s\n", tc.name, err) - } - t.Errorf("%s: expected error:\n%s\ngot:\n%s", tc.name, tc.wantErr, err) - } - } -} - -// Issue 31810. Check that a parenthesized first argument behaves properly. -func TestIssue31810(t *testing.T) { - t.Skip("broken in html/template") - - // A simple value with no arguments is fine. - var b bytes.Buffer - const text = "{{ (.) }}" - tmpl, err := New("").Parse(text) - if err != nil { - t.Error(err) - } - err = tmpl.Execute(&b, "result") - if err != nil { - t.Error(err) - } - if b.String() != "result" { - t.Errorf("%s got %q, expected %q", text, b.String(), "result") - } - - // Even a plain function fails - need to use call. - f := func() string { return "result" } - b.Reset() - err = tmpl.Execute(&b, f) - if err == nil { - t.Error("expected error with no call, got none") - } - - // Works if the function is explicitly called. - const textCall = "{{ (call .) }}" - tmpl, err = New("").Parse(textCall) - b.Reset() - err = tmpl.Execute(&b, f) - if err != nil { - t.Error(err) - } - if b.String() != "result" { - t.Errorf("%s got %q, expected %q", textCall, b.String(), "result") - } -} - -// Issue 39807. There was a race applying escapeTemplate. - -const raceText = ` -{{- define "jstempl" -}} -var v = "v"; -{{- end -}} -<script type="application/javascript"> -{{ template "jstempl" $ }} -</script> -` - -func TestEscapeRace(t *testing.T) { - tmpl := New("") - _, err := tmpl.New("templ.html").Parse(raceText) - if err != nil { - t.Fatal(err) - } - const count = 20 - for i := 0; i < count; i++ { - _, err := tmpl.New(fmt.Sprintf("x%d.html", i)).Parse(`{{ template "templ.html" .}}`) - if err != nil { - t.Fatal(err) - } - } - - var wg sync.WaitGroup - for i := 0; i < 10; i++ { - wg.Add(1) - go func() { - defer wg.Done() - for j := 0; j < count; j++ { - sub := tmpl.Lookup(fmt.Sprintf("x%d.html", j)) - if err := sub.Execute(ioutil.Discard, nil); err != nil { - t.Error(err) - } - } - }() - } - wg.Wait() -} - -func TestRecursiveExecute(t *testing.T) { - tmpl := New("") - - recur := func() (HTML, error) { - var sb strings.Builder - if err := tmpl.ExecuteTemplate(&sb, "subroutine", nil); err != nil { - t.Fatal(err) - } - return HTML(sb.String()), nil - } - - m := FuncMap{ - "recur": recur, - } - - top, err := tmpl.New("x.html").Funcs(m).Parse(`{{recur}}`) - if err != nil { - t.Fatal(err) - } - _, err = tmpl.New("subroutine").Parse(`<a href="/x?p={{"'a<b'"}}">`) - if err != nil { - t.Fatal(err) - } - if err := top.Execute(ioutil.Discard, nil); err != nil { - t.Fatal(err) - } -} - -// recursiveInvoker is for TestRecursiveExecuteViaMethod. -type recursiveInvoker struct { - t *testing.T - tmpl *Template -} - -func (r *recursiveInvoker) Recur() (string, error) { - var sb strings.Builder - if err := r.tmpl.ExecuteTemplate(&sb, "subroutine", nil); err != nil { - r.t.Fatal(err) - } - return sb.String(), nil -} - -func TestRecursiveExecuteViaMethod(t *testing.T) { - tmpl := New("") - top, err := tmpl.New("x.html").Parse(`{{.Recur}}`) - if err != nil { - t.Fatal(err) - } - _, err = tmpl.New("subroutine").Parse(`<a href="/x?p={{"'a<b'"}}">`) - if err != nil { - t.Fatal(err) - } - r := &recursiveInvoker{ - t: t, - tmpl: tmpl, - } - if err := top.Execute(ioutil.Discard, r); err != nil { - t.Fatal(err) - } -} - -// Issue 43295. -func TestTemplateFuncsAfterClone(t *testing.T) { - s := `{{ f . }}` - want := "test" - orig := New("orig").Funcs(map[string]interface{}{ - "f": func(in string) string { - return in - }, - }).New("child") - - overviewTmpl := Must(Must(orig.Clone()).Parse(s)) - var out strings.Builder - if err := overviewTmpl.Execute(&out, want); err != nil { - t.Fatal(err) - } - if got := out.String(); got != want { - t.Fatalf("got %q; want %q", got, want) - } -}
diff --git a/go.dev/cmd/internal/html/template/html.go b/go.dev/cmd/internal/html/template/html.go deleted file mode 100644 index 356b829..0000000 --- a/go.dev/cmd/internal/html/template/html.go +++ /dev/null
@@ -1,265 +0,0 @@ -// Copyright 2011 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package template - -import ( - "bytes" - "fmt" - "strings" - "unicode/utf8" -) - -// htmlNospaceEscaper escapes for inclusion in unquoted attribute values. -func htmlNospaceEscaper(args ...interface{}) string { - s, t := stringify(args...) - if t == contentTypeHTML { - return htmlReplacer(stripTags(s), htmlNospaceNormReplacementTable, false) - } - return htmlReplacer(s, htmlNospaceReplacementTable, false) -} - -// attrEscaper escapes for inclusion in quoted attribute values. -func attrEscaper(args ...interface{}) string { - s, t := stringify(args...) - if t == contentTypeHTML { - return htmlReplacer(stripTags(s), htmlNormReplacementTable, true) - } - return htmlReplacer(s, htmlReplacementTable, true) -} - -// rcdataEscaper escapes for inclusion in an RCDATA element body. -func rcdataEscaper(args ...interface{}) string { - s, t := stringify(args...) - if t == contentTypeHTML { - return htmlReplacer(s, htmlNormReplacementTable, true) - } - return htmlReplacer(s, htmlReplacementTable, true) -} - -// htmlEscaper escapes for inclusion in HTML text. -func htmlEscaper(args ...interface{}) string { - s, t := stringify(args...) - if t == contentTypeHTML { - return s - } - return htmlReplacer(s, htmlReplacementTable, true) -} - -// htmlReplacementTable contains the runes that need to be escaped -// inside a quoted attribute value or in a text node. -var htmlReplacementTable = []string{ - // https://www.w3.org/TR/html5/syntax.html#attribute-value-(unquoted)-state - // U+0000 NULL Parse error. Append a U+FFFD REPLACEMENT - // CHARACTER character to the current attribute's value. - // " - // and similarly - // https://www.w3.org/TR/html5/syntax.html#before-attribute-value-state - 0: "\uFFFD", - '"': """, - '&': "&", - '\'': "'", - '+': "+", - '<': "<", - '>': ">", -} - -// htmlNormReplacementTable is like htmlReplacementTable but without '&' to -// avoid over-encoding existing entities. -var htmlNormReplacementTable = []string{ - 0: "\uFFFD", - '"': """, - '\'': "'", - '+': "+", - '<': "<", - '>': ">", -} - -// htmlNospaceReplacementTable contains the runes that need to be escaped -// inside an unquoted attribute value. -// The set of runes escaped is the union of the HTML specials and -// those determined by running the JS below in browsers: -// <div id=d></div> -// <script>(function () { -// var a = [], d = document.getElementById("d"), i, c, s; -// for (i = 0; i < 0x10000; ++i) { -// c = String.fromCharCode(i); -// d.innerHTML = "<span title=" + c + "lt" + c + "></span>" -// s = d.getElementsByTagName("SPAN")[0]; -// if (!s || s.title !== c + "lt" + c) { a.push(i.toString(16)); } -// } -// document.write(a.join(", ")); -// })()</script> -var htmlNospaceReplacementTable = []string{ - 0: "�", - '\t': "	", - '\n': " ", - '\v': "", - '\f': "", - '\r': " ", - ' ': " ", - '"': """, - '&': "&", - '\'': "'", - '+': "+", - '<': "<", - '=': "=", - '>': ">", - // A parse error in the attribute value (unquoted) and - // before attribute value states. - // Treated as a quoting character by IE. - '`': "`", -} - -// htmlNospaceNormReplacementTable is like htmlNospaceReplacementTable but -// without '&' to avoid over-encoding existing entities. -var htmlNospaceNormReplacementTable = []string{ - 0: "�", - '\t': "	", - '\n': " ", - '\v': "", - '\f': "", - '\r': " ", - ' ': " ", - '"': """, - '\'': "'", - '+': "+", - '<': "<", - '=': "=", - '>': ">", - // A parse error in the attribute value (unquoted) and - // before attribute value states. - // Treated as a quoting character by IE. - '`': "`", -} - -// htmlReplacer returns s with runes replaced according to replacementTable -// and when badRunes is true, certain bad runes are allowed through unescaped. -func htmlReplacer(s string, replacementTable []string, badRunes bool) string { - written, b := 0, new(strings.Builder) - r, w := rune(0), 0 - for i := 0; i < len(s); i += w { - // Cannot use 'for range s' because we need to preserve the width - // of the runes in the input. If we see a decoding error, the input - // width will not be utf8.Runelen(r) and we will overrun the buffer. - r, w = utf8.DecodeRuneInString(s[i:]) - if int(r) < len(replacementTable) { - if repl := replacementTable[r]; len(repl) != 0 { - if written == 0 { - b.Grow(len(s)) - } - b.WriteString(s[written:i]) - b.WriteString(repl) - written = i + w - } - } else if badRunes { - // No-op. - // IE does not allow these ranges in unquoted attrs. - } else if 0xfdd0 <= r && r <= 0xfdef || 0xfff0 <= r && r <= 0xffff { - if written == 0 { - b.Grow(len(s)) - } - fmt.Fprintf(b, "%s&#x%x;", s[written:i], r) - written = i + w - } - } - if written == 0 { - return s - } - b.WriteString(s[written:]) - return b.String() -} - -// stripTags takes a snippet of HTML and returns only the text content. -// For example, `<b>¡Hi!</b> <script>...</script>` -> `¡Hi! `. -func stripTags(html string) string { - var b bytes.Buffer - s, c, i, allText := []byte(html), context{}, 0, true - // Using the transition funcs helps us avoid mangling - // `<div title="1>2">` or `I <3 Ponies!`. - for i != len(s) { - if c.delim == delimNone { - st := c.state - // Use RCDATA instead of parsing into JS or CSS styles. - if c.element != elementNone && !isInTag(st) { - st = stateRCDATA - } - d, nread := transitionFunc[st](c, s[i:]) - i1 := i + nread - if c.state == stateText || c.state == stateRCDATA { - // Emit text up to the start of the tag or comment. - j := i1 - if d.state != c.state { - for j1 := j - 1; j1 >= i; j1-- { - if s[j1] == '<' { - j = j1 - break - } - } - } - b.Write(s[i:j]) - } else { - allText = false - } - c, i = d, i1 - continue - } - i1 := i + bytes.IndexAny(s[i:], delimEnds[c.delim]) - if i1 < i { - break - } - if c.delim != delimSpaceOrTagEnd { - // Consume any quote. - i1++ - } - c, i = context{state: stateTag, element: c.element}, i1 - } - if allText { - return html - } else if c.state == stateText || c.state == stateRCDATA { - b.Write(s[i:]) - } - return b.String() -} - -// htmlNameFilter accepts valid parts of an HTML attribute or tag name or -// a known-safe HTML attribute. -func htmlNameFilter(args ...interface{}) string { - s, t := stringify(args...) - if t == contentTypeHTMLAttr { - return s - } - if len(s) == 0 { - // Avoid violation of structure preservation. - // <input checked {{.K}}={{.V}}>. - // Without this, if .K is empty then .V is the value of - // checked, but otherwise .V is the value of the attribute - // named .K. - return filterFailsafe - } - s = strings.ToLower(s) - if t := attrType(s); t != contentTypePlain { - // TODO: Split attr and element name part filters so we can recognize known attributes. - return filterFailsafe - } - for _, r := range s { - switch { - case '0' <= r && r <= '9': - case 'a' <= r && r <= 'z': - default: - return filterFailsafe - } - } - return s -} - -// commentEscaper returns the empty string regardless of input. -// Comment content does not correspond to any parsed structure or -// human-readable content, so the simplest and most secure policy is to drop -// content interpolated into comments. -// This approach is equally valid whether or not static comment content is -// removed from the template. -func commentEscaper(args ...interface{}) string { - return "" -}
diff --git a/go.dev/cmd/internal/html/template/html_test.go b/go.dev/cmd/internal/html/template/html_test.go deleted file mode 100644 index f04ee04..0000000 --- a/go.dev/cmd/internal/html/template/html_test.go +++ /dev/null
@@ -1,97 +0,0 @@ -// Copyright 2011 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package template - -import ( - "html" - "strings" - "testing" -) - -func TestHTMLNospaceEscaper(t *testing.T) { - input := ("\x00\x01\x02\x03\x04\x05\x06\x07\x08\t\n\x0b\x0c\r\x0e\x0f" + - "\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f" + - ` !"#$%&'()*+,-./` + - `0123456789:;<=>?` + - `@ABCDEFGHIJKLMNO` + - `PQRSTUVWXYZ[\]^_` + - "`abcdefghijklmno" + - "pqrstuvwxyz{|}~\x7f" + - "\u00A0\u0100\u2028\u2029\ufeff\ufdec\U0001D11E" + - "erroneous\x960") // keep at the end - - want := ("�\x01\x02\x03\x04\x05\x06\x07" + - "\x08	  \x0E\x0F" + - "\x10\x11\x12\x13\x14\x15\x16\x17" + - "\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f" + - ` !"#$%&'()*+,-./` + - `0123456789:;<=>?` + - `@ABCDEFGHIJKLMNO` + - `PQRSTUVWXYZ[\]^_` + - ``abcdefghijklmno` + - `pqrstuvwxyz{|}~` + "\u007f" + - "\u00A0\u0100\u2028\u2029\ufeff\U0001D11E" + - "erroneous�0") // keep at the end - - got := htmlNospaceEscaper(input) - if got != want { - t.Errorf("encode: want\n\t%q\nbut got\n\t%q", want, got) - } - - r := strings.NewReplacer("\x00", "\ufffd", "\x96", "\ufffd") - got, want = html.UnescapeString(got), r.Replace(input) - if want != got { - t.Errorf("decode: want\n\t%q\nbut got\n\t%q", want, got) - } -} - -func TestStripTags(t *testing.T) { - tests := []struct { - input, want string - }{ - {"", ""}, - {"Hello, World!", "Hello, World!"}, - {"foo&bar", "foo&bar"}, - {`Hello <a href="www.example.com/">World</a>!`, "Hello World!"}, - {"Foo <textarea>Bar</textarea> Baz", "Foo Bar Baz"}, - {"Foo <!-- Bar --> Baz", "Foo Baz"}, - {"<", "<"}, - {"foo < bar", "foo < bar"}, - {`Foo<script type="text/javascript">alert(1337)</script>Bar`, "FooBar"}, - {`Foo<div title="1>2">Bar`, "FooBar"}, - {`I <3 Ponies!`, `I <3 Ponies!`}, - {`<script>foo()</script>`, ``}, - } - - for _, test := range tests { - if got := stripTags(test.input); got != test.want { - t.Errorf("%q: want %q, got %q", test.input, test.want, got) - } - } -} - -func BenchmarkHTMLNospaceEscaper(b *testing.B) { - for i := 0; i < b.N; i++ { - htmlNospaceEscaper("The <i>quick</i>,\r\n<span style='color:brown'>brown</span> fox jumps\u2028over the <canine class=\"lazy\">dog</canine>") - } -} - -func BenchmarkHTMLNospaceEscaperNoSpecials(b *testing.B) { - for i := 0; i < b.N; i++ { - htmlNospaceEscaper("The_quick,_brown_fox_jumps_over_the_lazy_dog.") - } -} - -func BenchmarkStripTags(b *testing.B) { - for i := 0; i < b.N; i++ { - stripTags("The <i>quick</i>,\r\n<span style='color:brown'>brown</span> fox jumps\u2028over the <canine class=\"lazy\">dog</canine>") - } -} - -func BenchmarkStripTagsNoSpecials(b *testing.B) { - for i := 0; i < b.N; i++ { - stripTags("The quick, brown fox jumps over the lazy dog.") - } -}
diff --git a/go.dev/cmd/internal/html/template/js.go b/go.dev/cmd/internal/html/template/js.go deleted file mode 100644 index ea9c183..0000000 --- a/go.dev/cmd/internal/html/template/js.go +++ /dev/null
@@ -1,431 +0,0 @@ -// Copyright 2011 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package template - -import ( - "bytes" - "encoding/json" - "fmt" - "reflect" - "strings" - "unicode/utf8" -) - -// nextJSCtx returns the context that determines whether a slash after the -// given run of tokens starts a regular expression instead of a division -// operator: / or /=. -// -// This assumes that the token run does not include any string tokens, comment -// tokens, regular expression literal tokens, or division operators. -// -// This fails on some valid but nonsensical JavaScript programs like -// "x = ++/foo/i" which is quite different than "x++/foo/i", but is not known to -// fail on any known useful programs. It is based on the draft -// JavaScript 2.0 lexical grammar and requires one token of lookbehind: -// https://www.mozilla.org/js/language/js20-2000-07/rationale/syntax.html -func nextJSCtx(s []byte, preceding jsCtx) jsCtx { - s = bytes.TrimRight(s, "\t\n\f\r \u2028\u2029") - if len(s) == 0 { - return preceding - } - - // All cases below are in the single-byte UTF-8 group. - switch c, n := s[len(s)-1], len(s); c { - case '+', '-': - // ++ and -- are not regexp preceders, but + and - are whether - // they are used as infix or prefix operators. - start := n - 1 - // Count the number of adjacent dashes or pluses. - for start > 0 && s[start-1] == c { - start-- - } - if (n-start)&1 == 1 { - // Reached for trailing minus signs since "---" is the - // same as "-- -". - return jsCtxRegexp - } - return jsCtxDivOp - case '.': - // Handle "42." - if n != 1 && '0' <= s[n-2] && s[n-2] <= '9' { - return jsCtxDivOp - } - return jsCtxRegexp - // Suffixes for all punctuators from section 7.7 of the language spec - // that only end binary operators not handled above. - case ',', '<', '>', '=', '*', '%', '&', '|', '^', '?': - return jsCtxRegexp - // Suffixes for all punctuators from section 7.7 of the language spec - // that are prefix operators not handled above. - case '!', '~': - return jsCtxRegexp - // Matches all the punctuators from section 7.7 of the language spec - // that are open brackets not handled above. - case '(', '[': - return jsCtxRegexp - // Matches all the punctuators from section 7.7 of the language spec - // that precede expression starts. - case ':', ';', '{': - return jsCtxRegexp - // CAVEAT: the close punctuators ('}', ']', ')') precede div ops and - // are handled in the default except for '}' which can precede a - // division op as in - // ({ valueOf: function () { return 42 } } / 2 - // which is valid, but, in practice, developers don't divide object - // literals, so our heuristic works well for code like - // function () { ... } /foo/.test(x) && sideEffect(); - // The ')' punctuator can precede a regular expression as in - // if (b) /foo/.test(x) && ... - // but this is much less likely than - // (a + b) / c - case '}': - return jsCtxRegexp - default: - // Look for an IdentifierName and see if it is a keyword that - // can precede a regular expression. - j := n - for j > 0 && isJSIdentPart(rune(s[j-1])) { - j-- - } - if regexpPrecederKeywords[string(s[j:])] { - return jsCtxRegexp - } - } - // Otherwise is a punctuator not listed above, or - // a string which precedes a div op, or an identifier - // which precedes a div op. - return jsCtxDivOp -} - -// regexpPrecederKeywords is a set of reserved JS keywords that can precede a -// regular expression in JS source. -var regexpPrecederKeywords = map[string]bool{ - "break": true, - "case": true, - "continue": true, - "delete": true, - "do": true, - "else": true, - "finally": true, - "in": true, - "instanceof": true, - "return": true, - "throw": true, - "try": true, - "typeof": true, - "void": true, -} - -var jsonMarshalType = reflect.TypeOf((*json.Marshaler)(nil)).Elem() - -// indirectToJSONMarshaler returns the value, after dereferencing as many times -// as necessary to reach the base type (or nil) or an implementation of json.Marshal. -func indirectToJSONMarshaler(a interface{}) interface{} { - // text/template now supports passing untyped nil as a func call - // argument, so we must support it. Otherwise we'd panic below, as one - // cannot call the Type or Interface methods on an invalid - // reflect.Value. See golang.org/issue/18716. - if a == nil { - return nil - } - - v := reflect.ValueOf(a) - for !v.Type().Implements(jsonMarshalType) && v.Kind() == reflect.Ptr && !v.IsNil() { - v = v.Elem() - } - return v.Interface() -} - -// jsValEscaper escapes its inputs to a JS Expression (section 11.14) that has -// neither side-effects nor free variables outside (NaN, Infinity). -func jsValEscaper(args ...interface{}) string { - var a interface{} - if len(args) == 1 { - a = indirectToJSONMarshaler(args[0]) - switch t := a.(type) { - case JS: - return string(t) - case JSStr: - // TODO: normalize quotes. - return `"` + string(t) + `"` - case json.Marshaler: - // Do not treat as a Stringer. - case fmt.Stringer: - a = t.String() - } - } else { - for i, arg := range args { - args[i] = indirectToJSONMarshaler(arg) - } - a = fmt.Sprint(args...) - } - // TODO: detect cycles before calling Marshal which loops infinitely on - // cyclic data. This may be an unacceptable DoS risk. - b, err := json.Marshal(a) - if err != nil { - // Put a space before comment so that if it is flush against - // a division operator it is not turned into a line comment: - // x/{{y}} - // turning into - // x//* error marshaling y: - // second line of error message */null - return fmt.Sprintf(" /* %s */null ", strings.ReplaceAll(err.Error(), "*/", "* /")) - } - - // TODO: maybe post-process output to prevent it from containing - // "<!--", "-->", "<![CDATA[", "]]>", or "</script" - // in case custom marshalers produce output containing those. - // Note: Do not use \x escaping to save bytes because it is not JSON compatible and this escaper - // supports ld+json content-type. - if len(b) == 0 { - // In, `x=y/{{.}}*z` a json.Marshaler that produces "" should - // not cause the output `x=y/*z`. - return " null " - } - first, _ := utf8.DecodeRune(b) - last, _ := utf8.DecodeLastRune(b) - var buf strings.Builder - // Prevent IdentifierNames and NumericLiterals from running into - // keywords: in, instanceof, typeof, void - pad := isJSIdentPart(first) || isJSIdentPart(last) - if pad { - buf.WriteByte(' ') - } - written := 0 - // Make sure that json.Marshal escapes codepoints U+2028 & U+2029 - // so it falls within the subset of JSON which is valid JS. - for i := 0; i < len(b); { - rune, n := utf8.DecodeRune(b[i:]) - repl := "" - if rune == 0x2028 { - repl = `\u2028` - } else if rune == 0x2029 { - repl = `\u2029` - } - if repl != "" { - buf.Write(b[written:i]) - buf.WriteString(repl) - written = i + n - } - i += n - } - if buf.Len() != 0 { - buf.Write(b[written:]) - if pad { - buf.WriteByte(' ') - } - return buf.String() - } - return string(b) -} - -// jsStrEscaper produces a string that can be included between quotes in -// JavaScript source, in JavaScript embedded in an HTML5 <script> element, -// or in an HTML5 event handler attribute such as onclick. -func jsStrEscaper(args ...interface{}) string { - s, t := stringify(args...) - if t == contentTypeJSStr { - return replace(s, jsStrNormReplacementTable) - } - return replace(s, jsStrReplacementTable) -} - -// jsRegexpEscaper behaves like jsStrEscaper but escapes regular expression -// specials so the result is treated literally when included in a regular -// expression literal. /foo{{.X}}bar/ matches the string "foo" followed by -// the literal text of {{.X}} followed by the string "bar". -func jsRegexpEscaper(args ...interface{}) string { - s, _ := stringify(args...) - s = replace(s, jsRegexpReplacementTable) - if s == "" { - // /{{.X}}/ should not produce a line comment when .X == "". - return "(?:)" - } - return s -} - -// replace replaces each rune r of s with replacementTable[r], provided that -// r < len(replacementTable). If replacementTable[r] is the empty string then -// no replacement is made. -// It also replaces runes U+2028 and U+2029 with the raw strings `\u2028` and -// `\u2029`. -func replace(s string, replacementTable []string) string { - var b strings.Builder - r, w, written := rune(0), 0, 0 - for i := 0; i < len(s); i += w { - // See comment in htmlEscaper. - r, w = utf8.DecodeRuneInString(s[i:]) - var repl string - switch { - case int(r) < len(lowUnicodeReplacementTable): - repl = lowUnicodeReplacementTable[r] - case int(r) < len(replacementTable) && replacementTable[r] != "": - repl = replacementTable[r] - case r == '\u2028': - repl = `\u2028` - case r == '\u2029': - repl = `\u2029` - default: - continue - } - if written == 0 { - b.Grow(len(s)) - } - b.WriteString(s[written:i]) - b.WriteString(repl) - written = i + w - } - if written == 0 { - return s - } - b.WriteString(s[written:]) - return b.String() -} - -var lowUnicodeReplacementTable = []string{ - 0: `\u0000`, 1: `\u0001`, 2: `\u0002`, 3: `\u0003`, 4: `\u0004`, 5: `\u0005`, 6: `\u0006`, - '\a': `\u0007`, - '\b': `\u0008`, - '\t': `\t`, - '\n': `\n`, - '\v': `\u000b`, // "\v" == "v" on IE 6. - '\f': `\f`, - '\r': `\r`, - 0xe: `\u000e`, 0xf: `\u000f`, 0x10: `\u0010`, 0x11: `\u0011`, 0x12: `\u0012`, 0x13: `\u0013`, - 0x14: `\u0014`, 0x15: `\u0015`, 0x16: `\u0016`, 0x17: `\u0017`, 0x18: `\u0018`, 0x19: `\u0019`, - 0x1a: `\u001a`, 0x1b: `\u001b`, 0x1c: `\u001c`, 0x1d: `\u001d`, 0x1e: `\u001e`, 0x1f: `\u001f`, -} - -var jsStrReplacementTable = []string{ - 0: `\u0000`, - '\t': `\t`, - '\n': `\n`, - '\v': `\u000b`, // "\v" == "v" on IE 6. - '\f': `\f`, - '\r': `\r`, - // Encode HTML specials as hex so the output can be embedded - // in HTML attributes without further encoding. - '"': `\u0022`, - '&': `\u0026`, - '\'': `\u0027`, - '+': `\u002b`, - '/': `\/`, - '<': `\u003c`, - '>': `\u003e`, - '\\': `\\`, -} - -// jsStrNormReplacementTable is like jsStrReplacementTable but does not -// overencode existing escapes since this table has no entry for `\`. -var jsStrNormReplacementTable = []string{ - 0: `\u0000`, - '\t': `\t`, - '\n': `\n`, - '\v': `\u000b`, // "\v" == "v" on IE 6. - '\f': `\f`, - '\r': `\r`, - // Encode HTML specials as hex so the output can be embedded - // in HTML attributes without further encoding. - '"': `\u0022`, - '&': `\u0026`, - '\'': `\u0027`, - '+': `\u002b`, - '/': `\/`, - '<': `\u003c`, - '>': `\u003e`, -} -var jsRegexpReplacementTable = []string{ - 0: `\u0000`, - '\t': `\t`, - '\n': `\n`, - '\v': `\u000b`, // "\v" == "v" on IE 6. - '\f': `\f`, - '\r': `\r`, - // Encode HTML specials as hex so the output can be embedded - // in HTML attributes without further encoding. - '"': `\u0022`, - '$': `\$`, - '&': `\u0026`, - '\'': `\u0027`, - '(': `\(`, - ')': `\)`, - '*': `\*`, - '+': `\u002b`, - '-': `\-`, - '.': `\.`, - '/': `\/`, - '<': `\u003c`, - '>': `\u003e`, - '?': `\?`, - '[': `\[`, - '\\': `\\`, - ']': `\]`, - '^': `\^`, - '{': `\{`, - '|': `\|`, - '}': `\}`, -} - -// isJSIdentPart reports whether the given rune is a JS identifier part. -// It does not handle all the non-Latin letters, joiners, and combining marks, -// but it does handle every codepoint that can occur in a numeric literal or -// a keyword. -func isJSIdentPart(r rune) bool { - switch { - case r == '$': - return true - case '0' <= r && r <= '9': - return true - case 'A' <= r && r <= 'Z': - return true - case r == '_': - return true - case 'a' <= r && r <= 'z': - return true - } - return false -} - -// isJSType reports whether the given MIME type should be considered JavaScript. -// -// It is used to determine whether a script tag with a type attribute is a javascript container. -func isJSType(mimeType string) bool { - // per - // https://www.w3.org/TR/html5/scripting-1.html#attr-script-type - // https://tools.ietf.org/html/rfc7231#section-3.1.1 - // https://tools.ietf.org/html/rfc4329#section-3 - // https://www.ietf.org/rfc/rfc4627.txt - // discard parameters - if i := strings.Index(mimeType, ";"); i >= 0 { - mimeType = mimeType[:i] - } - mimeType = strings.ToLower(mimeType) - mimeType = strings.TrimSpace(mimeType) - switch mimeType { - case - "application/ecmascript", - "application/javascript", - "application/json", - "application/ld+json", - "application/x-ecmascript", - "application/x-javascript", - "module", - "text/ecmascript", - "text/javascript", - "text/javascript1.0", - "text/javascript1.1", - "text/javascript1.2", - "text/javascript1.3", - "text/javascript1.4", - "text/javascript1.5", - "text/jscript", - "text/livescript", - "text/x-ecmascript", - "text/x-javascript": - return true - default: - return false - } -}
diff --git a/go.dev/cmd/internal/html/template/js_test.go b/go.dev/cmd/internal/html/template/js_test.go deleted file mode 100644 index d7ee47b..0000000 --- a/go.dev/cmd/internal/html/template/js_test.go +++ /dev/null
@@ -1,423 +0,0 @@ -// Copyright 2011 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package template - -import ( - "bytes" - "math" - "strings" - "testing" -) - -func TestNextJsCtx(t *testing.T) { - tests := []struct { - jsCtx jsCtx - s string - }{ - // Statement terminators precede regexps. - {jsCtxRegexp, ";"}, - // This is not airtight. - // ({ valueOf: function () { return 1 } } / 2) - // is valid JavaScript but in practice, devs do not do this. - // A block followed by a statement starting with a RegExp is - // much more common: - // while (x) {...} /foo/.test(x) || panic() - {jsCtxRegexp, "}"}, - // But member, call, grouping, and array expression terminators - // precede div ops. - {jsCtxDivOp, ")"}, - {jsCtxDivOp, "]"}, - // At the start of a primary expression, array, or expression - // statement, expect a regexp. - {jsCtxRegexp, "("}, - {jsCtxRegexp, "["}, - {jsCtxRegexp, "{"}, - // Assignment operators precede regexps as do all exclusively - // prefix and binary operators. - {jsCtxRegexp, "="}, - {jsCtxRegexp, "+="}, - {jsCtxRegexp, "*="}, - {jsCtxRegexp, "*"}, - {jsCtxRegexp, "!"}, - // Whether the + or - is infix or prefix, it cannot precede a - // div op. - {jsCtxRegexp, "+"}, - {jsCtxRegexp, "-"}, - // An incr/decr op precedes a div operator. - // This is not airtight. In (g = ++/h/i) a regexp follows a - // pre-increment operator, but in practice devs do not try to - // increment or decrement regular expressions. - // (g++/h/i) where ++ is a postfix operator on g is much more - // common. - {jsCtxDivOp, "--"}, - {jsCtxDivOp, "++"}, - {jsCtxDivOp, "x--"}, - // When we have many dashes or pluses, then they are grouped - // left to right. - {jsCtxRegexp, "x---"}, // A postfix -- then a -. - // return followed by a slash returns the regexp literal or the - // slash starts a regexp literal in an expression statement that - // is dead code. - {jsCtxRegexp, "return"}, - {jsCtxRegexp, "return "}, - {jsCtxRegexp, "return\t"}, - {jsCtxRegexp, "return\n"}, - {jsCtxRegexp, "return\u2028"}, - // Identifiers can be divided and cannot validly be preceded by - // a regular expressions. Semicolon insertion cannot happen - // between an identifier and a regular expression on a new line - // because the one token lookahead for semicolon insertion has - // to conclude that it could be a div binary op and treat it as - // such. - {jsCtxDivOp, "x"}, - {jsCtxDivOp, "x "}, - {jsCtxDivOp, "x\t"}, - {jsCtxDivOp, "x\n"}, - {jsCtxDivOp, "x\u2028"}, - {jsCtxDivOp, "preturn"}, - // Numbers precede div ops. - {jsCtxDivOp, "0"}, - // Dots that are part of a number are div preceders. - {jsCtxDivOp, "0."}, - } - - for _, test := range tests { - if nextJSCtx([]byte(test.s), jsCtxRegexp) != test.jsCtx { - t.Errorf("want %s got %q", test.jsCtx, test.s) - } - if nextJSCtx([]byte(test.s), jsCtxDivOp) != test.jsCtx { - t.Errorf("want %s got %q", test.jsCtx, test.s) - } - } - - if nextJSCtx([]byte(" "), jsCtxRegexp) != jsCtxRegexp { - t.Error("Blank tokens") - } - - if nextJSCtx([]byte(" "), jsCtxDivOp) != jsCtxDivOp { - t.Error("Blank tokens") - } -} - -func TestJSValEscaper(t *testing.T) { - tests := []struct { - x interface{} - js string - }{ - {int(42), " 42 "}, - {uint(42), " 42 "}, - {int16(42), " 42 "}, - {uint16(42), " 42 "}, - {int32(-42), " -42 "}, - {uint32(42), " 42 "}, - {int16(-42), " -42 "}, - {uint16(42), " 42 "}, - {int64(-42), " -42 "}, - {uint64(42), " 42 "}, - {uint64(1) << 53, " 9007199254740992 "}, - // ulp(1 << 53) > 1 so this loses precision in JS - // but it is still a representable integer literal. - {uint64(1)<<53 + 1, " 9007199254740993 "}, - {float32(1.0), " 1 "}, - {float32(-1.0), " -1 "}, - {float32(0.5), " 0.5 "}, - {float32(-0.5), " -0.5 "}, - {float32(1.0) / float32(256), " 0.00390625 "}, - {float32(0), " 0 "}, - {math.Copysign(0, -1), " -0 "}, - {float64(1.0), " 1 "}, - {float64(-1.0), " -1 "}, - {float64(0.5), " 0.5 "}, - {float64(-0.5), " -0.5 "}, - {float64(0), " 0 "}, - {math.Copysign(0, -1), " -0 "}, - {"", `""`}, - {"foo", `"foo"`}, - // Newlines. - {"\r\n\u2028\u2029", `"\r\n\u2028\u2029"`}, - // "\v" == "v" on IE 6 so use "\u000b" instead. - {"\t\x0b", `"\t\u000b"`}, - {struct{ X, Y int }{1, 2}, `{"X":1,"Y":2}`}, - {[]interface{}{}, "[]"}, - {[]interface{}{42, "foo", nil}, `[42,"foo",null]`}, - {[]string{"<!--", "</script>", "-->"}, `["\u003c!--","\u003c/script\u003e","--\u003e"]`}, - {"<!--", `"\u003c!--"`}, - {"-->", `"--\u003e"`}, - {"<![CDATA[", `"\u003c![CDATA["`}, - {"]]>", `"]]\u003e"`}, - {"</script", `"\u003c/script"`}, - {"\U0001D11E", "\"\U0001D11E\""}, // or "\uD834\uDD1E" - {nil, " null "}, - } - - for _, test := range tests { - if js := jsValEscaper(test.x); js != test.js { - t.Errorf("%+v: want\n\t%q\ngot\n\t%q", test.x, test.js, js) - } - // Make sure that escaping corner cases are not broken - // by nesting. - a := []interface{}{test.x} - want := "[" + strings.TrimSpace(test.js) + "]" - if js := jsValEscaper(a); js != want { - t.Errorf("%+v: want\n\t%q\ngot\n\t%q", a, want, js) - } - } -} - -func TestJSStrEscaper(t *testing.T) { - tests := []struct { - x interface{} - esc string - }{ - {"", ``}, - {"foo", `foo`}, - {"\u0000", `\u0000`}, - {"\t", `\t`}, - {"\n", `\n`}, - {"\r", `\r`}, - {"\u2028", `\u2028`}, - {"\u2029", `\u2029`}, - {"\\", `\\`}, - {"\\n", `\\n`}, - {"foo\r\nbar", `foo\r\nbar`}, - // Preserve attribute boundaries. - {`"`, `\u0022`}, - {`'`, `\u0027`}, - // Allow embedding in HTML without further escaping. - {`&`, `\u0026amp;`}, - // Prevent breaking out of text node and element boundaries. - {"</script>", `\u003c\/script\u003e`}, - {"<![CDATA[", `\u003c![CDATA[`}, - {"]]>", `]]\u003e`}, - // https://dev.w3.org/html5/markup/aria/syntax.html#escaping-text-span - // "The text in style, script, title, and textarea elements - // must not have an escaping text span start that is not - // followed by an escaping text span end." - // Furthermore, spoofing an escaping text span end could lead - // to different interpretation of a </script> sequence otherwise - // masked by the escaping text span, and spoofing a start could - // allow regular text content to be interpreted as script - // allowing script execution via a combination of a JS string - // injection followed by an HTML text injection. - {"<!--", `\u003c!--`}, - {"-->", `--\u003e`}, - // From https://code.google.com/p/doctype/wiki/ArticleUtf7 - {"+ADw-script+AD4-alert(1)+ADw-/script+AD4-", - `\u002bADw-script\u002bAD4-alert(1)\u002bADw-\/script\u002bAD4-`, - }, - // Invalid UTF-8 sequence - {"foo\xA0bar", "foo\xA0bar"}, - // Invalid unicode scalar value. - {"foo\xed\xa0\x80bar", "foo\xed\xa0\x80bar"}, - } - - for _, test := range tests { - esc := jsStrEscaper(test.x) - if esc != test.esc { - t.Errorf("%q: want %q got %q", test.x, test.esc, esc) - } - } -} - -func TestJSRegexpEscaper(t *testing.T) { - tests := []struct { - x interface{} - esc string - }{ - {"", `(?:)`}, - {"foo", `foo`}, - {"\u0000", `\u0000`}, - {"\t", `\t`}, - {"\n", `\n`}, - {"\r", `\r`}, - {"\u2028", `\u2028`}, - {"\u2029", `\u2029`}, - {"\\", `\\`}, - {"\\n", `\\n`}, - {"foo\r\nbar", `foo\r\nbar`}, - // Preserve attribute boundaries. - {`"`, `\u0022`}, - {`'`, `\u0027`}, - // Allow embedding in HTML without further escaping. - {`&`, `\u0026amp;`}, - // Prevent breaking out of text node and element boundaries. - {"</script>", `\u003c\/script\u003e`}, - {"<![CDATA[", `\u003c!\[CDATA\[`}, - {"]]>", `\]\]\u003e`}, - // Escaping text spans. - {"<!--", `\u003c!\-\-`}, - {"-->", `\-\-\u003e`}, - {"*", `\*`}, - {"+", `\u002b`}, - {"?", `\?`}, - {"[](){}", `\[\]\(\)\{\}`}, - {"$foo|x.y", `\$foo\|x\.y`}, - {"x^y", `x\^y`}, - } - - for _, test := range tests { - esc := jsRegexpEscaper(test.x) - if esc != test.esc { - t.Errorf("%q: want %q got %q", test.x, test.esc, esc) - } - } -} - -func TestEscapersOnLower7AndSelectHighCodepoints(t *testing.T) { - input := ("\x00\x01\x02\x03\x04\x05\x06\x07\x08\t\n\x0b\x0c\r\x0e\x0f" + - "\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f" + - ` !"#$%&'()*+,-./` + - `0123456789:;<=>?` + - `@ABCDEFGHIJKLMNO` + - `PQRSTUVWXYZ[\]^_` + - "`abcdefghijklmno" + - "pqrstuvwxyz{|}~\x7f" + - "\u00A0\u0100\u2028\u2029\ufeff\U0001D11E") - - tests := []struct { - name string - escaper func(...interface{}) string - escaped string - }{ - { - "jsStrEscaper", - jsStrEscaper, - `\u0000\u0001\u0002\u0003\u0004\u0005\u0006\u0007` + - `\u0008\t\n\u000b\f\r\u000e\u000f` + - `\u0010\u0011\u0012\u0013\u0014\u0015\u0016\u0017` + - `\u0018\u0019\u001a\u001b\u001c\u001d\u001e\u001f` + - ` !\u0022#$%\u0026\u0027()*\u002b,-.\/` + - `0123456789:;\u003c=\u003e?` + - `@ABCDEFGHIJKLMNO` + - `PQRSTUVWXYZ[\\]^_` + - "`abcdefghijklmno" + - "pqrstuvwxyz{|}~\u007f" + - "\u00A0\u0100\\u2028\\u2029\ufeff\U0001D11E", - }, - { - "jsRegexpEscaper", - jsRegexpEscaper, - `\u0000\u0001\u0002\u0003\u0004\u0005\u0006\u0007` + - `\u0008\t\n\u000b\f\r\u000e\u000f` + - `\u0010\u0011\u0012\u0013\u0014\u0015\u0016\u0017` + - `\u0018\u0019\u001a\u001b\u001c\u001d\u001e\u001f` + - ` !\u0022#\$%\u0026\u0027\(\)\*\u002b,\-\.\/` + - `0123456789:;\u003c=\u003e\?` + - `@ABCDEFGHIJKLMNO` + - `PQRSTUVWXYZ\[\\\]\^_` + - "`abcdefghijklmno" + - `pqrstuvwxyz\{\|\}~` + "\u007f" + - "\u00A0\u0100\\u2028\\u2029\ufeff\U0001D11E", - }, - } - - for _, test := range tests { - if s := test.escaper(input); s != test.escaped { - t.Errorf("%s once: want\n\t%q\ngot\n\t%q", test.name, test.escaped, s) - continue - } - - // Escape it rune by rune to make sure that any - // fast-path checking does not break escaping. - var buf bytes.Buffer - for _, c := range input { - buf.WriteString(test.escaper(string(c))) - } - - if s := buf.String(); s != test.escaped { - t.Errorf("%s rune-wise: want\n\t%q\ngot\n\t%q", test.name, test.escaped, s) - continue - } - } -} - -func TestIsJsMimeType(t *testing.T) { - tests := []struct { - in string - out bool - }{ - {"application/javascript;version=1.8", true}, - {"application/javascript;version=1.8;foo=bar", true}, - {"application/javascript/version=1.8", false}, - {"text/javascript", true}, - {"application/json", true}, - {"application/ld+json", true}, - {"module", true}, - } - - for _, test := range tests { - if isJSType(test.in) != test.out { - t.Errorf("isJSType(%q) = %v, want %v", test.in, !test.out, test.out) - } - } -} - -func BenchmarkJSValEscaperWithNum(b *testing.B) { - for i := 0; i < b.N; i++ { - jsValEscaper(3.141592654) - } -} - -func BenchmarkJSValEscaperWithStr(b *testing.B) { - for i := 0; i < b.N; i++ { - jsValEscaper("The <i>quick</i>,\r\n<span style='color:brown'>brown</span> fox jumps\u2028over the <canine class=\"lazy\">dog</canine>") - } -} - -func BenchmarkJSValEscaperWithStrNoSpecials(b *testing.B) { - for i := 0; i < b.N; i++ { - jsValEscaper("The quick, brown fox jumps over the lazy dog") - } -} - -func BenchmarkJSValEscaperWithObj(b *testing.B) { - o := struct { - S string - N int - }{ - "The <i>quick</i>,\r\n<span style='color:brown'>brown</span> fox jumps\u2028over the <canine class=\"lazy\">dog</canine>\u2028", - 42, - } - for i := 0; i < b.N; i++ { - jsValEscaper(o) - } -} - -func BenchmarkJSValEscaperWithObjNoSpecials(b *testing.B) { - o := struct { - S string - N int - }{ - "The quick, brown fox jumps over the lazy dog", - 42, - } - for i := 0; i < b.N; i++ { - jsValEscaper(o) - } -} - -func BenchmarkJSStrEscaperNoSpecials(b *testing.B) { - for i := 0; i < b.N; i++ { - jsStrEscaper("The quick, brown fox jumps over the lazy dog.") - } -} - -func BenchmarkJSStrEscaper(b *testing.B) { - for i := 0; i < b.N; i++ { - jsStrEscaper("The <i>quick</i>,\r\n<span style='color:brown'>brown</span> fox jumps\u2028over the <canine class=\"lazy\">dog</canine>") - } -} - -func BenchmarkJSRegexpEscaperNoSpecials(b *testing.B) { - for i := 0; i < b.N; i++ { - jsRegexpEscaper("The quick, brown fox jumps over the lazy dog") - } -} - -func BenchmarkJSRegexpEscaper(b *testing.B) { - for i := 0; i < b.N; i++ { - jsRegexpEscaper("The <i>quick</i>,\r\n<span style='color:brown'>brown</span> fox jumps\u2028over the <canine class=\"lazy\">dog</canine>") - } -}
diff --git a/go.dev/cmd/internal/html/template/jsctx_string.go b/go.dev/cmd/internal/html/template/jsctx_string.go deleted file mode 100644 index dd1d87e..0000000 --- a/go.dev/cmd/internal/html/template/jsctx_string.go +++ /dev/null
@@ -1,16 +0,0 @@ -// Code generated by "stringer -type jsCtx"; DO NOT EDIT. - -package template - -import "strconv" - -const _jsCtx_name = "jsCtxRegexpjsCtxDivOpjsCtxUnknown" - -var _jsCtx_index = [...]uint8{0, 11, 21, 33} - -func (i jsCtx) String() string { - if i >= jsCtx(len(_jsCtx_index)-1) { - return "jsCtx(" + strconv.FormatInt(int64(i), 10) + ")" - } - return _jsCtx_name[_jsCtx_index[i]:_jsCtx_index[i+1]] -}
diff --git a/go.dev/cmd/internal/html/template/multi_test.go b/go.dev/cmd/internal/html/template/multi_test.go deleted file mode 100644 index 30247ce..0000000 --- a/go.dev/cmd/internal/html/template/multi_test.go +++ /dev/null
@@ -1,247 +0,0 @@ -// Copyright 2011 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -// Tests for multiple-template execution, copied from text/template. - -package template - -import ( - "bytes" - "testing" - - "golang.org/x/website/go.dev/cmd/internal/text/template/parse" -) - -var multiExecTests = []execTest{ - {"empty", "", "", nil, true}, - {"text", "some text", "some text", nil, true}, - {"invoke x", `{{template "x" .SI}}`, "TEXT", tVal, true}, - {"invoke x no args", `{{template "x"}}`, "TEXT", tVal, true}, - {"invoke dot int", `{{template "dot" .I}}`, "17", tVal, true}, - {"invoke dot []int", `{{template "dot" .SI}}`, "[3 4 5]", tVal, true}, - {"invoke dotV", `{{template "dotV" .U}}`, "v", tVal, true}, - {"invoke nested int", `{{template "nested" .I}}`, "17", tVal, true}, - {"variable declared by template", `{{template "nested" $x:=.SI}},{{index $x 1}}`, "[3 4 5],4", tVal, true}, - - // User-defined function: test argument evaluator. - {"testFunc literal", `{{oneArg "joe"}}`, "oneArg=joe", tVal, true}, - {"testFunc .", `{{oneArg .}}`, "oneArg=joe", "joe", true}, -} - -// These strings are also in testdata/*. -const multiText1 = ` - {{define "x"}}TEXT{{end}} - {{define "dotV"}}{{.V}}{{end}} -` - -const multiText2 = ` - {{define "dot"}}{{.}}{{end}} - {{define "nested"}}{{template "dot" .}}{{end}} -` - -func TestMultiExecute(t *testing.T) { - // Declare a couple of templates first. - template, err := New("root").Parse(multiText1) - if err != nil { - t.Fatalf("parse error for 1: %s", err) - } - _, err = template.Parse(multiText2) - if err != nil { - t.Fatalf("parse error for 2: %s", err) - } - testExecute(multiExecTests, template, t) -} - -func TestParseFiles(t *testing.T) { - _, err := ParseFiles("DOES NOT EXIST") - if err == nil { - t.Error("expected error for non-existent file; got none") - } - template := New("root") - _, err = template.ParseFiles("testdata/file1.tmpl", "testdata/file2.tmpl") - if err != nil { - t.Fatalf("error parsing files: %v", err) - } - testExecute(multiExecTests, template, t) -} - -func TestParseGlob(t *testing.T) { - _, err := ParseGlob("DOES NOT EXIST") - if err == nil { - t.Error("expected error for non-existent file; got none") - } - _, err = New("error").ParseGlob("[x") - if err == nil { - t.Error("expected error for bad pattern; got none") - } - template := New("root") - _, err = template.ParseGlob("testdata/file*.tmpl") - if err != nil { - t.Fatalf("error parsing files: %v", err) - } - testExecute(multiExecTests, template, t) -} - -// In these tests, actual content (not just template definitions) comes from the parsed files. - -var templateFileExecTests = []execTest{ - {"test", `{{template "tmpl1.tmpl"}}{{template "tmpl2.tmpl"}}`, "template1\n\ny\ntemplate2\n\nx\n", 0, true}, -} - -func TestParseFilesWithData(t *testing.T) { - template, err := New("root").ParseFiles("testdata/tmpl1.tmpl", "testdata/tmpl2.tmpl") - if err != nil { - t.Fatalf("error parsing files: %v", err) - } - testExecute(templateFileExecTests, template, t) -} - -func TestParseGlobWithData(t *testing.T) { - template, err := New("root").ParseGlob("testdata/tmpl*.tmpl") - if err != nil { - t.Fatalf("error parsing files: %v", err) - } - testExecute(templateFileExecTests, template, t) -} - -const ( - cloneText1 = `{{define "a"}}{{template "b"}}{{template "c"}}{{end}}` - cloneText2 = `{{define "b"}}b{{end}}` - cloneText3 = `{{define "c"}}root{{end}}` - cloneText4 = `{{define "c"}}clone{{end}}` -) - -// Issue 7032 -func TestAddParseTreeToUnparsedTemplate(t *testing.T) { - master := "{{define \"master\"}}{{end}}" - tmpl := New("master") - tree, err := parse.Parse("master", master, "", "", nil) - if err != nil { - t.Fatalf("unexpected parse err: %v", err) - } - masterTree := tree["master"] - tmpl.AddParseTree("master", masterTree) // used to panic -} - -func TestRedefinition(t *testing.T) { - var tmpl *Template - var err error - if tmpl, err = New("tmpl1").Parse(`{{define "test"}}foo{{end}}`); err != nil { - t.Fatalf("parse 1: %v", err) - } - if _, err = tmpl.Parse(`{{define "test"}}bar{{end}}`); err != nil { - t.Fatalf("got error %v, expected nil", err) - } - if _, err = tmpl.New("tmpl2").Parse(`{{define "test"}}bar{{end}}`); err != nil { - t.Fatalf("got error %v, expected nil", err) - } -} - -// Issue 10879 -func TestEmptyTemplateCloneCrash(t *testing.T) { - t1 := New("base") - t1.Clone() // used to panic -} - -// Issue 10910, 10926 -func TestTemplateLookUp(t *testing.T) { - t.Skip("broken on html/template") // TODO - t1 := New("foo") - if t1.Lookup("foo") != nil { - t.Error("Lookup returned non-nil value for undefined template foo") - } - t1.New("bar") - if t1.Lookup("bar") != nil { - t.Error("Lookup returned non-nil value for undefined template bar") - } - t1.Parse(`{{define "foo"}}test{{end}}`) - if t1.Lookup("foo") == nil { - t.Error("Lookup returned nil value for defined template") - } -} - -func TestParse(t *testing.T) { - // In multiple calls to Parse with the same receiver template, only one call - // can contain text other than space, comments, and template definitions - t1 := New("test") - if _, err := t1.Parse(`{{define "test"}}{{end}}`); err != nil { - t.Fatalf("parsing test: %s", err) - } - if _, err := t1.Parse(`{{define "test"}}{{/* this is a comment */}}{{end}}`); err != nil { - t.Fatalf("parsing test: %s", err) - } - if _, err := t1.Parse(`{{define "test"}}foo{{end}}`); err != nil { - t.Fatalf("parsing test: %s", err) - } -} - -func TestEmptyTemplate(t *testing.T) { - cases := []struct { - defn []string - in string - want string - }{ - {[]string{"x", "y"}, "", "y"}, - {[]string{""}, "once", ""}, - {[]string{"", ""}, "twice", ""}, - {[]string{"{{.}}", "{{.}}"}, "twice", "twice"}, - {[]string{"{{/* a comment */}}", "{{/* a comment */}}"}, "comment", ""}, - {[]string{"{{.}}", ""}, "twice", "twice"}, // TODO: should want "" not "twice" - } - - for i, c := range cases { - root := New("root") - - var ( - m *Template - err error - ) - for _, d := range c.defn { - m, err = root.New(c.in).Parse(d) - if err != nil { - t.Fatal(err) - } - } - buf := &bytes.Buffer{} - if err := m.Execute(buf, c.in); err != nil { - t.Error(i, err) - continue - } - if buf.String() != c.want { - t.Errorf("expected string %q: got %q", c.want, buf.String()) - } - } -} - -// Issue 19249 was a regression in 1.8 caused by the handling of empty -// templates added in that release, which got different answers depending -// on the order templates appeared in the internal map. -func TestIssue19294(t *testing.T) { - // The empty block in "xhtml" should be replaced during execution - // by the contents of "stylesheet", but if the internal map associating - // names with templates is built in the wrong order, the empty block - // looks non-empty and this doesn't happen. - var inlined = map[string]string{ - "stylesheet": `{{define "stylesheet"}}stylesheet{{end}}`, - "xhtml": `{{block "stylesheet" .}}{{end}}`, - } - all := []string{"stylesheet", "xhtml"} - for i := 0; i < 100; i++ { - res, err := New("title.xhtml").Parse(`{{template "xhtml" .}}`) - if err != nil { - t.Fatal(err) - } - for _, name := range all { - _, err := res.New(name).Parse(inlined[name]) - if err != nil { - t.Fatal(err) - } - } - var buf bytes.Buffer - res.Execute(&buf, 0) - if buf.String() != "stylesheet" { - t.Fatalf("iteration %d: got %q; expected %q", i, buf.String(), "stylesheet") - } - } -}
diff --git a/go.dev/cmd/internal/html/template/state_string.go b/go.dev/cmd/internal/html/template/state_string.go deleted file mode 100644 index 05104be..0000000 --- a/go.dev/cmd/internal/html/template/state_string.go +++ /dev/null
@@ -1,16 +0,0 @@ -// Code generated by "stringer -type state"; DO NOT EDIT. - -package template - -import "strconv" - -const _state_name = "stateTextstateTagstateAttrNamestateAfterNamestateBeforeValuestateHTMLCmtstateRCDATAstateAttrstateURLstateSrcsetstateJSstateJSDqStrstateJSSqStrstateJSRegexpstateJSBlockCmtstateJSLineCmtstateCSSstateCSSDqStrstateCSSSqStrstateCSSDqURLstateCSSSqURLstateCSSURLstateCSSBlockCmtstateCSSLineCmtstateError" - -var _state_index = [...]uint16{0, 9, 17, 30, 44, 60, 72, 83, 92, 100, 111, 118, 130, 142, 155, 170, 184, 192, 205, 218, 231, 244, 255, 271, 286, 296} - -func (i state) String() string { - if i >= state(len(_state_index)-1) { - return "state(" + strconv.FormatInt(int64(i), 10) + ")" - } - return _state_name[_state_index[i]:_state_index[i+1]] -}
diff --git a/go.dev/cmd/internal/html/template/template.go b/go.dev/cmd/internal/html/template/template.go deleted file mode 100644 index 1de4bd5..0000000 --- a/go.dev/cmd/internal/html/template/template.go +++ /dev/null
@@ -1,496 +0,0 @@ -// Copyright 2011 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package template - -import ( - "fmt" - "io" - "io/ioutil" - "path/filepath" - "sync" - - "golang.org/x/website/go.dev/cmd/internal/text/template" - "golang.org/x/website/go.dev/cmd/internal/text/template/parse" -) - -// Template is a specialized Template from "golang.org/x/website/go.dev/cmd/internal/text/template" that produces a safe -// HTML document fragment. -type Template struct { - // Sticky error if escaping fails, or escapeOK if succeeded. - escapeErr error - // We could embed the text/template field, but it's safer not to because - // we need to keep our version of the name space and the underlying - // template's in sync. - text *template.Template - // The underlying template's parse tree, updated to be HTML-safe. - Tree *parse.Tree - *nameSpace // common to all associated templates -} - -// escapeOK is a sentinel value used to indicate valid escaping. -var escapeOK = fmt.Errorf("template escaped correctly") - -// nameSpace is the data structure shared by all templates in an association. -type nameSpace struct { - mu sync.Mutex - set map[string]*Template - escaped bool - esc escaper -} - -// Templates returns a slice of the templates associated with t, including t -// itself. -func (t *Template) Templates() []*Template { - ns := t.nameSpace - ns.mu.Lock() - defer ns.mu.Unlock() - // Return a slice so we don't expose the map. - m := make([]*Template, 0, len(ns.set)) - for _, v := range ns.set { - m = append(m, v) - } - return m -} - -// Option sets options for the template. Options are described by -// strings, either a simple string or "key=value". There can be at -// most one equals sign in an option string. If the option string -// is unrecognized or otherwise invalid, Option panics. -// -// Known options: -// -// missingkey: Control the behavior during execution if a map is -// indexed with a key that is not present in the map. -// "missingkey=default" or "missingkey=invalid" -// The default behavior: Do nothing and continue execution. -// If printed, the result of the index operation is the string -// "<no value>". -// "missingkey=zero" -// The operation returns the zero value for the map type's element. -// "missingkey=error" -// Execution stops immediately with an error. -// -func (t *Template) Option(opt ...string) *Template { - t.text.Option(opt...) - return t -} - -// checkCanParse checks whether it is OK to parse templates. -// If not, it returns an error. -func (t *Template) checkCanParse() error { - if t == nil { - return nil - } - t.nameSpace.mu.Lock() - defer t.nameSpace.mu.Unlock() - if t.nameSpace.escaped { - return fmt.Errorf("golang.org/x/website/go.dev/cmd/internal/html/template: cannot Parse after Execute") - } - return nil -} - -// escape escapes all associated templates. -func (t *Template) escape() error { - t.nameSpace.mu.Lock() - defer t.nameSpace.mu.Unlock() - t.nameSpace.escaped = true - if t.escapeErr == nil { - if t.Tree == nil { - return fmt.Errorf("template: %q is an incomplete or empty template", t.Name()) - } - if err := escapeTemplate(t, t.text.Root, t.Name()); err != nil { - return err - } - } else if t.escapeErr != escapeOK { - return t.escapeErr - } - return nil -} - -// Execute applies a parsed template to the specified data object, -// writing the output to wr. -// If an error occurs executing the template or writing its output, -// execution stops, but partial results may already have been written to -// the output writer. -// A template may be executed safely in parallel, although if parallel -// executions share a Writer the output may be interleaved. -func (t *Template) Execute(wr io.Writer, data interface{}) error { - if err := t.escape(); err != nil { - return err - } - return t.text.Execute(wr, data) -} - -// ExecuteTemplate applies the template associated with t that has the given -// name to the specified data object and writes the output to wr. -// If an error occurs executing the template or writing its output, -// execution stops, but partial results may already have been written to -// the output writer. -// A template may be executed safely in parallel, although if parallel -// executions share a Writer the output may be interleaved. -func (t *Template) ExecuteTemplate(wr io.Writer, name string, data interface{}) error { - tmpl, err := t.lookupAndEscapeTemplate(name) - if err != nil { - return err - } - return tmpl.text.Execute(wr, data) -} - -// lookupAndEscapeTemplate guarantees that the template with the given name -// is escaped, or returns an error if it cannot be. It returns the named -// template. -func (t *Template) lookupAndEscapeTemplate(name string) (tmpl *Template, err error) { - t.nameSpace.mu.Lock() - defer t.nameSpace.mu.Unlock() - t.nameSpace.escaped = true - tmpl = t.set[name] - if tmpl == nil { - return nil, fmt.Errorf("golang.org/x/website/go.dev/cmd/internal/html/template: %q is undefined", name) - } - if tmpl.escapeErr != nil && tmpl.escapeErr != escapeOK { - return nil, tmpl.escapeErr - } - if tmpl.text.Tree == nil || tmpl.text.Root == nil { - return nil, fmt.Errorf("golang.org/x/website/go.dev/cmd/internal/html/template: %q is an incomplete template", name) - } - if t.text.Lookup(name) == nil { - panic("golang.org/x/website/go.dev/cmd/internal/html/template internal error: template escaping out of sync") - } - if tmpl.escapeErr == nil { - err = escapeTemplate(tmpl, tmpl.text.Root, name) - } - return tmpl, err -} - -// DefinedTemplates returns a string listing the defined templates, -// prefixed by the string "; defined templates are: ". If there are none, -// it returns the empty string. Used to generate an error message. -func (t *Template) DefinedTemplates() string { - return t.text.DefinedTemplates() -} - -// Parse parses text as a template body for t. -// Named template definitions ({{define ...}} or {{block ...}} statements) in text -// define additional templates associated with t and are removed from the -// definition of t itself. -// -// Templates can be redefined in successive calls to Parse, -// before the first use of Execute on t or any associated template. -// A template definition with a body containing only white space and comments -// is considered empty and will not replace an existing template's body. -// This allows using Parse to add new named template definitions without -// overwriting the main template body. -func (t *Template) Parse(text string) (*Template, error) { - if err := t.checkCanParse(); err != nil { - return nil, err - } - - ret, err := t.text.Parse(text) - if err != nil { - return nil, err - } - - // In general, all the named templates might have changed underfoot. - // Regardless, some new ones may have been defined. - // The template.Template set has been updated; update ours. - t.nameSpace.mu.Lock() - defer t.nameSpace.mu.Unlock() - for _, v := range ret.Templates() { - name := v.Name() - tmpl := t.set[name] - if tmpl == nil { - tmpl = t.new(name) - } - tmpl.text = v - tmpl.Tree = v.Tree - } - return t, nil -} - -// AddParseTree creates a new template with the name and parse tree -// and associates it with t. -// -// It returns an error if t or any associated template has already been executed. -func (t *Template) AddParseTree(name string, tree *parse.Tree) (*Template, error) { - if err := t.checkCanParse(); err != nil { - return nil, err - } - - t.nameSpace.mu.Lock() - defer t.nameSpace.mu.Unlock() - text, err := t.text.AddParseTree(name, tree) - if err != nil { - return nil, err - } - ret := &Template{ - nil, - text, - text.Tree, - t.nameSpace, - } - t.set[name] = ret - return ret, nil -} - -// Clone returns a duplicate of the template, including all associated -// templates. The actual representation is not copied, but the name space of -// associated templates is, so further calls to Parse in the copy will add -// templates to the copy but not to the original. Clone can be used to prepare -// common templates and use them with variant definitions for other templates -// by adding the variants after the clone is made. -// -// It returns an error if t has already been executed. -func (t *Template) Clone() (*Template, error) { - t.nameSpace.mu.Lock() - defer t.nameSpace.mu.Unlock() - if t.escapeErr != nil { - return nil, fmt.Errorf("golang.org/x/website/go.dev/cmd/internal/html/template: cannot Clone %q after it has executed", t.Name()) - } - textClone, err := t.text.Clone() - if err != nil { - return nil, err - } - ns := &nameSpace{set: make(map[string]*Template)} - ns.esc = makeEscaper(ns) - ret := &Template{ - nil, - textClone, - textClone.Tree, - ns, - } - ret.set[ret.Name()] = ret - for _, x := range textClone.Templates() { - name := x.Name() - src := t.set[name] - if src == nil || src.escapeErr != nil { - return nil, fmt.Errorf("golang.org/x/website/go.dev/cmd/internal/html/template: cannot Clone %q after it has executed", t.Name()) - } - x.Tree = x.Tree.Copy() - ret.set[name] = &Template{ - nil, - x, - x.Tree, - ret.nameSpace, - } - } - // Return the template associated with the name of this template. - return ret.set[ret.Name()], nil -} - -// New allocates a new HTML template with the given name. -func New(name string) *Template { - ns := &nameSpace{set: make(map[string]*Template)} - ns.esc = makeEscaper(ns) - tmpl := &Template{ - nil, - template.New(name), - nil, - ns, - } - tmpl.set[name] = tmpl - return tmpl -} - -// New allocates a new HTML template associated with the given one -// and with the same delimiters. The association, which is transitive, -// allows one template to invoke another with a {{template}} action. -// -// If a template with the given name already exists, the new HTML template -// will replace it. The existing template will be reset and disassociated with -// t. -func (t *Template) New(name string) *Template { - t.nameSpace.mu.Lock() - defer t.nameSpace.mu.Unlock() - return t.new(name) -} - -// new is the implementation of New, without the lock. -func (t *Template) new(name string) *Template { - tmpl := &Template{ - nil, - t.text.New(name), - nil, - t.nameSpace, - } - if existing, ok := tmpl.set[name]; ok { - emptyTmpl := New(existing.Name()) - *existing = *emptyTmpl - } - tmpl.set[name] = tmpl - return tmpl -} - -// Name returns the name of the template. -func (t *Template) Name() string { - return t.text.Name() -} - -// FuncMap is the type of the map defining the mapping from names to -// functions. Each function must have either a single return value, or two -// return values of which the second has type error. In that case, if the -// second (error) argument evaluates to non-nil during execution, execution -// terminates and Execute returns that error. FuncMap has the same base type -// as FuncMap in "golang.org/x/website/go.dev/cmd/internal/text/template", copied here so clients need not import -// "golang.org/x/website/go.dev/cmd/internal/text/template". -type FuncMap map[string]interface{} - -// Funcs adds the elements of the argument map to the template's function map. -// It must be called before the template is parsed. -// It panics if a value in the map is not a function with appropriate return -// type. However, it is legal to overwrite elements of the map. The return -// value is the template, so calls can be chained. -func (t *Template) Funcs(funcMap FuncMap) *Template { - t.text.Funcs(template.FuncMap(funcMap)) - return t -} - -// Delims sets the action delimiters to the specified strings, to be used in -// subsequent calls to Parse, ParseFiles, or ParseGlob. Nested template -// definitions will inherit the settings. An empty delimiter stands for the -// corresponding default: {{ or }}. -// The return value is the template, so calls can be chained. -func (t *Template) Delims(left, right string) *Template { - t.text.Delims(left, right) - return t -} - -// Lookup returns the template with the given name that is associated with t, -// or nil if there is no such template. -func (t *Template) Lookup(name string) *Template { - t.nameSpace.mu.Lock() - defer t.nameSpace.mu.Unlock() - return t.set[name] -} - -// Must is a helper that wraps a call to a function returning (*Template, error) -// and panics if the error is non-nil. It is intended for use in variable initializations -// such as -// var t = template.Must(template.New("name").Parse("html")) -func Must(t *Template, err error) *Template { - if err != nil { - panic(err) - } - return t -} - -// ParseFiles creates a new Template and parses the template definitions from -// the named files. The returned template's name will have the (base) name and -// (parsed) contents of the first file. There must be at least one file. -// If an error occurs, parsing stops and the returned *Template is nil. -// -// When parsing multiple files with the same name in different directories, -// the last one mentioned will be the one that results. -// For instance, ParseFiles("a/foo", "b/foo") stores "b/foo" as the template -// named "foo", while "a/foo" is unavailable. -func ParseFiles(filenames ...string) (*Template, error) { - return parseFiles(nil, readFileOS, filenames...) -} - -// ParseFiles parses the named files and associates the resulting templates with -// t. If an error occurs, parsing stops and the returned template is nil; -// otherwise it is t. There must be at least one file. -// -// When parsing multiple files with the same name in different directories, -// the last one mentioned will be the one that results. -// -// ParseFiles returns an error if t or any associated template has already been executed. -func (t *Template) ParseFiles(filenames ...string) (*Template, error) { - return parseFiles(t, readFileOS, filenames...) -} - -// parseFiles is the helper for the method and function. If the argument -// template is nil, it is created from the first file. -func parseFiles(t *Template, readFile func(string) (string, []byte, error), filenames ...string) (*Template, error) { - if err := t.checkCanParse(); err != nil { - return nil, err - } - - if len(filenames) == 0 { - // Not really a problem, but be consistent. - return nil, fmt.Errorf("golang.org/x/website/go.dev/cmd/internal/html/template: no files named in call to ParseFiles") - } - for _, filename := range filenames { - name, b, err := readFile(filename) - if err != nil { - return nil, err - } - s := string(b) - // First template becomes return value if not already defined, - // and we use that one for subsequent New calls to associate - // all the templates together. Also, if this file has the same name - // as t, this file becomes the contents of t, so - // t, err := New(name).Funcs(xxx).ParseFiles(name) - // works. Otherwise we create a new template associated with t. - var tmpl *Template - if t == nil { - t = New(name) - } - if name == t.Name() { - tmpl = t - } else { - tmpl = t.New(name) - } - _, err = tmpl.Parse(s) - if err != nil { - return nil, err - } - } - return t, nil -} - -// ParseGlob creates a new Template and parses the template definitions from -// the files identified by the pattern. The files are matched according to the -// semantics of filepath.Match, and the pattern must match at least one file. -// The returned template will have the (base) name and (parsed) contents of the -// first file matched by the pattern. ParseGlob is equivalent to calling -// ParseFiles with the list of files matched by the pattern. -// -// When parsing multiple files with the same name in different directories, -// the last one mentioned will be the one that results. -func ParseGlob(pattern string) (*Template, error) { - return parseGlob(nil, pattern) -} - -// ParseGlob parses the template definitions in the files identified by the -// pattern and associates the resulting templates with t. The files are matched -// according to the semantics of filepath.Match, and the pattern must match at -// least one file. ParseGlob is equivalent to calling t.ParseFiles with the -// list of files matched by the pattern. -// -// When parsing multiple files with the same name in different directories, -// the last one mentioned will be the one that results. -// -// ParseGlob returns an error if t or any associated template has already been executed. -func (t *Template) ParseGlob(pattern string) (*Template, error) { - return parseGlob(t, pattern) -} - -// parseGlob is the implementation of the function and method ParseGlob. -func parseGlob(t *Template, pattern string) (*Template, error) { - if err := t.checkCanParse(); err != nil { - return nil, err - } - filenames, err := filepath.Glob(pattern) - if err != nil { - return nil, err - } - if len(filenames) == 0 { - return nil, fmt.Errorf("golang.org/x/website/go.dev/cmd/internal/html/template: pattern matches no files: %#q", pattern) - } - return parseFiles(t, readFileOS, filenames...) -} - -// IsTrue reports whether the value is 'true', in the sense of not the zero of its type, -// and whether the value has a meaningful truth value. This is the definition of -// truth used by if and other such actions. -func IsTrue(val interface{}) (truth, ok bool) { - return template.IsTrue(val) -} - -func readFileOS(file string) (name string, b []byte, err error) { - name = filepath.Base(file) - b, err = ioutil.ReadFile(file) - return -}
diff --git a/go.dev/cmd/internal/html/template/template_test.go b/go.dev/cmd/internal/html/template/template_test.go deleted file mode 100644 index c04c43e..0000000 --- a/go.dev/cmd/internal/html/template/template_test.go +++ /dev/null
@@ -1,219 +0,0 @@ -// Copyright 2016 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package template_test - -import ( - "bytes" - "encoding/json" - "strings" - "testing" - - . "golang.org/x/website/go.dev/cmd/internal/html/template" - "golang.org/x/website/go.dev/cmd/internal/text/template/parse" -) - -func TestTemplateClone(t *testing.T) { - // https://golang.org/issue/12996 - orig := New("name") - clone, err := orig.Clone() - if err != nil { - t.Fatal(err) - } - if len(clone.Templates()) != len(orig.Templates()) { - t.Fatalf("Invalid length of t.Clone().Templates()") - } - - const want = "stuff" - parsed := Must(clone.Parse(want)) - var buf bytes.Buffer - err = parsed.Execute(&buf, nil) - if err != nil { - t.Fatal(err) - } - if got := buf.String(); got != want { - t.Fatalf("got %q; want %q", got, want) - } -} - -func TestRedefineNonEmptyAfterExecution(t *testing.T) { - c := newTestCase(t) - c.mustParse(c.root, `foo`) - c.mustExecute(c.root, nil, "foo") - c.mustNotParse(c.root, `bar`) -} - -func TestRedefineEmptyAfterExecution(t *testing.T) { - c := newTestCase(t) - c.mustParse(c.root, ``) - c.mustExecute(c.root, nil, "") - c.mustNotParse(c.root, `foo`) - c.mustExecute(c.root, nil, "") -} - -func TestRedefineAfterNonExecution(t *testing.T) { - c := newTestCase(t) - c.mustParse(c.root, `{{if .}}<{{template "X"}}>{{end}}{{define "X"}}foo{{end}}`) - c.mustExecute(c.root, 0, "") - c.mustNotParse(c.root, `{{define "X"}}bar{{end}}`) - c.mustExecute(c.root, 1, "<foo>") -} - -func TestRedefineAfterNamedExecution(t *testing.T) { - c := newTestCase(t) - c.mustParse(c.root, `<{{template "X" .}}>{{define "X"}}foo{{end}}`) - c.mustExecute(c.root, nil, "<foo>") - c.mustNotParse(c.root, `{{define "X"}}bar{{end}}`) - c.mustExecute(c.root, nil, "<foo>") -} - -func TestRedefineNestedByNameAfterExecution(t *testing.T) { - c := newTestCase(t) - c.mustParse(c.root, `{{define "X"}}foo{{end}}`) - c.mustExecute(c.lookup("X"), nil, "foo") - c.mustNotParse(c.root, `{{define "X"}}bar{{end}}`) - c.mustExecute(c.lookup("X"), nil, "foo") -} - -func TestRedefineNestedByTemplateAfterExecution(t *testing.T) { - c := newTestCase(t) - c.mustParse(c.root, `{{define "X"}}foo{{end}}`) - c.mustExecute(c.lookup("X"), nil, "foo") - c.mustNotParse(c.lookup("X"), `bar`) - c.mustExecute(c.lookup("X"), nil, "foo") -} - -func TestRedefineSafety(t *testing.T) { - c := newTestCase(t) - c.mustParse(c.root, `<html><a href="{{template "X"}}">{{define "X"}}{{end}}`) - c.mustExecute(c.root, nil, `<html><a href="">`) - // Note: Every version of Go prior to Go 1.8 accepted the redefinition of "X" - // on the next line, but luckily kept it from being used in the outer template. - // Now we reject it, which makes clearer that we're not going to use it. - c.mustNotParse(c.root, `{{define "X"}}" bar="baz{{end}}`) - c.mustExecute(c.root, nil, `<html><a href="">`) -} - -func TestRedefineTopUse(t *testing.T) { - c := newTestCase(t) - c.mustParse(c.root, `{{template "X"}}{{.}}{{define "X"}}{{end}}`) - c.mustExecute(c.root, 42, `42`) - c.mustNotParse(c.root, `{{define "X"}}<script>{{end}}`) - c.mustExecute(c.root, 42, `42`) -} - -func TestRedefineOtherParsers(t *testing.T) { - c := newTestCase(t) - c.mustParse(c.root, ``) - c.mustExecute(c.root, nil, ``) - if _, err := c.root.ParseFiles("no.template"); err == nil || !strings.Contains(err.Error(), "Execute") { - t.Errorf("ParseFiles: %v\nwanted error about already having Executed", err) - } - if _, err := c.root.ParseGlob("*.no.template"); err == nil || !strings.Contains(err.Error(), "Execute") { - t.Errorf("ParseGlob: %v\nwanted error about already having Executed", err) - } - if _, err := c.root.AddParseTree("t1", c.root.Tree); err == nil || !strings.Contains(err.Error(), "Execute") { - t.Errorf("AddParseTree: %v\nwanted error about already having Executed", err) - } -} - -func TestNumbers(t *testing.T) { - c := newTestCase(t) - c.mustParse(c.root, `{{print 1_2.3_4}} {{print 0x0_1.e_0p+02}}`) - c.mustExecute(c.root, nil, "12.34 7.5") -} - -func TestStringsInScriptsWithJsonContentTypeAreCorrectlyEscaped(t *testing.T) { - // See #33671 and #37634 for more context on this. - tests := []struct{ name, in string }{ - {"empty", ""}, - {"invalid", string(rune(-1))}, - {"null", "\u0000"}, - {"unit separator", "\u001F"}, - {"tab", "\t"}, - {"gt and lt", "<>"}, - {"quotes", `'"`}, - {"ASCII letters", "ASCII letters"}, - {"Unicode", "ʕ⊙ϖ⊙ʔ"}, - {"Pizza", "🍕"}, - } - const ( - prefix = `<script type="application/ld+json">` - suffix = `</script>` - templ = prefix + `"{{.}}"` + suffix - ) - tpl := Must(New("JS string is JSON string").Parse(templ)) - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - var buf bytes.Buffer - if err := tpl.Execute(&buf, tt.in); err != nil { - t.Fatalf("Cannot render template: %v", err) - } - trimmed := bytes.TrimSuffix(bytes.TrimPrefix(buf.Bytes(), []byte(prefix)), []byte(suffix)) - var got string - if err := json.Unmarshal(trimmed, &got); err != nil { - t.Fatalf("Cannot parse JS string %q as JSON: %v", trimmed[1:len(trimmed)-1], err) - } - if got != tt.in { - t.Errorf("Serialization changed the string value: got %q want %q", got, tt.in) - } - }) - } -} - -func TestSkipEscapeComments(t *testing.T) { - c := newTestCase(t) - tr := parse.New("root") - tr.Mode = parse.ParseComments - newT, err := tr.Parse("{{/* A comment */}}{{ 1 }}{{/* Another comment */}}", "", "", make(map[string]*parse.Tree)) - if err != nil { - t.Fatalf("Cannot parse template text: %v", err) - } - c.root, err = c.root.AddParseTree("root", newT) - if err != nil { - t.Fatalf("Cannot add parse tree to template: %v", err) - } - c.mustExecute(c.root, nil, "1") -} - -type testCase struct { - t *testing.T - root *Template -} - -func newTestCase(t *testing.T) *testCase { - return &testCase{ - t: t, - root: New("root"), - } -} - -func (c *testCase) lookup(name string) *Template { - return c.root.Lookup(name) -} - -func (c *testCase) mustParse(t *Template, text string) { - _, err := t.Parse(text) - if err != nil { - c.t.Fatalf("parse: %v", err) - } -} - -func (c *testCase) mustNotParse(t *Template, text string) { - _, err := t.Parse(text) - if err == nil { - c.t.Fatalf("parse: unexpected success") - } -} - -func (c *testCase) mustExecute(t *Template, val interface{}, want string) { - var buf bytes.Buffer - err := t.Execute(&buf, val) - if err != nil { - c.t.Fatalf("execute: %v", err) - } - if buf.String() != want { - c.t.Fatalf("template output:\n%s\nwant:\n%s", buf.String(), want) - } -}
diff --git a/go.dev/cmd/internal/html/template/testdata/file1.tmpl b/go.dev/cmd/internal/html/template/testdata/file1.tmpl deleted file mode 100644 index febf9d9..0000000 --- a/go.dev/cmd/internal/html/template/testdata/file1.tmpl +++ /dev/null
@@ -1,2 +0,0 @@ -{{define "x"}}TEXT{{end}} -{{define "dotV"}}{{.V}}{{end}}
diff --git a/go.dev/cmd/internal/html/template/testdata/file2.tmpl b/go.dev/cmd/internal/html/template/testdata/file2.tmpl deleted file mode 100644 index 39bf6fb..0000000 --- a/go.dev/cmd/internal/html/template/testdata/file2.tmpl +++ /dev/null
@@ -1,2 +0,0 @@ -{{define "dot"}}{{.}}{{end}} -{{define "nested"}}{{template "dot" .}}{{end}}
diff --git a/go.dev/cmd/internal/html/template/testdata/fs.zip b/go.dev/cmd/internal/html/template/testdata/fs.zip deleted file mode 100644 index 8581313..0000000 --- a/go.dev/cmd/internal/html/template/testdata/fs.zip +++ /dev/null Binary files differ
diff --git a/go.dev/cmd/internal/html/template/testdata/tmpl1.tmpl b/go.dev/cmd/internal/html/template/testdata/tmpl1.tmpl deleted file mode 100644 index b72b3a3..0000000 --- a/go.dev/cmd/internal/html/template/testdata/tmpl1.tmpl +++ /dev/null
@@ -1,3 +0,0 @@ -template1 -{{define "x"}}x{{end}} -{{template "y"}}
diff --git a/go.dev/cmd/internal/html/template/testdata/tmpl2.tmpl b/go.dev/cmd/internal/html/template/testdata/tmpl2.tmpl deleted file mode 100644 index 16beba6..0000000 --- a/go.dev/cmd/internal/html/template/testdata/tmpl2.tmpl +++ /dev/null
@@ -1,3 +0,0 @@ -template2 -{{define "y"}}y{{end}} -{{template "x"}}
diff --git a/go.dev/cmd/internal/html/template/transition.go b/go.dev/cmd/internal/html/template/transition.go deleted file mode 100644 index 06df679..0000000 --- a/go.dev/cmd/internal/html/template/transition.go +++ /dev/null
@@ -1,592 +0,0 @@ -// Copyright 2011 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package template - -import ( - "bytes" - "strings" -) - -// transitionFunc is the array of context transition functions for text nodes. -// A transition function takes a context and template text input, and returns -// the updated context and the number of bytes consumed from the front of the -// input. -var transitionFunc = [...]func(context, []byte) (context, int){ - stateText: tText, - stateTag: tTag, - stateAttrName: tAttrName, - stateAfterName: tAfterName, - stateBeforeValue: tBeforeValue, - stateHTMLCmt: tHTMLCmt, - stateRCDATA: tSpecialTagEnd, - stateAttr: tAttr, - stateURL: tURL, - stateSrcset: tURL, - stateJS: tJS, - stateJSDqStr: tJSDelimited, - stateJSSqStr: tJSDelimited, - stateJSRegexp: tJSDelimited, - stateJSBlockCmt: tBlockCmt, - stateJSLineCmt: tLineCmt, - stateCSS: tCSS, - stateCSSDqStr: tCSSStr, - stateCSSSqStr: tCSSStr, - stateCSSDqURL: tCSSStr, - stateCSSSqURL: tCSSStr, - stateCSSURL: tCSSStr, - stateCSSBlockCmt: tBlockCmt, - stateCSSLineCmt: tLineCmt, - stateError: tError, -} - -var commentStart = []byte("<!--") -var commentEnd = []byte("-->") - -// tText is the context transition function for the text state. -func tText(c context, s []byte) (context, int) { - k := 0 - for { - i := k + bytes.IndexByte(s[k:], '<') - if i < k || i+1 == len(s) { - return c, len(s) - } else if i+4 <= len(s) && bytes.Equal(commentStart, s[i:i+4]) { - return context{state: stateHTMLCmt}, i + 4 - } - i++ - end := false - if s[i] == '/' { - if i+1 == len(s) { - return c, len(s) - } - end, i = true, i+1 - } - j, e := eatTagName(s, i) - if j != i { - if end { - e = elementNone - } - // We've found an HTML tag. - return context{state: stateTag, element: e}, j - } - k = j - } -} - -var elementContentType = [...]state{ - elementNone: stateText, - elementScript: stateJS, - elementStyle: stateCSS, - elementTextarea: stateRCDATA, - elementTitle: stateRCDATA, -} - -// tTag is the context transition function for the tag state. -func tTag(c context, s []byte) (context, int) { - // Find the attribute name. - i := eatWhiteSpace(s, 0) - if i == len(s) { - return c, len(s) - } - if s[i] == '>' { - return context{ - state: elementContentType[c.element], - element: c.element, - }, i + 1 - } - j, err := eatAttrName(s, i) - if err != nil { - return context{state: stateError, err: err}, len(s) - } - state, attr := stateTag, attrNone - if i == j { - return context{ - state: stateError, - err: errorf(ErrBadHTML, nil, 0, "expected space, attr name, or end of tag, but got %q", s[i:]), - }, len(s) - } - - attrName := strings.ToLower(string(s[i:j])) - if c.element == elementScript && attrName == "type" { - attr = attrScriptType - } else { - switch attrType(attrName) { - case contentTypeURL: - attr = attrURL - case contentTypeCSS: - attr = attrStyle - case contentTypeJS: - attr = attrScript - case contentTypeSrcset: - attr = attrSrcset - } - } - - if j == len(s) { - state = stateAttrName - } else { - state = stateAfterName - } - return context{state: state, element: c.element, attr: attr}, j -} - -// tAttrName is the context transition function for stateAttrName. -func tAttrName(c context, s []byte) (context, int) { - i, err := eatAttrName(s, 0) - if err != nil { - return context{state: stateError, err: err}, len(s) - } else if i != len(s) { - c.state = stateAfterName - } - return c, i -} - -// tAfterName is the context transition function for stateAfterName. -func tAfterName(c context, s []byte) (context, int) { - // Look for the start of the value. - i := eatWhiteSpace(s, 0) - if i == len(s) { - return c, len(s) - } else if s[i] != '=' { - // Occurs due to tag ending '>', and valueless attribute. - c.state = stateTag - return c, i - } - c.state = stateBeforeValue - // Consume the "=". - return c, i + 1 -} - -var attrStartStates = [...]state{ - attrNone: stateAttr, - attrScript: stateJS, - attrScriptType: stateAttr, - attrStyle: stateCSS, - attrURL: stateURL, - attrSrcset: stateSrcset, -} - -// tBeforeValue is the context transition function for stateBeforeValue. -func tBeforeValue(c context, s []byte) (context, int) { - i := eatWhiteSpace(s, 0) - if i == len(s) { - return c, len(s) - } - // Find the attribute delimiter. - delim := delimSpaceOrTagEnd - switch s[i] { - case '\'': - delim, i = delimSingleQuote, i+1 - case '"': - delim, i = delimDoubleQuote, i+1 - } - c.state, c.delim = attrStartStates[c.attr], delim - return c, i -} - -// tHTMLCmt is the context transition function for stateHTMLCmt. -func tHTMLCmt(c context, s []byte) (context, int) { - if i := bytes.Index(s, commentEnd); i != -1 { - return context{}, i + 3 - } - return c, len(s) -} - -// specialTagEndMarkers maps element types to the character sequence that -// case-insensitively signals the end of the special tag body. -var specialTagEndMarkers = [...][]byte{ - elementScript: []byte("script"), - elementStyle: []byte("style"), - elementTextarea: []byte("textarea"), - elementTitle: []byte("title"), -} - -var ( - specialTagEndPrefix = []byte("</") - tagEndSeparators = []byte("> \t\n\f/") -) - -// tSpecialTagEnd is the context transition function for raw text and RCDATA -// element states. -func tSpecialTagEnd(c context, s []byte) (context, int) { - if c.element != elementNone { - if i := indexTagEnd(s, specialTagEndMarkers[c.element]); i != -1 { - return context{}, i - } - } - return c, len(s) -} - -// indexTagEnd finds the index of a special tag end in a case insensitive way, or returns -1 -func indexTagEnd(s []byte, tag []byte) int { - res := 0 - plen := len(specialTagEndPrefix) - for len(s) > 0 { - // Try to find the tag end prefix first - i := bytes.Index(s, specialTagEndPrefix) - if i == -1 { - return i - } - s = s[i+plen:] - // Try to match the actual tag if there is still space for it - if len(tag) <= len(s) && bytes.EqualFold(tag, s[:len(tag)]) { - s = s[len(tag):] - // Check the tag is followed by a proper separator - if len(s) > 0 && bytes.IndexByte(tagEndSeparators, s[0]) != -1 { - return res + i - } - res += len(tag) - } - res += i + plen - } - return -1 -} - -// tAttr is the context transition function for the attribute state. -func tAttr(c context, s []byte) (context, int) { - return c, len(s) -} - -// tURL is the context transition function for the URL state. -func tURL(c context, s []byte) (context, int) { - if bytes.ContainsAny(s, "#?") { - c.urlPart = urlPartQueryOrFrag - } else if len(s) != eatWhiteSpace(s, 0) && c.urlPart == urlPartNone { - // HTML5 uses "Valid URL potentially surrounded by spaces" for - // attrs: https://www.w3.org/TR/html5/index.html#attributes-1 - c.urlPart = urlPartPreQuery - } - return c, len(s) -} - -// tJS is the context transition function for the JS state. -func tJS(c context, s []byte) (context, int) { - i := bytes.IndexAny(s, `"'/`) - if i == -1 { - // Entire input is non string, comment, regexp tokens. - c.jsCtx = nextJSCtx(s, c.jsCtx) - return c, len(s) - } - 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, i = stateJSLineCmt, i+1 - case i+1 < len(s) && s[i+1] == '*': - c.state, i = stateJSBlockCmt, i+1 - case c.jsCtx == jsCtxRegexp: - c.state = stateJSRegexp - case c.jsCtx == jsCtxDivOp: - c.jsCtx = jsCtxRegexp - default: - return context{ - state: stateError, - err: errorf(ErrSlashAmbig, nil, 0, "'/' could start a division or regexp: %.32q", s[i:]), - }, len(s) - } - default: - panic("unreachable") - } - return c, i + 1 -} - -// tJSDelimited is the context transition function for the JS string and regexp -// states. -func tJSDelimited(c context, s []byte) (context, int) { - specials := `\"` - switch c.state { - case stateJSSqStr: - specials = `\'` - case stateJSRegexp: - specials = `\/[]` - } - - k, inCharset := 0, false - for { - i := k + bytes.IndexAny(s[k:], specials) - if i < k { - break - } - switch s[i] { - case '\\': - i++ - if i == len(s) { - return context{ - state: stateError, - err: errorf(ErrPartialEscape, nil, 0, "unfinished escape sequence in JS string: %q", s), - }, len(s) - } - case '[': - inCharset = true - case ']': - inCharset = false - default: - // end delimiter - if !inCharset { - c.state, c.jsCtx = stateJS, jsCtxDivOp - return c, i + 1 - } - } - k = i + 1 - } - - if inCharset { - // This can be fixed by making context richer if interpolation - // into charsets is desired. - return context{ - state: stateError, - err: errorf(ErrPartialCharset, nil, 0, "unfinished JS regexp charset: %q", s), - }, len(s) - } - - return c, len(s) -} - -var blockCommentEnd = []byte("*/") - -// tBlockCmt is the context transition function for /*comment*/ states. -func tBlockCmt(c context, s []byte) (context, int) { - i := bytes.Index(s, blockCommentEnd) - if i == -1 { - return c, len(s) - } - switch c.state { - case stateJSBlockCmt: - c.state = stateJS - case stateCSSBlockCmt: - c.state = stateCSS - default: - panic(c.state.String()) - } - return c, i + 2 -} - -// tLineCmt is the context transition function for //comment states. -func tLineCmt(c context, s []byte) (context, int) { - var lineTerminators string - var endState state - switch c.state { - case stateJSLineCmt: - lineTerminators, endState = "\n\r\u2028\u2029", stateJS - case stateCSSLineCmt: - lineTerminators, endState = "\n\f\r", stateCSS - // Line comments are not part of any published CSS standard but - // are supported by the 4 major browsers. - // This defines line comments as - // LINECOMMENT ::= "//" [^\n\f\d]* - // since https://www.w3.org/TR/css3-syntax/#SUBTOK-nl defines - // newlines: - // nl ::= #xA | #xD #xA | #xD | #xC - default: - panic(c.state.String()) - } - - i := bytes.IndexAny(s, lineTerminators) - if i == -1 { - return c, len(s) - } - c.state = endState - // Per section 7.4 of EcmaScript 5 : https://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 - // recognized separately by the lexical grammar and becomes part - // of the stream of input elements for the syntactic grammar." - return c, i -} - -// tCSS is the context transition function for the CSS state. -func tCSS(c context, s []byte) (context, int) { - // CSS quoted strings are almost never used except for: - // (1) URLs as in background: "/foo.png" - // (2) Multiword font-names as in font-family: "Times New Roman" - // (3) List separators in content values as in inline-lists: - // <style> - // ul.inlineList { list-style: none; padding:0 } - // ul.inlineList > li { display: inline } - // ul.inlineList > li:before { content: ", " } - // ul.inlineList > li:first-child:before { content: "" } - // </style> - // <ul class=inlineList><li>One<li>Two<li>Three</ul> - // (4) Attribute value selectors as in a[href="http://example.com/"] - // - // We conservatively treat all strings as URLs, but make some - // allowances to avoid confusion. - // - // In (1), our conservative assumption is justified. - // In (2), valid font names do not contain ':', '?', or '#', so our - // conservative assumption is fine since we will never transition past - // urlPartPreQuery. - // In (3), our protocol heuristic should not be tripped, and there - // should not be non-space content after a '?' or '#', so as long as - // we only %-encode RFC 3986 reserved characters we are ok. - // In (4), we should URL escape for URL attributes, and for others we - // have the attribute name available if our conservative assumption - // proves problematic for real code. - - k := 0 - for { - i := k + bytes.IndexAny(s[k:], `("'/`) - if i < k { - return c, len(s) - } - switch s[i] { - case '(': - // Look for url to the left. - p := bytes.TrimRight(s[:i], "\t\n\f\r ") - if endsWithCSSKeyword(p, "url") { - j := len(s) - len(bytes.TrimLeft(s[i+1:], "\t\n\f\r ")) - switch { - case j != len(s) && s[j] == '"': - c.state, j = stateCSSDqURL, j+1 - case j != len(s) && s[j] == '\'': - c.state, j = stateCSSSqURL, j+1 - default: - c.state = stateCSSURL - } - return c, j - } - case '/': - if i+1 < len(s) { - switch s[i+1] { - case '/': - c.state = stateCSSLineCmt - return c, i + 2 - case '*': - c.state = stateCSSBlockCmt - return c, i + 2 - } - } - case '"': - c.state = stateCSSDqStr - return c, i + 1 - case '\'': - c.state = stateCSSSqStr - return c, i + 1 - } - k = i + 1 - } -} - -// tCSSStr is the context transition function for the CSS string and URL states. -func tCSSStr(c context, s []byte) (context, int) { - var endAndEsc string - switch c.state { - case stateCSSDqStr, stateCSSDqURL: - endAndEsc = `\"` - case stateCSSSqStr, stateCSSSqURL: - endAndEsc = `\'` - case stateCSSURL: - // Unquoted URLs end with a newline or close parenthesis. - // The below includes the wc (whitespace character) and nl. - endAndEsc = "\\\t\n\f\r )" - default: - panic(c.state.String()) - } - - k := 0 - for { - i := k + bytes.IndexAny(s[k:], endAndEsc) - if i < k { - c, nread := tURL(c, decodeCSS(s[k:])) - return c, k + nread - } - if s[i] == '\\' { - i++ - if i == len(s) { - return context{ - state: stateError, - err: errorf(ErrPartialEscape, nil, 0, "unfinished escape sequence in CSS string: %q", s), - }, len(s) - } - } else { - c.state = stateCSS - return c, i + 1 - } - c, _ = tURL(c, decodeCSS(s[:i+1])) - k = i + 1 - } -} - -// tError is the context transition function for the error state. -func tError(c context, s []byte) (context, int) { - return c, len(s) -} - -// eatAttrName returns the largest j such that s[i:j] is an attribute name. -// It returns an error if s[i:] does not look like it begins with an -// attribute name, such as encountering a quote mark without a preceding -// equals sign. -func eatAttrName(s []byte, i int) (int, *Error) { - for j := i; j < len(s); j++ { - switch s[j] { - case ' ', '\t', '\n', '\f', '\r', '=', '>': - return j, nil - case '\'', '"', '<': - // These result in a parse warning in HTML5 and are - // indicative of serious problems if seen in an attr - // name in a template. - return -1, errorf(ErrBadHTML, nil, 0, "%q in attribute name: %.32q", s[j:j+1], s) - default: - // No-op. - } - } - return len(s), nil -} - -var elementNameMap = map[string]element{ - "script": elementScript, - "style": elementStyle, - "textarea": elementTextarea, - "title": elementTitle, -} - -// asciiAlpha reports whether c is an ASCII letter. -func asciiAlpha(c byte) bool { - return 'A' <= c && c <= 'Z' || 'a' <= c && c <= 'z' -} - -// asciiAlphaNum reports whether c is an ASCII letter or digit. -func asciiAlphaNum(c byte) bool { - return asciiAlpha(c) || '0' <= c && c <= '9' -} - -// eatTagName returns the largest j such that s[i:j] is a tag name and the tag type. -func eatTagName(s []byte, i int) (int, element) { - if i == len(s) || !asciiAlpha(s[i]) { - return i, elementNone - } - j := i + 1 - for j < len(s) { - x := s[j] - if asciiAlphaNum(x) { - j++ - continue - } - // Allow "x-y" or "x:y" but not "x-", "-y", or "x--y". - if (x == ':' || x == '-') && j+1 < len(s) && asciiAlphaNum(s[j+1]) { - j += 2 - continue - } - break - } - return j, elementNameMap[strings.ToLower(string(s[i:j]))] -} - -// eatWhiteSpace returns the largest j such that s[i:j] is white space. -func eatWhiteSpace(s []byte, i int) int { - for j := i; j < len(s); j++ { - switch s[j] { - case ' ', '\t', '\n', '\f', '\r': - // No-op. - default: - return j - } - } - return len(s) -}
diff --git a/go.dev/cmd/internal/html/template/transition_test.go b/go.dev/cmd/internal/html/template/transition_test.go deleted file mode 100644 index 412a4c7..0000000 --- a/go.dev/cmd/internal/html/template/transition_test.go +++ /dev/null
@@ -1,60 +0,0 @@ -// Copyright 2011 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package template - -import ( - "bytes" - "strings" - "testing" -) - -func TestFindEndTag(t *testing.T) { - tests := []struct { - s, tag string - want int - }{ - {"", "tag", -1}, - {"hello </textarea> hello", "textarea", 6}, - {"hello </TEXTarea> hello", "textarea", 6}, - {"hello </textAREA>", "textarea", 6}, - {"hello </textarea", "textareax", -1}, - {"hello </textarea>", "tag", -1}, - {"hello tag </textarea", "tag", -1}, - {"hello </tag> </other> </textarea> <other>", "textarea", 22}, - {"</textarea> <other>", "textarea", 0}, - {"<div> </div> </TEXTAREA>", "textarea", 13}, - {"<div> </div> </TEXTAREA\t>", "textarea", 13}, - {"<div> </div> </TEXTAREA >", "textarea", 13}, - {"<div> </div> </TEXTAREAfoo", "textarea", -1}, - {"</TEXTAREAfoo </textarea>", "textarea", 14}, - {"<</script >", "script", 1}, - {"</script>", "textarea", -1}, - } - for _, test := range tests { - if got := indexTagEnd([]byte(test.s), []byte(test.tag)); test.want != got { - t.Errorf("%q/%q: want\n\t%d\nbut got\n\t%d", test.s, test.tag, test.want, got) - } - } -} - -func BenchmarkTemplateSpecialTags(b *testing.B) { - - r := struct { - Name, Gift string - }{"Aunt Mildred", "bone china tea set"} - - h1 := "<textarea> Hello Hello Hello </textarea> " - h2 := "<textarea> <p> Dear {{.Name}},\n{{with .Gift}}Thank you for the lovely {{.}}. {{end}}\nBest wishes. </p>\n</textarea>" - html := strings.Repeat(h1, 100) + h2 + strings.Repeat(h1, 100) + h2 - - var buf bytes.Buffer - for i := 0; i < b.N; i++ { - tmpl := Must(New("foo").Parse(html)) - if err := tmpl.Execute(&buf, r); err != nil { - b.Fatal(err) - } - buf.Reset() - } -}
diff --git a/go.dev/cmd/internal/html/template/url.go b/go.dev/cmd/internal/html/template/url.go deleted file mode 100644 index 6f8185a..0000000 --- a/go.dev/cmd/internal/html/template/url.go +++ /dev/null
@@ -1,219 +0,0 @@ -// Copyright 2011 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package template - -import ( - "bytes" - "fmt" - "strings" -) - -// urlFilter returns its input unless it contains an unsafe scheme in which -// case it defangs the entire URL. -// -// Schemes that cause unintended side effects that are irreversible without user -// interaction are considered unsafe. For example, clicking on a "javascript:" -// link can immediately trigger JavaScript code execution. -// -// This filter conservatively assumes that all schemes other than the following -// are unsafe: -// * http: Navigates to a new website, and may open a new window or tab. -// These side effects can be reversed by navigating back to the -// previous website, or closing the window or tab. No irreversible -// changes will take place without further user interaction with -// the new website. -// * https: Same as http. -// * mailto: Opens an email program and starts a new draft. This side effect -// is not irreversible until the user explicitly clicks send; it -// can be undone by closing the email program. -// -// To allow URLs containing other schemes to bypass this filter, developers must -// explicitly indicate that such a URL is expected and safe by encapsulating it -// in a template.URL value. -func urlFilter(args ...interface{}) string { - s, t := stringify(args...) - if t == contentTypeURL { - return s - } - if !isSafeURL(s) { - return "#" + filterFailsafe - } - return s -} - -// isSafeURL is true if s is a relative URL or if URL has a protocol in -// (http, https, mailto). -func isSafeURL(s string) bool { - if i := strings.IndexRune(s, ':'); i >= 0 && !strings.ContainsRune(s[:i], '/') { - - protocol := s[:i] - if !strings.EqualFold(protocol, "http") && !strings.EqualFold(protocol, "https") && !strings.EqualFold(protocol, "mailto") { - return false - } - } - return true -} - -// urlEscaper produces an output that can be embedded in a URL query. -// The output can be embedded in an HTML attribute without further escaping. -func urlEscaper(args ...interface{}) string { - return urlProcessor(false, args...) -} - -// urlNormalizer normalizes URL content so it can be embedded in a quote-delimited -// string or parenthesis delimited url(...). -// The normalizer does not encode all HTML specials. Specifically, it does not -// encode '&' so correct embedding in an HTML attribute requires escaping of -// '&' to '&'. -func urlNormalizer(args ...interface{}) string { - return urlProcessor(true, args...) -} - -// urlProcessor normalizes (when norm is true) or escapes its input to produce -// a valid hierarchical or opaque URL part. -func urlProcessor(norm bool, args ...interface{}) string { - s, t := stringify(args...) - if t == contentTypeURL { - norm = true - } - var b bytes.Buffer - if processURLOnto(s, norm, &b) { - return b.String() - } - return s -} - -// processURLOnto appends a normalized URL corresponding to its input to b -// and reports whether the appended content differs from s. -func processURLOnto(s string, norm bool, b *bytes.Buffer) bool { - b.Grow(len(s) + 16) - written := 0 - // The byte loop below assumes that all URLs use UTF-8 as the - // content-encoding. This is similar to the URI to IRI encoding scheme - // defined in section 3.1 of RFC 3987, and behaves the same as the - // EcmaScript builtin encodeURIComponent. - // It should not cause any misencoding of URLs in pages with - // Content-type: text/html;charset=UTF-8. - for i, n := 0, len(s); i < n; i++ { - c := s[i] - switch c { - // Single quote and parens are sub-delims in RFC 3986, but we - // escape them so the output can be embedded in single - // quoted attributes and unquoted CSS url(...) constructs. - // Single quotes are reserved in URLs, but are only used in - // the obsolete "mark" rule in an appendix in RFC 3986 - // so can be safely encoded. - case '!', '#', '$', '&', '*', '+', ',', '/', ':', ';', '=', '?', '@', '[', ']': - if norm { - continue - } - // Unreserved according to RFC 3986 sec 2.3 - // "For consistency, percent-encoded octets in the ranges of - // ALPHA (%41-%5A and %61-%7A), DIGIT (%30-%39), hyphen (%2D), - // period (%2E), underscore (%5F), or tilde (%7E) should not be - // created by URI producers - case '-', '.', '_', '~': - continue - case '%': - // When normalizing do not re-encode valid escapes. - if norm && i+2 < len(s) && isHex(s[i+1]) && isHex(s[i+2]) { - continue - } - default: - // Unreserved according to RFC 3986 sec 2.3 - if 'a' <= c && c <= 'z' { - continue - } - if 'A' <= c && c <= 'Z' { - continue - } - if '0' <= c && c <= '9' { - continue - } - } - b.WriteString(s[written:i]) - fmt.Fprintf(b, "%%%02x", c) - written = i + 1 - } - b.WriteString(s[written:]) - return written != 0 -} - -// Filters and normalizes srcset values which are comma separated -// URLs followed by metadata. -func srcsetFilterAndEscaper(args ...interface{}) string { - s, t := stringify(args...) - switch t { - case contentTypeSrcset: - return s - case contentTypeURL: - // Normalizing gets rid of all HTML whitespace - // which separate the image URL from its metadata. - var b bytes.Buffer - if processURLOnto(s, true, &b) { - s = b.String() - } - // Additionally, commas separate one source from another. - return strings.ReplaceAll(s, ",", "%2c") - } - - var b bytes.Buffer - written := 0 - for i := 0; i < len(s); i++ { - if s[i] == ',' { - filterSrcsetElement(s, written, i, &b) - b.WriteString(",") - written = i + 1 - } - } - filterSrcsetElement(s, written, len(s), &b) - return b.String() -} - -// Derived from https://play.golang.org/p/Dhmj7FORT5 -const htmlSpaceAndASCIIAlnumBytes = "\x00\x36\x00\x00\x01\x00\xff\x03\xfe\xff\xff\x07\xfe\xff\xff\x07" - -// isHTMLSpace is true iff c is a whitespace character per -// https://infra.spec.whatwg.org/#ascii-whitespace -func isHTMLSpace(c byte) bool { - return (c <= 0x20) && 0 != (htmlSpaceAndASCIIAlnumBytes[c>>3]&(1<<uint(c&0x7))) -} - -func isHTMLSpaceOrASCIIAlnum(c byte) bool { - return (c < 0x80) && 0 != (htmlSpaceAndASCIIAlnumBytes[c>>3]&(1<<uint(c&0x7))) -} - -func filterSrcsetElement(s string, left int, right int, b *bytes.Buffer) { - start := left - for start < right && isHTMLSpace(s[start]) { - start++ - } - end := right - for i := start; i < right; i++ { - if isHTMLSpace(s[i]) { - end = i - break - } - } - if url := s[start:end]; isSafeURL(url) { - // If image metadata is only spaces or alnums then - // we don't need to URL normalize it. - metadataOk := true - for i := end; i < right; i++ { - if !isHTMLSpaceOrASCIIAlnum(s[i]) { - metadataOk = false - break - } - } - if metadataOk { - b.WriteString(s[left:start]) - processURLOnto(url, true, b) - b.WriteString(s[end:right]) - return - } - } - b.WriteString("#") - b.WriteString(filterFailsafe) -}
diff --git a/go.dev/cmd/internal/html/template/url_test.go b/go.dev/cmd/internal/html/template/url_test.go deleted file mode 100644 index 75c354e..0000000 --- a/go.dev/cmd/internal/html/template/url_test.go +++ /dev/null
@@ -1,169 +0,0 @@ -// Copyright 2011 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package template - -import ( - "testing" -) - -func TestURLNormalizer(t *testing.T) { - tests := []struct { - url, want string - }{ - {"", ""}, - { - "http://example.com:80/foo/bar?q=foo%20&bar=x+y#frag", - "http://example.com:80/foo/bar?q=foo%20&bar=x+y#frag", - }, - {" ", "%20"}, - {"%7c", "%7c"}, - {"%7C", "%7C"}, - {"%2", "%252"}, - {"%", "%25"}, - {"%z", "%25z"}, - {"/foo|bar/%5c\u1234", "/foo%7cbar/%5c%e1%88%b4"}, - } - for _, test := range tests { - if got := urlNormalizer(test.url); test.want != got { - t.Errorf("%q: want\n\t%q\nbut got\n\t%q", test.url, test.want, got) - } - if test.want != urlNormalizer(test.want) { - t.Errorf("not idempotent: %q", test.want) - } - } -} - -func TestURLFilters(t *testing.T) { - input := ("\x00\x01\x02\x03\x04\x05\x06\x07\x08\t\n\x0b\x0c\r\x0e\x0f" + - "\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f" + - ` !"#$%&'()*+,-./` + - `0123456789:;<=>?` + - `@ABCDEFGHIJKLMNO` + - `PQRSTUVWXYZ[\]^_` + - "`abcdefghijklmno" + - "pqrstuvwxyz{|}~\x7f" + - "\u00A0\u0100\u2028\u2029\ufeff\U0001D11E") - - tests := []struct { - name string - escaper func(...interface{}) string - escaped string - }{ - { - "urlEscaper", - urlEscaper, - "%00%01%02%03%04%05%06%07%08%09%0a%0b%0c%0d%0e%0f" + - "%10%11%12%13%14%15%16%17%18%19%1a%1b%1c%1d%1e%1f" + - "%20%21%22%23%24%25%26%27%28%29%2a%2b%2c-.%2f" + - "0123456789%3a%3b%3c%3d%3e%3f" + - "%40ABCDEFGHIJKLMNO" + - "PQRSTUVWXYZ%5b%5c%5d%5e_" + - "%60abcdefghijklmno" + - "pqrstuvwxyz%7b%7c%7d~%7f" + - "%c2%a0%c4%80%e2%80%a8%e2%80%a9%ef%bb%bf%f0%9d%84%9e", - }, - { - "urlNormalizer", - urlNormalizer, - "%00%01%02%03%04%05%06%07%08%09%0a%0b%0c%0d%0e%0f" + - "%10%11%12%13%14%15%16%17%18%19%1a%1b%1c%1d%1e%1f" + - "%20!%22#$%25&%27%28%29*+,-./" + - "0123456789:;%3c=%3e?" + - "@ABCDEFGHIJKLMNO" + - "PQRSTUVWXYZ[%5c]%5e_" + - "%60abcdefghijklmno" + - "pqrstuvwxyz%7b%7c%7d~%7f" + - "%c2%a0%c4%80%e2%80%a8%e2%80%a9%ef%bb%bf%f0%9d%84%9e", - }, - } - - for _, test := range tests { - if s := test.escaper(input); s != test.escaped { - t.Errorf("%s: want\n\t%q\ngot\n\t%q", test.name, test.escaped, s) - continue - } - } -} - -func TestSrcsetFilter(t *testing.T) { - tests := []struct { - name string - input string - want string - }{ - { - "one ok", - "http://example.com/img.png", - "http://example.com/img.png", - }, - { - "one ok with metadata", - " /img.png 200w", - " /img.png 200w", - }, - { - "one bad", - "javascript:alert(1) 200w", - "#ZgotmplZ", - }, - { - "two ok", - "foo.png, bar.png", - "foo.png, bar.png", - }, - { - "left bad", - "javascript:alert(1), /foo.png", - "#ZgotmplZ, /foo.png", - }, - { - "right bad", - "/bogus#, javascript:alert(1)", - "/bogus#,#ZgotmplZ", - }, - } - - for _, test := range tests { - if got := srcsetFilterAndEscaper(test.input); got != test.want { - t.Errorf("%s: srcsetFilterAndEscaper(%q) want %q != %q", test.name, test.input, test.want, got) - } - } -} - -func BenchmarkURLEscaper(b *testing.B) { - for i := 0; i < b.N; i++ { - urlEscaper("http://example.com:80/foo?q=bar%20&baz=x+y#frag") - } -} - -func BenchmarkURLEscaperNoSpecials(b *testing.B) { - for i := 0; i < b.N; i++ { - urlEscaper("TheQuickBrownFoxJumpsOverTheLazyDog.") - } -} - -func BenchmarkURLNormalizer(b *testing.B) { - for i := 0; i < b.N; i++ { - urlNormalizer("The quick brown fox jumps over the lazy dog.\n") - } -} - -func BenchmarkURLNormalizerNoSpecials(b *testing.B) { - for i := 0; i < b.N; i++ { - urlNormalizer("http://example.com:80/foo?q=bar%20&baz=x+y#frag") - } -} - -func BenchmarkSrcsetFilter(b *testing.B) { - for i := 0; i < b.N; i++ { - srcsetFilterAndEscaper(" /foo/bar.png 200w, /baz/boo(1).png") - } -} - -func BenchmarkSrcsetFilterNoSpecials(b *testing.B) { - for i := 0; i < b.N; i++ { - srcsetFilterAndEscaper("http://example.com:80/foo?q=bar%20&baz=x+y#frag") - } -}
diff --git a/go.dev/cmd/internal/html/template/urlpart_string.go b/go.dev/cmd/internal/html/template/urlpart_string.go deleted file mode 100644 index 813eea9..0000000 --- a/go.dev/cmd/internal/html/template/urlpart_string.go +++ /dev/null
@@ -1,16 +0,0 @@ -// Code generated by "stringer -type urlPart"; DO NOT EDIT. - -package template - -import "strconv" - -const _urlPart_name = "urlPartNoneurlPartPreQueryurlPartQueryOrFragurlPartUnknown" - -var _urlPart_index = [...]uint8{0, 11, 26, 44, 58} - -func (i urlPart) String() string { - if i >= urlPart(len(_urlPart_index)-1) { - return "urlPart(" + strconv.FormatInt(int64(i), 10) + ")" - } - return _urlPart_name[_urlPart_index[i]:_urlPart_index[i+1]] -}
diff --git a/go.dev/cmd/internal/site/md.go b/go.dev/cmd/internal/site/md.go index c36c18c..72e2052 100644 --- a/go.dev/cmd/internal/site/md.go +++ b/go.dev/cmd/internal/site/md.go
@@ -16,8 +16,8 @@ "github.com/yuin/goldmark/renderer/html" "github.com/yuin/goldmark/text" "github.com/yuin/goldmark/util" - "golang.org/x/website/go.dev/cmd/internal/html/template" "golang.org/x/website/go.dev/cmd/internal/tmplfunc" + "golang.org/x/website/internal/backport/html/template" ) // markdownToHTML converts markdown to HTML using the renderer and settings that Hugo uses.
diff --git a/go.dev/cmd/internal/site/site.go b/go.dev/cmd/internal/site/site.go index f33421c..7ac0a19 100644 --- a/go.dev/cmd/internal/site/site.go +++ b/go.dev/cmd/internal/site/site.go
@@ -20,7 +20,7 @@ "strings" "time" - "golang.org/x/website/go.dev/cmd/internal/html/template" + "golang.org/x/website/internal/backport/html/template" "gopkg.in/yaml.v3" )
diff --git a/go.dev/cmd/internal/site/tmpl.go b/go.dev/cmd/internal/site/tmpl.go index a7ad25a..9bf04c1 100644 --- a/go.dev/cmd/internal/site/tmpl.go +++ b/go.dev/cmd/internal/site/tmpl.go
@@ -9,8 +9,8 @@ "reflect" "strings" - "golang.org/x/website/go.dev/cmd/internal/html/template" "golang.org/x/website/go.dev/cmd/internal/tmplfunc" + "golang.org/x/website/internal/backport/html/template" "gopkg.in/yaml.v3" )
diff --git a/go.dev/cmd/internal/text/template/doc.go b/go.dev/cmd/internal/text/template/doc.go deleted file mode 100644 index 5332002..0000000 --- a/go.dev/cmd/internal/text/template/doc.go +++ /dev/null
@@ -1,463 +0,0 @@ -// Copyright 2011 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -/* -Package template implements data-driven templates for generating textual output. - -To generate HTML output, see package html/template, which has the same interface -as this package but automatically secures HTML output against certain attacks. - -Templates are executed by applying them to a data structure. Annotations in the -template refer to elements of the data structure (typically a field of a struct -or a key in a map) to control execution and derive values to be displayed. -Execution of the template walks the structure and sets the cursor, represented -by a period '.' and called "dot", to the value at the current location in the -structure as execution proceeds. - -The input text for a template is UTF-8-encoded text in any format. -"Actions"--data evaluations or control structures--are delimited by -"{{" and "}}"; all text outside actions is copied to the output unchanged. -Except for raw strings, actions may not span newlines, although comments can. - -Once parsed, a template may be executed safely in parallel, although if parallel -executions share a Writer the output may be interleaved. - -Here is a trivial example that prints "17 items are made of wool". - - type Inventory struct { - Material string - Count uint - } - sweaters := Inventory{"wool", 17} - tmpl, err := template.New("test").Parse("{{.Count}} items are made of {{.Material}}") - if err != nil { panic(err) } - err = tmpl.Execute(os.Stdout, sweaters) - if err != nil { panic(err) } - -More intricate examples appear below. - -Text and spaces - -By default, all text between actions is copied verbatim when the template is -executed. For example, the string " items are made of " in the example above -appears on standard output when the program is run. - -However, to aid in formatting template source code, if an action's left -delimiter (by default "{{") is followed immediately by a minus sign and white -space, all trailing white space is trimmed from the immediately preceding text. -Similarly, if the right delimiter ("}}") is preceded by white space and a minus -sign, all leading white space is trimmed from the immediately following text. -In these trim markers, the white space must be present: -"{{- 3}}" is like "{{3}}" but trims the immediately preceding text, while -"{{-3}}" parses as an action containing the number -3. - -For instance, when executing the template whose source is - - "{{23 -}} < {{- 45}}" - -the generated output would be - - "23<45" - -For this trimming, the definition of white space characters is the same as in Go: -space, horizontal tab, carriage return, and newline. - -Actions - -Here is the list of actions. "Arguments" and "pipelines" are evaluations of -data, defined in detail in the corresponding sections that follow. - -*/ -// {{/* a comment */}} -// {{- /* a comment with white space trimmed from preceding and following text */ -}} -// A comment; discarded. May contain newlines. -// Comments do not nest and must start and end at the -// delimiters, as shown here. -/* - - {{pipeline}} - The default textual representation (the same as would be - printed by fmt.Print) of the value of the pipeline is copied - to the output. - - {{if pipeline}} T1 {{end}} - If the value of the pipeline is empty, no output is generated; - otherwise, T1 is executed. The empty values are false, 0, any - nil pointer or interface value, and any array, slice, map, or - string of length zero. - Dot is unaffected. - - {{if pipeline}} T1 {{else}} T0 {{end}} - If the value of the pipeline is empty, T0 is executed; - otherwise, T1 is executed. Dot is unaffected. - - {{if pipeline}} T1 {{else if pipeline}} T0 {{end}} - To simplify the appearance of if-else chains, the else action - of an if may include another if directly; the effect is exactly - the same as writing - {{if pipeline}} T1 {{else}}{{if pipeline}} T0 {{end}}{{end}} - - {{range pipeline}} T1 {{end}} - The value of the pipeline must be an array, slice, map, or channel. - If the value of the pipeline has length zero, nothing is output; - otherwise, dot is set to the successive elements of the array, - slice, or map and T1 is executed. If the value is a map and the - keys are of basic type with a defined order, the elements will be - visited in sorted key order. - - {{range pipeline}} T1 {{else}} T0 {{end}} - The value of the pipeline must be an array, slice, map, or channel. - If the value of the pipeline has length zero, dot is unaffected and - T0 is executed; otherwise, dot is set to the successive elements - of the array, slice, or map and T1 is executed. - - {{break}} - The innermost {{range pipeline}} loop is ended early, stopping the - current iteration and bypassing all remaining iterations. - - {{continue}} - The current iteration of the innermost {{range pipeline}} loop is - stopped, and the loop starts the next iteration. - - {{template "name"}} - The template with the specified name is executed with nil data. - - {{template "name" pipeline}} - The template with the specified name is executed with dot set - to the value of the pipeline. - - {{block "name" pipeline}} T1 {{end}} - A block is shorthand for defining a template - {{define "name"}} T1 {{end}} - and then executing it in place - {{template "name" pipeline}} - The typical use is to define a set of root templates that are - then customized by redefining the block templates within. - - {{with pipeline}} T1 {{end}} - If the value of the pipeline is empty, no output is generated; - otherwise, dot is set to the value of the pipeline and T1 is - executed. - - {{with pipeline}} T1 {{else}} T0 {{end}} - If the value of the pipeline is empty, dot is unaffected and T0 - is executed; otherwise, dot is set to the value of the pipeline - and T1 is executed. - -Arguments - -An argument is a simple value, denoted by one of the following. - - - A boolean, string, character, integer, floating-point, imaginary - or complex constant in Go syntax. These behave like Go's untyped - constants. Note that, as in Go, whether a large integer constant - overflows when assigned or passed to a function can depend on whether - the host machine's ints are 32 or 64 bits. - - The keyword nil, representing an untyped Go nil. - - The character '.' (period): - . - The result is the value of dot. - - A variable name, which is a (possibly empty) alphanumeric string - preceded by a dollar sign, such as - $piOver2 - or - $ - The result is the value of the variable. - Variables are described below. - - The name of a field of the data, which must be a struct, preceded - by a period, such as - .Field - The result is the value of the field. Field invocations may be - chained: - .Field1.Field2 - Fields can also be evaluated on variables, including chaining: - $x.Field1.Field2 - - The name of a key of the data, which must be a map, preceded - by a period, such as - .Key - The result is the map element value indexed by the key. - Key invocations may be chained and combined with fields to any - depth: - .Field1.Key1.Field2.Key2 - Although the key must be an alphanumeric identifier, unlike with - field names they do not need to start with an upper case letter. - Keys can also be evaluated on variables, including chaining: - $x.key1.key2 - - The name of a niladic method of the data, preceded by a period, - such as - .Method - The result is the value of invoking the method with dot as the - receiver, dot.Method(). Such a method must have one return value (of - any type) or two return values, the second of which is an error. - If it has two and the returned error is non-nil, execution terminates - and an error is returned to the caller as the value of Execute. - Method invocations may be chained and combined with fields and keys - to any depth: - .Field1.Key1.Method1.Field2.Key2.Method2 - Methods can also be evaluated on variables, including chaining: - $x.Method1.Field - - The name of a niladic function, such as - fun - The result is the value of invoking the function, fun(). The return - types and values behave as in methods. Functions and function - names are described below. - - A parenthesized instance of one the above, for grouping. The result - may be accessed by a field or map key invocation. - print (.F1 arg1) (.F2 arg2) - (.StructValuedMethod "arg").Field - -Arguments may evaluate to any type; if they are pointers the implementation -automatically indirects to the base type when required. -If an evaluation yields a function value, such as a function-valued -field of a struct, the function is not invoked automatically, but it -can be used as a truth value for an if action and the like. To invoke -it, use the call function, defined below. - -Pipelines - -A pipeline is a possibly chained sequence of "commands". A command is a simple -value (argument) or a function or method call, possibly with multiple arguments: - - Argument - The result is the value of evaluating the argument. - .Method [Argument...] - The method can be alone or the last element of a chain but, - unlike methods in the middle of a chain, it can take arguments. - The result is the value of calling the method with the - arguments: - dot.Method(Argument1, etc.) - functionName [Argument...] - The result is the value of calling the function associated - with the name: - function(Argument1, etc.) - Functions and function names are described below. - -A pipeline may be "chained" by separating a sequence of commands with pipeline -characters '|'. In a chained pipeline, the result of each command is -passed as the last argument of the following command. The output of the final -command in the pipeline is the value of the pipeline. - -The output of a command will be either one value or two values, the second of -which has type error. If that second value is present and evaluates to -non-nil, execution terminates and the error is returned to the caller of -Execute. - -Variables - -A pipeline inside an action may initialize a variable to capture the result. -The initialization has syntax - - $variable := pipeline - -where $variable is the name of the variable. An action that declares a -variable produces no output. - -Variables previously declared can also be assigned, using the syntax - - $variable = pipeline - -If a "range" action initializes a variable, the variable is set to the -successive elements of the iteration. Also, a "range" may declare two -variables, separated by a comma: - - range $index, $element := pipeline - -in which case $index and $element are set to the successive values of the -array/slice index or map key and element, respectively. Note that if there is -only one variable, it is assigned the element; this is opposite to the -convention in Go range clauses. - -A variable's scope extends to the "end" action of the control structure ("if", -"with", or "range") in which it is declared, or to the end of the template if -there is no such control structure. A template invocation does not inherit -variables from the point of its invocation. - -When execution begins, $ is set to the data argument passed to Execute, that is, -to the starting value of dot. - -Examples - -Here are some example one-line templates demonstrating pipelines and variables. -All produce the quoted word "output": - - {{"\"output\""}} - A string constant. - {{`"output"`}} - A raw string constant. - {{printf "%q" "output"}} - A function call. - {{"output" | printf "%q"}} - A function call whose final argument comes from the previous - command. - {{printf "%q" (print "out" "put")}} - A parenthesized argument. - {{"put" | printf "%s%s" "out" | printf "%q"}} - A more elaborate call. - {{"output" | printf "%s" | printf "%q"}} - A longer chain. - {{with "output"}}{{printf "%q" .}}{{end}} - A with action using dot. - {{with $x := "output" | printf "%q"}}{{$x}}{{end}} - A with action that creates and uses a variable. - {{with $x := "output"}}{{printf "%q" $x}}{{end}} - A with action that uses the variable in another action. - {{with $x := "output"}}{{$x | printf "%q"}}{{end}} - The same, but pipelined. - -Functions - -During execution functions are found in two function maps: first in the -template, then in the global function map. By default, no functions are defined -in the template but the Funcs method can be used to add them. - -Predefined global functions are named as follows. - - and - Returns the boolean AND of its arguments by returning the - first empty argument or the last argument, that is, - "and x y" behaves as "if x then y else x." Only those - arguments necessary to determine the answer are evaluated. - call - Returns the result of calling the first argument, which - must be a function, with the remaining arguments as parameters. - Thus "call .X.Y 1 2" is, in Go notation, dot.X.Y(1, 2) where - Y is a func-valued field, map entry, or the like. - The first argument must be the result of an evaluation - that yields a value of function type (as distinct from - a predefined function such as print). The function must - return either one or two result values, the second of which - is of type error. If the arguments don't match the function - or the returned error value is non-nil, execution stops. - html - Returns the escaped HTML equivalent of the textual - representation of its arguments. This function is unavailable - in html/template, with a few exceptions. - index - Returns the result of indexing its first argument by the - following arguments. Thus "index x 1 2 3" is, in Go syntax, - x[1][2][3]. Each indexed item must be a map, slice, or array. - slice - slice returns the result of slicing its first argument by the - remaining arguments. Thus "slice x 1 2" is, in Go syntax, x[1:2], - while "slice x" is x[:], "slice x 1" is x[1:], and "slice x 1 2 3" - is x[1:2:3]. The first argument must be a string, slice, or array. - js - Returns the escaped JavaScript equivalent of the textual - representation of its arguments. - len - Returns the integer length of its argument. - not - Returns the boolean negation of its single argument. - or - Returns the boolean OR of its arguments by returning the - first non-empty argument or the last argument, that is, - "or x y" behaves as "if x then x else y". Only those - arguments necessary to determine the answer are evaluated. - print - An alias for fmt.Sprint - printf - An alias for fmt.Sprintf - println - An alias for fmt.Sprintln - urlquery - Returns the escaped value of the textual representation of - its arguments in a form suitable for embedding in a URL query. - This function is unavailable in html/template, with a few - exceptions. - -The boolean functions take any zero value to be false and a non-zero -value to be true. - -There is also a set of binary comparison operators defined as -functions: - - eq - Returns the boolean truth of arg1 == arg2 - ne - Returns the boolean truth of arg1 != arg2 - lt - Returns the boolean truth of arg1 < arg2 - le - Returns the boolean truth of arg1 <= arg2 - gt - Returns the boolean truth of arg1 > arg2 - ge - Returns the boolean truth of arg1 >= arg2 - -For simpler multi-way equality tests, eq (only) accepts two or more -arguments and compares the second and subsequent to the first, -returning in effect - - arg1==arg2 || arg1==arg3 || arg1==arg4 ... - -(Unlike with || in Go, however, eq is a function call and all the -arguments will be evaluated.) - -The comparison functions work on any values whose type Go defines as -comparable. For basic types such as integers, the rules are relaxed: -size and exact type are ignored, so any integer value, signed or unsigned, -may be compared with any other integer value. (The arithmetic value is compared, -not the bit pattern, so all negative integers are less than all unsigned integers.) -However, as usual, one may not compare an int with a float32 and so on. - -Associated templates - -Each template is named by a string specified when it is created. Also, each -template is associated with zero or more other templates that it may invoke by -name; such associations are transitive and form a name space of templates. - -A template may use a template invocation to instantiate another associated -template; see the explanation of the "template" action above. The name must be -that of a template associated with the template that contains the invocation. - -Nested template definitions - -When parsing a template, another template may be defined and associated with the -template being parsed. Template definitions must appear at the top level of the -template, much like global variables in a Go program. - -The syntax of such definitions is to surround each template declaration with a -"define" and "end" action. - -The define action names the template being created by providing a string -constant. Here is a simple example: - - `{{define "T1"}}ONE{{end}} - {{define "T2"}}TWO{{end}} - {{define "T3"}}{{template "T1"}} {{template "T2"}}{{end}} - {{template "T3"}}` - -This defines two templates, T1 and T2, and a third T3 that invokes the other two -when it is executed. Finally it invokes T3. If executed this template will -produce the text - - ONE TWO - -By construction, a template may reside in only one association. If it's -necessary to have a template addressable from multiple associations, the -template definition must be parsed multiple times to create distinct *Template -values, or must be copied with the Clone or AddParseTree method. - -Parse may be called multiple times to assemble the various associated templates; -see the ParseFiles and ParseGlob functions and methods for simple ways to parse -related templates stored in files. - -A template may be executed directly or through ExecuteTemplate, which executes -an associated template identified by name. To invoke our example above, we -might write, - - err := tmpl.Execute(os.Stdout, "no data needed") - if err != nil { - log.Fatalf("execution failed: %s", err) - } - -or to invoke a particular template explicitly by name, - - err := tmpl.ExecuteTemplate(os.Stdout, "T2", "no data needed") - if err != nil { - log.Fatalf("execution failed: %s", err) - } - -*/ -package template
diff --git a/go.dev/cmd/internal/text/template/example_test.go b/go.dev/cmd/internal/text/template/example_test.go deleted file mode 100644 index 5f2e806..0000000 --- a/go.dev/cmd/internal/text/template/example_test.go +++ /dev/null
@@ -1,111 +0,0 @@ -// Copyright 2011 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package template_test - -import ( - "log" - "os" - "strings" - - "golang.org/x/website/go.dev/cmd/internal/text/template" -) - -func ExampleTemplate() { - // Define a template. - const letter = ` -Dear {{.Name}}, -{{if .Attended}} -It was a pleasure to see you at the wedding. -{{- else}} -It is a shame you couldn't make it to the wedding. -{{- end}} -{{with .Gift -}} -Thank you for the lovely {{.}}. -{{end}} -Best wishes, -Josie -` - - // Prepare some data to insert into the template. - type Recipient struct { - Name, Gift string - Attended bool - } - var recipients = []Recipient{ - {"Aunt Mildred", "bone china tea set", true}, - {"Uncle John", "moleskin pants", false}, - {"Cousin Rodney", "", false}, - } - - // Create a new template and parse the letter into it. - t := template.Must(template.New("letter").Parse(letter)) - - // Execute the template for each recipient. - for _, r := range recipients { - err := t.Execute(os.Stdout, r) - if err != nil { - log.Println("executing template:", err) - } - } - - // Output: - // Dear Aunt Mildred, - // - // It was a pleasure to see you at the wedding. - // Thank you for the lovely bone china tea set. - // - // Best wishes, - // Josie - // - // Dear Uncle John, - // - // It is a shame you couldn't make it to the wedding. - // Thank you for the lovely moleskin pants. - // - // Best wishes, - // Josie - // - // Dear Cousin Rodney, - // - // It is a shame you couldn't make it to the wedding. - // - // Best wishes, - // Josie -} - -// The following example is duplicated in html/template; keep them in sync. - -func ExampleTemplate_block() { - const ( - master = `Names:{{block "list" .}}{{"\n"}}{{range .}}{{println "-" .}}{{end}}{{end}}` - overlay = `{{define "list"}} {{join . ", "}}{{end}} ` - ) - var ( - funcs = template.FuncMap{"join": strings.Join} - guardians = []string{"Gamora", "Groot", "Nebula", "Rocket", "Star-Lord"} - ) - masterTmpl, err := template.New("master").Funcs(funcs).Parse(master) - if err != nil { - log.Fatal(err) - } - overlayTmpl, err := template.Must(masterTmpl.Clone()).Parse(overlay) - if err != nil { - log.Fatal(err) - } - if err := masterTmpl.Execute(os.Stdout, guardians); err != nil { - log.Fatal(err) - } - if err := overlayTmpl.Execute(os.Stdout, guardians); err != nil { - log.Fatal(err) - } - // Output: - // Names: - // - Gamora - // - Groot - // - Nebula - // - Rocket - // - Star-Lord - // Names: Gamora, Groot, Nebula, Rocket, Star-Lord -}
diff --git a/go.dev/cmd/internal/text/template/examplefiles_test.go b/go.dev/cmd/internal/text/template/examplefiles_test.go deleted file mode 100644 index b80a9e2..0000000 --- a/go.dev/cmd/internal/text/template/examplefiles_test.go +++ /dev/null
@@ -1,183 +0,0 @@ -// Copyright 2012 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package template_test - -import ( - "io" - "io/ioutil" - "log" - "os" - "path/filepath" - - "golang.org/x/website/go.dev/cmd/internal/text/template" -) - -// templateFile defines the contents of a template to be stored in a file, for testing. -type templateFile struct { - name string - contents string -} - -func createTestDir(files []templateFile) string { - dir, err := ioutil.TempDir("", "template") - if err != nil { - log.Fatal(err) - } - for _, file := range files { - f, err := os.Create(filepath.Join(dir, file.name)) - if err != nil { - log.Fatal(err) - } - defer f.Close() - _, err = io.WriteString(f, file.contents) - if err != nil { - log.Fatal(err) - } - } - return dir -} - -// Here we demonstrate loading a set of templates from a directory. -func ExampleTemplate_glob() { - // Here we create a temporary directory and populate it with our sample - // template definition files; usually the template files would already - // exist in some location known to the program. - dir := createTestDir([]templateFile{ - // T0.tmpl is a plain template file that just invokes T1. - {"T0.tmpl", `T0 invokes T1: ({{template "T1"}})`}, - // T1.tmpl defines a template, T1 that invokes T2. - {"T1.tmpl", `{{define "T1"}}T1 invokes T2: ({{template "T2"}}){{end}}`}, - // T2.tmpl defines a template T2. - {"T2.tmpl", `{{define "T2"}}This is T2{{end}}`}, - }) - // Clean up after the test; another quirk of running as an example. - defer os.RemoveAll(dir) - - // pattern is the glob pattern used to find all the template files. - pattern := filepath.Join(dir, "*.tmpl") - - // Here starts the example proper. - // T0.tmpl is the first name matched, so it becomes the starting template, - // the value returned by ParseGlob. - tmpl := template.Must(template.ParseGlob(pattern)) - - err := tmpl.Execute(os.Stdout, nil) - if err != nil { - log.Fatalf("template execution: %s", err) - } - // Output: - // T0 invokes T1: (T1 invokes T2: (This is T2)) -} - -// This example demonstrates one way to share some templates -// and use them in different contexts. In this variant we add multiple driver -// templates by hand to an existing bundle of templates. -func ExampleTemplate_helpers() { - // Here we create a temporary directory and populate it with our sample - // template definition files; usually the template files would already - // exist in some location known to the program. - dir := createTestDir([]templateFile{ - // T1.tmpl defines a template, T1 that invokes T2. - {"T1.tmpl", `{{define "T1"}}T1 invokes T2: ({{template "T2"}}){{end}}`}, - // T2.tmpl defines a template T2. - {"T2.tmpl", `{{define "T2"}}This is T2{{end}}`}, - }) - // Clean up after the test; another quirk of running as an example. - defer os.RemoveAll(dir) - - // pattern is the glob pattern used to find all the template files. - pattern := filepath.Join(dir, "*.tmpl") - - // Here starts the example proper. - // Load the helpers. - templates := template.Must(template.ParseGlob(pattern)) - // Add one driver template to the bunch; we do this with an explicit template definition. - _, err := templates.Parse("{{define `driver1`}}Driver 1 calls T1: ({{template `T1`}})\n{{end}}") - if err != nil { - log.Fatal("parsing driver1: ", err) - } - // Add another driver template. - _, err = templates.Parse("{{define `driver2`}}Driver 2 calls T2: ({{template `T2`}})\n{{end}}") - if err != nil { - log.Fatal("parsing driver2: ", err) - } - // We load all the templates before execution. This package does not require - // that behavior but html/template's escaping does, so it's a good habit. - err = templates.ExecuteTemplate(os.Stdout, "driver1", nil) - if err != nil { - log.Fatalf("driver1 execution: %s", err) - } - err = templates.ExecuteTemplate(os.Stdout, "driver2", nil) - if err != nil { - log.Fatalf("driver2 execution: %s", err) - } - // Output: - // Driver 1 calls T1: (T1 invokes T2: (This is T2)) - // Driver 2 calls T2: (This is T2) -} - -// This example demonstrates how to use one group of driver -// templates with distinct sets of helper templates. -func ExampleTemplate_share() { - // Here we create a temporary directory and populate it with our sample - // template definition files; usually the template files would already - // exist in some location known to the program. - dir := createTestDir([]templateFile{ - // T0.tmpl is a plain template file that just invokes T1. - {"T0.tmpl", "T0 ({{.}} version) invokes T1: ({{template `T1`}})\n"}, - // T1.tmpl defines a template, T1 that invokes T2. Note T2 is not defined - {"T1.tmpl", `{{define "T1"}}T1 invokes T2: ({{template "T2"}}){{end}}`}, - }) - // Clean up after the test; another quirk of running as an example. - defer os.RemoveAll(dir) - - // pattern is the glob pattern used to find all the template files. - pattern := filepath.Join(dir, "*.tmpl") - - // Here starts the example proper. - // Load the drivers. - drivers := template.Must(template.ParseGlob(pattern)) - - // We must define an implementation of the T2 template. First we clone - // the drivers, then add a definition of T2 to the template name space. - - // 1. Clone the helper set to create a new name space from which to run them. - first, err := drivers.Clone() - if err != nil { - log.Fatal("cloning helpers: ", err) - } - // 2. Define T2, version A, and parse it. - _, err = first.Parse("{{define `T2`}}T2, version A{{end}}") - if err != nil { - log.Fatal("parsing T2: ", err) - } - - // Now repeat the whole thing, using a different version of T2. - // 1. Clone the drivers. - second, err := drivers.Clone() - if err != nil { - log.Fatal("cloning drivers: ", err) - } - // 2. Define T2, version B, and parse it. - _, err = second.Parse("{{define `T2`}}T2, version B{{end}}") - if err != nil { - log.Fatal("parsing T2: ", err) - } - - // Execute the templates in the reverse order to verify the - // first is unaffected by the second. - err = second.ExecuteTemplate(os.Stdout, "T0.tmpl", "second") - if err != nil { - log.Fatalf("second execution: %s", err) - } - err = first.ExecuteTemplate(os.Stdout, "T0.tmpl", "first") - if err != nil { - log.Fatalf("first: execution: %s", err) - } - - // Output: - // T0 (second version) invokes T1: (T1 invokes T2: (T2, version B)) - // T0 (first version) invokes T1: (T1 invokes T2: (T2, version A)) -}
diff --git a/go.dev/cmd/internal/text/template/examplefunc_test.go b/go.dev/cmd/internal/text/template/examplefunc_test.go deleted file mode 100644 index 826c25c..0000000 --- a/go.dev/cmd/internal/text/template/examplefunc_test.go +++ /dev/null
@@ -1,55 +0,0 @@ -// Copyright 2012 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package template_test - -import ( - "log" - "os" - "strings" - - "golang.org/x/website/go.dev/cmd/internal/text/template" -) - -// This example demonstrates a custom function to process template text. -// It installs the strings.Title function and uses it to -// Make Title Text Look Good In Our Template's Output. -func ExampleTemplate_func() { - // First we create a FuncMap with which to register the function. - funcMap := template.FuncMap{ - // The name "title" is what the function will be called in the template text. - "title": strings.Title, - } - - // A simple template definition to test our function. - // We print the input text several ways: - // - the original - // - title-cased - // - title-cased and then printed with %q - // - printed with %q and then title-cased. - const templateText = ` -Input: {{printf "%q" .}} -Output 0: {{title .}} -Output 1: {{title . | printf "%q"}} -Output 2: {{printf "%q" . | title}} -` - - // Create a template, add the function map, and parse the text. - tmpl, err := template.New("titleTest").Funcs(funcMap).Parse(templateText) - if err != nil { - log.Fatalf("parsing: %s", err) - } - - // Run the template to verify the output. - err = tmpl.Execute(os.Stdout, "the go programming language") - if err != nil { - log.Fatalf("execution: %s", err) - } - - // Output: - // Input: "the go programming language" - // Output 0: The Go Programming Language - // Output 1: "The Go Programming Language" - // Output 2: "The Go Programming Language" -}
diff --git a/go.dev/cmd/internal/text/template/exec.go b/go.dev/cmd/internal/text/template/exec.go deleted file mode 100644 index be8835a..0000000 --- a/go.dev/cmd/internal/text/template/exec.go +++ /dev/null
@@ -1,1028 +0,0 @@ -// Copyright 2011 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package template - -import ( - "errors" - "fmt" - "io" - "reflect" - "runtime" - "strings" - - "golang.org/x/website/go.dev/cmd/internal/fmtsort" - "golang.org/x/website/go.dev/cmd/internal/text/template/parse" -) - -// maxExecDepth specifies the maximum stack depth of templates within -// templates. This limit is only practically reached by accidentally -// recursive template invocations. This limit allows us to return -// an error instead of triggering a stack overflow. -var maxExecDepth = initMaxExecDepth() - -func initMaxExecDepth() int { - if runtime.GOARCH == "wasm" { - return 1000 - } - return 100000 -} - -// state represents the state of an execution. It's not part of the -// template so that multiple executions of the same template -// can execute in parallel. -type state struct { - tmpl *Template - wr io.Writer - node parse.Node // current node, for errors - vars []variable // push-down stack of variable values. - depth int // the height of the stack of executing templates. -} - -// variable holds the dynamic value of a variable such as $, $x etc. -type variable struct { - name string - value reflect.Value -} - -// push pushes a new variable on the stack. -func (s *state) push(name string, value reflect.Value) { - s.vars = append(s.vars, variable{name, value}) -} - -// mark returns the length of the variable stack. -func (s *state) mark() int { - return len(s.vars) -} - -// pop pops the variable stack up to the mark. -func (s *state) pop(mark int) { - s.vars = s.vars[0:mark] -} - -// setVar overwrites the last declared variable with the given name. -// Used by variable assignments. -func (s *state) setVar(name string, value reflect.Value) { - for i := s.mark() - 1; i >= 0; i-- { - if s.vars[i].name == name { - s.vars[i].value = value - return - } - } - s.errorf("undefined variable: %s", name) -} - -// setTopVar overwrites the top-nth variable on the stack. Used by range iterations. -func (s *state) setTopVar(n int, value reflect.Value) { - s.vars[len(s.vars)-n].value = value -} - -// varValue returns the value of the named variable. -func (s *state) varValue(name string) reflect.Value { - for i := s.mark() - 1; i >= 0; i-- { - if s.vars[i].name == name { - return s.vars[i].value - } - } - s.errorf("undefined variable: %s", name) - return zero -} - -var zero reflect.Value - -type missingValType struct{} - -var missingVal = reflect.ValueOf(missingValType{}) - -// at marks the state to be on node n, for error reporting. -func (s *state) at(node parse.Node) { - s.node = node -} - -// doublePercent returns the string with %'s replaced by %%, if necessary, -// so it can be used safely inside a Printf format string. -func doublePercent(str string) string { - return strings.ReplaceAll(str, "%", "%%") -} - -// TODO: It would be nice if ExecError was more broken down, but -// the way ErrorContext embeds the template name makes the -// processing too clumsy. - -// ExecError is the custom error type returned when Execute has an -// error evaluating its template. (If a write error occurs, the actual -// error is returned; it will not be of type ExecError.) -type ExecError struct { - Name string // Name of template. - Err error // Pre-formatted error. -} - -func (e ExecError) Error() string { - return e.Err.Error() -} - -func (e ExecError) Unwrap() error { - return e.Err -} - -// errorf records an ExecError and terminates processing. -func (s *state) errorf(format string, args ...interface{}) { - name := doublePercent(s.tmpl.Name()) - if s.node == nil { - format = fmt.Sprintf("template: %s: %s", name, format) - } else { - location, context := s.tmpl.ErrorContext(s.node) - format = fmt.Sprintf("template: %s: executing %q at <%s>: %s", location, name, doublePercent(context), format) - } - panic(ExecError{ - Name: s.tmpl.Name(), - Err: fmt.Errorf(format, args...), - }) -} - -// writeError is the wrapper type used internally when Execute has an -// error writing to its output. We strip the wrapper in errRecover. -// Note that this is not an implementation of error, so it cannot escape -// from the package as an error value. -type writeError struct { - Err error // Original error. -} - -func (s *state) writeError(err error) { - panic(writeError{ - Err: err, - }) -} - -// errRecover is the handler that turns panics into returns from the top -// level of Parse. -func errRecover(errp *error) { - e := recover() - if e != nil { - switch err := e.(type) { - case runtime.Error: - panic(e) - case writeError: - *errp = err.Err // Strip the wrapper. - case ExecError: - *errp = err // Keep the wrapper. - default: - panic(e) - } - } -} - -// ExecuteTemplate applies the template associated with t that has the given name -// to the specified data object and writes the output to wr. -// If an error occurs executing the template or writing its output, -// execution stops, but partial results may already have been written to -// the output writer. -// A template may be executed safely in parallel, although if parallel -// executions share a Writer the output may be interleaved. -func (t *Template) ExecuteTemplate(wr io.Writer, name string, data interface{}) error { - tmpl := t.Lookup(name) - if tmpl == nil { - return fmt.Errorf("template: no template %q associated with template %q", name, t.name) - } - return tmpl.Execute(wr, data) -} - -// Execute applies a parsed template to the specified data object, -// and writes the output to wr. -// If an error occurs executing the template or writing its output, -// execution stops, but partial results may already have been written to -// the output writer. -// A template may be executed safely in parallel, although if parallel -// executions share a Writer the output may be interleaved. -// -// If data is a reflect.Value, the template applies to the concrete -// value that the reflect.Value holds, as in fmt.Print. -func (t *Template) Execute(wr io.Writer, data interface{}) error { - return t.execute(wr, data) -} - -func (t *Template) execute(wr io.Writer, data interface{}) (err error) { - defer errRecover(&err) - value, ok := data.(reflect.Value) - if !ok { - value = reflect.ValueOf(data) - } - state := &state{ - tmpl: t, - wr: wr, - vars: []variable{{"$", value}}, - } - if t.Tree == nil || t.Root == nil { - state.errorf("%q is an incomplete or empty template", t.Name()) - } - state.walk(value, t.Root) - return -} - -// DefinedTemplates returns a string listing the defined templates, -// prefixed by the string "; defined templates are: ". If there are none, -// it returns the empty string. For generating an error message here -// and in html/template. -func (t *Template) DefinedTemplates() string { - if t.common == nil { - return "" - } - var b strings.Builder - t.muTmpl.RLock() - defer t.muTmpl.RUnlock() - for name, tmpl := range t.tmpl { - if tmpl.Tree == nil || tmpl.Root == nil { - continue - } - if b.Len() == 0 { - b.WriteString("; defined templates are: ") - } else { - b.WriteString(", ") - } - fmt.Fprintf(&b, "%q", name) - } - return b.String() -} - -// Sentinel errors for use with panic to signal early exits from range loops. -var ( - walkBreak = errors.New("break") - walkContinue = errors.New("continue") -) - -// Walk functions step through the major pieces of the template structure, -// generating output as they go. -func (s *state) walk(dot reflect.Value, node parse.Node) { - s.at(node) - switch node := node.(type) { - case *parse.ActionNode: - // Do not pop variables so they persist until next end. - // Also, if the action declares variables, don't print the result. - val := s.evalPipeline(dot, node.Pipe) - if len(node.Pipe.Decl) == 0 { - s.printValue(node, val) - } - case *parse.BreakNode: - panic(walkBreak) - case *parse.CommentNode: - case *parse.ContinueNode: - panic(walkContinue) - case *parse.IfNode: - s.walkIfOrWith(parse.NodeIf, dot, node.Pipe, node.List, node.ElseList) - case *parse.ListNode: - for _, node := range node.Nodes { - s.walk(dot, node) - } - case *parse.RangeNode: - s.walkRange(dot, node) - case *parse.TemplateNode: - s.walkTemplate(dot, node) - case *parse.TextNode: - if _, err := s.wr.Write(node.Text); err != nil { - s.writeError(err) - } - case *parse.WithNode: - s.walkIfOrWith(parse.NodeWith, dot, node.Pipe, node.List, node.ElseList) - default: - s.errorf("unknown node: %s", node) - } -} - -// walkIfOrWith walks an 'if' or 'with' node. The two control structures -// are identical in behavior except that 'with' sets dot. -func (s *state) walkIfOrWith(typ parse.NodeType, dot reflect.Value, pipe *parse.PipeNode, list, elseList *parse.ListNode) { - defer s.pop(s.mark()) - val := s.evalPipeline(dot, pipe) - truth, ok := isTrue(indirectInterface(val)) - if !ok { - s.errorf("if/with can't use %v", val) - } - if truth { - if typ == parse.NodeWith { - s.walk(val, list) - } else { - s.walk(dot, list) - } - } else if elseList != nil { - s.walk(dot, elseList) - } -} - -// IsTrue reports whether the value is 'true', in the sense of not the zero of its type, -// and whether the value has a meaningful truth value. This is the definition of -// truth used by if and other such actions. -func IsTrue(val interface{}) (truth, ok bool) { - return isTrue(reflect.ValueOf(val)) -} - -func isTrue(val reflect.Value) (truth, ok bool) { - if !val.IsValid() { - // Something like var x interface{}, never set. It's a form of nil. - return false, true - } - switch val.Kind() { - case reflect.Array, reflect.Map, reflect.Slice, reflect.String: - truth = val.Len() > 0 - case reflect.Bool: - truth = val.Bool() - case reflect.Complex64, reflect.Complex128: - truth = val.Complex() != 0 - case reflect.Chan, reflect.Func, reflect.Ptr, reflect.Interface: - truth = !val.IsNil() - case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: - truth = val.Int() != 0 - case reflect.Float32, reflect.Float64: - truth = val.Float() != 0 - case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: - truth = val.Uint() != 0 - case reflect.Struct: - truth = true // Struct values are always true. - default: - return - } - return truth, true -} - -func (s *state) walkRange(dot reflect.Value, r *parse.RangeNode) { - s.at(r) - defer func() { - if r := recover(); r != nil && r != walkBreak { - panic(r) - } - }() - defer s.pop(s.mark()) - val, _ := indirect(s.evalPipeline(dot, r.Pipe)) - // mark top of stack before any variables in the body are pushed. - mark := s.mark() - oneIteration := func(index, elem reflect.Value) { - // Set top var (lexically the second if there are two) to the element. - if len(r.Pipe.Decl) > 0 { - s.setTopVar(1, elem) - } - // Set next var (lexically the first if there are two) to the index. - if len(r.Pipe.Decl) > 1 { - s.setTopVar(2, index) - } - defer s.pop(mark) - defer func() { - // Consume panic(walkContinue) - if r := recover(); r != nil && r != walkContinue { - panic(r) - } - }() - s.walk(elem, r.List) - } - switch val.Kind() { - case reflect.Array, reflect.Slice: - if val.Len() == 0 { - break - } - for i := 0; i < val.Len(); i++ { - oneIteration(reflect.ValueOf(i), val.Index(i)) - } - return - case reflect.Map: - if val.Len() == 0 { - break - } - om := fmtsort.Sort(val) - for i, key := range om.Key { - oneIteration(key, om.Value[i]) - } - return - case reflect.Chan: - if val.IsNil() { - break - } - if val.Type().ChanDir() == reflect.SendDir { - s.errorf("range over send-only channel %v", val) - break - } - i := 0 - for ; ; i++ { - elem, ok := val.Recv() - if !ok { - break - } - oneIteration(reflect.ValueOf(i), elem) - } - if i == 0 { - break - } - return - case reflect.Invalid: - break // An invalid value is likely a nil map, etc. and acts like an empty map. - default: - s.errorf("range can't iterate over %v", val) - } - if r.ElseList != nil { - s.walk(dot, r.ElseList) - } -} - -func (s *state) walkTemplate(dot reflect.Value, t *parse.TemplateNode) { - s.at(t) - tmpl := s.tmpl.Lookup(t.Name) - if tmpl == nil { - s.errorf("template %q not defined", t.Name) - } - if s.depth == maxExecDepth { - s.errorf("exceeded maximum template depth (%v)", maxExecDepth) - } - // Variables declared by the pipeline persist. - dot = s.evalPipeline(dot, t.Pipe) - newState := *s - newState.depth++ - newState.tmpl = tmpl - // No dynamic scoping: template invocations inherit no variables. - newState.vars = []variable{{"$", dot}} - newState.walk(dot, tmpl.Root) -} - -// Eval functions evaluate pipelines, commands, and their elements and extract -// values from the data structure by examining fields, calling methods, and so on. -// The printing of those values happens only through walk functions. - -// evalPipeline returns the value acquired by evaluating a pipeline. If the -// pipeline has a variable declaration, the variable will be pushed on the -// stack. Callers should therefore pop the stack after they are finished -// executing commands depending on the pipeline value. -func (s *state) evalPipeline(dot reflect.Value, pipe *parse.PipeNode) (value reflect.Value) { - if pipe == nil { - return - } - s.at(pipe) - value = missingVal - for _, cmd := range pipe.Cmds { - value = s.evalCommand(dot, cmd, value) // previous value is this one's final arg. - // If the object has type interface{}, dig down one level to the thing inside. - if value.Kind() == reflect.Interface && value.Type().NumMethod() == 0 { - value = reflect.ValueOf(value.Interface()) // lovely! - } - } - for _, variable := range pipe.Decl { - if pipe.IsAssign { - s.setVar(variable.Ident[0], value) - } else { - s.push(variable.Ident[0], value) - } - } - return value -} - -func (s *state) notAFunction(args []parse.Node, final reflect.Value) { - if len(args) > 1 || final != missingVal { - s.errorf("can't give argument to non-function %s", args[0]) - } -} - -func (s *state) evalCommand(dot reflect.Value, cmd *parse.CommandNode, final reflect.Value) reflect.Value { - firstWord := cmd.Args[0] - switch n := firstWord.(type) { - case *parse.FieldNode: - return s.evalFieldNode(dot, n, cmd.Args, final) - case *parse.ChainNode: - return s.evalChainNode(dot, n, cmd.Args, final) - case *parse.IdentifierNode: - // Must be a function. - return s.evalFunction(dot, n, cmd, cmd.Args, final) - case *parse.PipeNode: - // Parenthesized pipeline. The arguments are all inside the pipeline; final must be absent. - s.notAFunction(cmd.Args, final) - return s.evalPipeline(dot, n) - case *parse.VariableNode: - return s.evalVariableNode(dot, n, cmd.Args, final) - } - s.at(firstWord) - s.notAFunction(cmd.Args, final) - switch word := firstWord.(type) { - case *parse.BoolNode: - return reflect.ValueOf(word.True) - case *parse.DotNode: - return dot - case *parse.NilNode: - s.errorf("nil is not a command") - case *parse.NumberNode: - return s.idealConstant(word) - case *parse.StringNode: - return reflect.ValueOf(word.Text) - } - s.errorf("can't evaluate command %q", firstWord) - panic("not reached") -} - -// idealConstant is called to return the value of a number in a context where -// we don't know the type. In that case, the syntax of the number tells us -// its type, and we use Go rules to resolve. Note there is no such thing as -// a uint ideal constant in this situation - the value must be of int type. -func (s *state) idealConstant(constant *parse.NumberNode) reflect.Value { - // These are ideal constants but we don't know the type - // and we have no context. (If it was a method argument, - // we'd know what we need.) The syntax guides us to some extent. - s.at(constant) - switch { - case constant.IsComplex: - return reflect.ValueOf(constant.Complex128) // incontrovertible. - - case constant.IsFloat && - !isHexInt(constant.Text) && !isRuneInt(constant.Text) && - strings.ContainsAny(constant.Text, ".eEpP"): - return reflect.ValueOf(constant.Float64) - - case constant.IsInt: - n := int(constant.Int64) - if int64(n) != constant.Int64 { - s.errorf("%s overflows int", constant.Text) - } - return reflect.ValueOf(n) - - case constant.IsUint: - s.errorf("%s overflows int", constant.Text) - } - return zero -} - -func isRuneInt(s string) bool { - return len(s) > 0 && s[0] == '\'' -} - -func isHexInt(s string) bool { - return len(s) > 2 && s[0] == '0' && (s[1] == 'x' || s[1] == 'X') && !strings.ContainsAny(s, "pP") -} - -func (s *state) evalFieldNode(dot reflect.Value, field *parse.FieldNode, args []parse.Node, final reflect.Value) reflect.Value { - s.at(field) - return s.evalFieldChain(dot, dot, field, field.Ident, args, final) -} - -func (s *state) evalChainNode(dot reflect.Value, chain *parse.ChainNode, args []parse.Node, final reflect.Value) reflect.Value { - s.at(chain) - if len(chain.Field) == 0 { - s.errorf("internal error: no fields in evalChainNode") - } - if chain.Node.Type() == parse.NodeNil { - s.errorf("indirection through explicit nil in %s", chain) - } - // (pipe).Field1.Field2 has pipe as .Node, fields as .Field. Eval the pipeline, then the fields. - pipe := s.evalArg(dot, nil, chain.Node) - return s.evalFieldChain(dot, pipe, chain, chain.Field, args, final) -} - -func (s *state) evalVariableNode(dot reflect.Value, variable *parse.VariableNode, args []parse.Node, final reflect.Value) reflect.Value { - // $x.Field has $x as the first ident, Field as the second. Eval the var, then the fields. - s.at(variable) - value := s.varValue(variable.Ident[0]) - if len(variable.Ident) == 1 { - s.notAFunction(args, final) - return value - } - return s.evalFieldChain(dot, value, variable, variable.Ident[1:], args, final) -} - -// evalFieldChain evaluates .X.Y.Z possibly followed by arguments. -// dot is the environment in which to evaluate arguments, while -// receiver is the value being walked along the chain. -func (s *state) evalFieldChain(dot, receiver reflect.Value, node parse.Node, ident []string, args []parse.Node, final reflect.Value) reflect.Value { - n := len(ident) - for i := 0; i < n-1; i++ { - receiver = s.evalField(dot, ident[i], node, nil, missingVal, receiver) - } - // Now if it's a method, it gets the arguments. - return s.evalField(dot, ident[n-1], node, args, final, receiver) -} - -func (s *state) evalFunction(dot reflect.Value, node *parse.IdentifierNode, cmd parse.Node, args []parse.Node, final reflect.Value) reflect.Value { - s.at(node) - name := node.Ident - function, isBuiltin, ok := findFunction(name, s.tmpl) - if !ok { - s.errorf("%q is not a defined function", name) - } - return s.evalCall(dot, function, isBuiltin, cmd, name, args, final) -} - -// evalField evaluates an expression like (.Field) or (.Field arg1 arg2). -// The 'final' argument represents the return value from the preceding -// value of the pipeline, if any. -func (s *state) evalField(dot reflect.Value, fieldName string, node parse.Node, args []parse.Node, final, receiver reflect.Value) reflect.Value { - if !receiver.IsValid() { - if s.tmpl.option.missingKey == mapError { // Treat invalid value as missing map key. - s.errorf("nil data; no entry for key %q", fieldName) - } - return zero - } - typ := receiver.Type() - receiver, isNil := indirect(receiver) - if receiver.Kind() == reflect.Interface && isNil { - // Calling a method on a nil interface can't work. The - // MethodByName method call below would panic. - s.errorf("nil pointer evaluating %s.%s", typ, fieldName) - return zero - } - - // Unless it's an interface, need to get to a value of type *T to guarantee - // we see all methods of T and *T. - ptr := receiver - if ptr.Kind() != reflect.Interface && ptr.Kind() != reflect.Ptr && ptr.CanAddr() { - ptr = ptr.Addr() - } - if method := ptr.MethodByName(fieldName); method.IsValid() { - return s.evalCall(dot, method, false, node, fieldName, args, final) - } - hasArgs := len(args) > 1 || final != missingVal - // It's not a method; must be a field of a struct or an element of a map. - switch receiver.Kind() { - case reflect.Struct: - tField, ok := receiver.Type().FieldByName(fieldName) - if ok { - field := receiver.FieldByIndex(tField.Index) - if tField.PkgPath != "" { - s.errorf("%s is an unexported field of struct type %s", fieldName, typ) - } - // If it's a function, we must call it. - if hasArgs { - s.errorf("%s has arguments but cannot be invoked as function", fieldName) - } - return field - } - case reflect.Map: - // If it's a map, attempt to use the field name as a key. - nameVal := reflect.ValueOf(fieldName) - if nameVal.Type().AssignableTo(receiver.Type().Key()) { - if hasArgs { - s.errorf("%s is not a method but has arguments", fieldName) - } - result := receiver.MapIndex(nameVal) - if !result.IsValid() { - switch s.tmpl.option.missingKey { - case mapInvalid: - // Just use the invalid value. - case mapZeroValue: - result = reflect.Zero(receiver.Type().Elem()) - case mapError: - s.errorf("map has no entry for key %q", fieldName) - } - } - return result - } - case reflect.Ptr: - etyp := receiver.Type().Elem() - if etyp.Kind() == reflect.Struct { - if _, ok := etyp.FieldByName(fieldName); !ok { - // If there's no such field, say "can't evaluate" - // instead of "nil pointer evaluating". - break - } - } - if isNil { - s.errorf("nil pointer evaluating %s.%s", typ, fieldName) - } - } - s.errorf("can't evaluate field %s in type %s", fieldName, typ) - panic("not reached") -} - -var ( - errorType = reflect.TypeOf((*error)(nil)).Elem() - fmtStringerType = reflect.TypeOf((*fmt.Stringer)(nil)).Elem() - reflectValueType = reflect.TypeOf((*reflect.Value)(nil)).Elem() -) - -// evalCall executes a function or method call. If it's a method, fun already has the receiver bound, so -// it looks just like a function call. The arg list, if non-nil, includes (in the manner of the shell), arg[0] -// as the function itself. -func (s *state) evalCall(dot, fun reflect.Value, isBuiltin bool, node parse.Node, name string, args []parse.Node, final reflect.Value) reflect.Value { - if args != nil { - args = args[1:] // Zeroth arg is function name/node; not passed to function. - } - typ := fun.Type() - numIn := len(args) - if final != missingVal { - numIn++ - } - numFixed := len(args) - if typ.IsVariadic() { - numFixed = typ.NumIn() - 1 // last arg is the variadic one. - if numIn < numFixed { - s.errorf("wrong number of args for %s: want at least %d got %d", name, typ.NumIn()-1, len(args)) - } - } else if numIn != typ.NumIn() { - s.errorf("wrong number of args for %s: want %d got %d", name, typ.NumIn(), numIn) - } - if !goodFunc(typ) { - // TODO: This could still be a confusing error; maybe goodFunc should provide info. - s.errorf("can't call method/function %q with %d results", name, typ.NumOut()) - } - - // Special case for builtin and/or, which short-circuit. - if isBuiltin && (name == "and" || name == "or") { - argType := typ.In(0) - var v reflect.Value - for _, arg := range args { - v = s.evalArg(dot, argType, arg).Interface().(reflect.Value) - if truth(v) == (name == "or") { - break - } - } - return v - } - - // Build the arg list. - argv := make([]reflect.Value, numIn) - // Args must be evaluated. Fixed args first. - i := 0 - for ; i < numFixed && i < len(args); i++ { - argv[i] = s.evalArg(dot, typ.In(i), args[i]) - } - // Now the ... args. - if typ.IsVariadic() { - argType := typ.In(typ.NumIn() - 1).Elem() // Argument is a slice. - for ; i < len(args); i++ { - argv[i] = s.evalArg(dot, argType, args[i]) - } - } - // Add final value if necessary. - if final != missingVal { - t := typ.In(typ.NumIn() - 1) - if typ.IsVariadic() { - if numIn-1 < numFixed { - // The added final argument corresponds to a fixed parameter of the function. - // Validate against the type of the actual parameter. - t = typ.In(numIn - 1) - } else { - // The added final argument corresponds to the variadic part. - // Validate against the type of the elements of the variadic slice. - t = t.Elem() - } - } - argv[i] = s.validateType(final, t) - } - v, err := safeCall(fun, argv) - // If we have an error that is not nil, stop execution and return that - // error to the caller. - if err != nil { - s.at(node) - s.errorf("error calling %s: %w", name, err) - } - if v.Type() == reflectValueType { - v = v.Interface().(reflect.Value) - } - return v -} - -// canBeNil reports whether an untyped nil can be assigned to the type. See reflect.Zero. -func canBeNil(typ reflect.Type) bool { - switch typ.Kind() { - case reflect.Chan, reflect.Func, reflect.Interface, reflect.Map, reflect.Ptr, reflect.Slice: - return true - case reflect.Struct: - return typ == reflectValueType - } - return false -} - -// validateType guarantees that the value is valid and assignable to the type. -func (s *state) validateType(value reflect.Value, typ reflect.Type) reflect.Value { - if !value.IsValid() { - if typ == nil { - // An untyped nil interface{}. Accept as a proper nil value. - return reflect.ValueOf(nil) - } - if canBeNil(typ) { - // Like above, but use the zero value of the non-nil type. - return reflect.Zero(typ) - } - s.errorf("invalid value; expected %s", typ) - } - if typ == reflectValueType && value.Type() != typ { - return reflect.ValueOf(value) - } - if typ != nil && !value.Type().AssignableTo(typ) { - if value.Kind() == reflect.Interface && !value.IsNil() { - value = value.Elem() - if value.Type().AssignableTo(typ) { - return value - } - // fallthrough - } - // Does one dereference or indirection work? We could do more, as we - // do with method receivers, but that gets messy and method receivers - // are much more constrained, so it makes more sense there than here. - // Besides, one is almost always all you need. - switch { - case value.Kind() == reflect.Ptr && value.Type().Elem().AssignableTo(typ): - value = value.Elem() - if !value.IsValid() { - s.errorf("dereference of nil pointer of type %s", typ) - } - case reflect.PtrTo(value.Type()).AssignableTo(typ) && value.CanAddr(): - value = value.Addr() - default: - s.errorf("wrong type for value; expected %s; got %s", typ, value.Type()) - } - } - return value -} - -func (s *state) evalArg(dot reflect.Value, typ reflect.Type, n parse.Node) reflect.Value { - s.at(n) - switch arg := n.(type) { - case *parse.DotNode: - return s.validateType(dot, typ) - case *parse.NilNode: - if canBeNil(typ) { - return reflect.Zero(typ) - } - s.errorf("cannot assign nil to %s", typ) - case *parse.FieldNode: - return s.validateType(s.evalFieldNode(dot, arg, []parse.Node{n}, missingVal), typ) - case *parse.VariableNode: - return s.validateType(s.evalVariableNode(dot, arg, nil, missingVal), typ) - case *parse.PipeNode: - return s.validateType(s.evalPipeline(dot, arg), typ) - case *parse.IdentifierNode: - return s.validateType(s.evalFunction(dot, arg, arg, nil, missingVal), typ) - case *parse.ChainNode: - return s.validateType(s.evalChainNode(dot, arg, nil, missingVal), typ) - } - switch typ.Kind() { - case reflect.Bool: - return s.evalBool(typ, n) - case reflect.Complex64, reflect.Complex128: - return s.evalComplex(typ, n) - case reflect.Float32, reflect.Float64: - return s.evalFloat(typ, n) - case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: - return s.evalInteger(typ, n) - case reflect.Interface: - if typ.NumMethod() == 0 { - return s.evalEmptyInterface(dot, n) - } - case reflect.Struct: - if typ == reflectValueType { - return reflect.ValueOf(s.evalEmptyInterface(dot, n)) - } - case reflect.String: - return s.evalString(typ, n) - case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: - return s.evalUnsignedInteger(typ, n) - } - s.errorf("can't handle %s for arg of type %s", n, typ) - panic("not reached") -} - -func (s *state) evalBool(typ reflect.Type, n parse.Node) reflect.Value { - s.at(n) - if n, ok := n.(*parse.BoolNode); ok { - value := reflect.New(typ).Elem() - value.SetBool(n.True) - return value - } - s.errorf("expected bool; found %s", n) - panic("not reached") -} - -func (s *state) evalString(typ reflect.Type, n parse.Node) reflect.Value { - s.at(n) - if n, ok := n.(*parse.StringNode); ok { - value := reflect.New(typ).Elem() - value.SetString(n.Text) - return value - } - s.errorf("expected string; found %s", n) - panic("not reached") -} - -func (s *state) evalInteger(typ reflect.Type, n parse.Node) reflect.Value { - s.at(n) - if n, ok := n.(*parse.NumberNode); ok && n.IsInt { - value := reflect.New(typ).Elem() - value.SetInt(n.Int64) - return value - } - s.errorf("expected integer; found %s", n) - panic("not reached") -} - -func (s *state) evalUnsignedInteger(typ reflect.Type, n parse.Node) reflect.Value { - s.at(n) - if n, ok := n.(*parse.NumberNode); ok && n.IsUint { - value := reflect.New(typ).Elem() - value.SetUint(n.Uint64) - return value - } - s.errorf("expected unsigned integer; found %s", n) - panic("not reached") -} - -func (s *state) evalFloat(typ reflect.Type, n parse.Node) reflect.Value { - s.at(n) - if n, ok := n.(*parse.NumberNode); ok && n.IsFloat { - value := reflect.New(typ).Elem() - value.SetFloat(n.Float64) - return value - } - s.errorf("expected float; found %s", n) - panic("not reached") -} - -func (s *state) evalComplex(typ reflect.Type, n parse.Node) reflect.Value { - if n, ok := n.(*parse.NumberNode); ok && n.IsComplex { - value := reflect.New(typ).Elem() - value.SetComplex(n.Complex128) - return value - } - s.errorf("expected complex; found %s", n) - panic("not reached") -} - -func (s *state) evalEmptyInterface(dot reflect.Value, n parse.Node) reflect.Value { - s.at(n) - switch n := n.(type) { - case *parse.BoolNode: - return reflect.ValueOf(n.True) - case *parse.DotNode: - return dot - case *parse.FieldNode: - return s.evalFieldNode(dot, n, nil, missingVal) - case *parse.IdentifierNode: - return s.evalFunction(dot, n, n, nil, missingVal) - case *parse.NilNode: - // NilNode is handled in evalArg, the only place that calls here. - s.errorf("evalEmptyInterface: nil (can't happen)") - case *parse.NumberNode: - return s.idealConstant(n) - case *parse.StringNode: - return reflect.ValueOf(n.Text) - case *parse.VariableNode: - return s.evalVariableNode(dot, n, nil, missingVal) - case *parse.PipeNode: - return s.evalPipeline(dot, n) - } - s.errorf("can't handle assignment of %s to empty interface argument", n) - panic("not reached") -} - -// indirect returns the item at the end of indirection, and a bool to indicate -// if it's nil. If the returned bool is true, the returned value's kind will be -// either a pointer or interface. -func indirect(v reflect.Value) (rv reflect.Value, isNil bool) { - for ; v.Kind() == reflect.Ptr || v.Kind() == reflect.Interface; v = v.Elem() { - if v.IsNil() { - return v, true - } - } - return v, false -} - -// indirectInterface returns the concrete value in an interface value, -// or else the zero reflect.Value. -// That is, if v represents the interface value x, the result is the same as reflect.ValueOf(x): -// the fact that x was an interface value is forgotten. -func indirectInterface(v reflect.Value) reflect.Value { - if v.Kind() != reflect.Interface { - return v - } - if v.IsNil() { - return reflect.Value{} - } - return v.Elem() -} - -// printValue writes the textual representation of the value to the output of -// the template. -func (s *state) printValue(n parse.Node, v reflect.Value) { - s.at(n) - iface, ok := printableValue(v) - if !ok { - s.errorf("can't print %s of type %s", n, v.Type()) - } - _, err := fmt.Fprint(s.wr, iface) - if err != nil { - s.writeError(err) - } -} - -// printableValue returns the, possibly indirected, interface value inside v that -// is best for a call to formatted printer. -func printableValue(v reflect.Value) (interface{}, bool) { - if v.Kind() == reflect.Ptr { - v, _ = indirect(v) // fmt.Fprint handles nil. - } - if !v.IsValid() { - return "<no value>", true - } - - if !v.Type().Implements(errorType) && !v.Type().Implements(fmtStringerType) { - if v.CanAddr() && (reflect.PtrTo(v.Type()).Implements(errorType) || reflect.PtrTo(v.Type()).Implements(fmtStringerType)) { - v = v.Addr() - } else { - switch v.Kind() { - case reflect.Chan, reflect.Func: - return nil, false - } - } - } - return v.Interface(), true -}
diff --git a/go.dev/cmd/internal/text/template/exec_test.go b/go.dev/cmd/internal/text/template/exec_test.go deleted file mode 100644 index dbf2631..0000000 --- a/go.dev/cmd/internal/text/template/exec_test.go +++ /dev/null
@@ -1,1782 +0,0 @@ -// Copyright 2011 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package template - -import ( - "bytes" - "errors" - "flag" - "fmt" - "io/ioutil" - "reflect" - "strings" - "sync" - "testing" -) - -var debug = flag.Bool("debug", false, "show the errors produced by the tests") - -// T has lots of interesting pieces to use to test execution. -type T struct { - // Basics - True bool - I int - U16 uint16 - X, S string - FloatZero float64 - ComplexZero complex128 - // Nested structs. - U *U - // Struct with String method. - V0 V - V1, V2 *V - // Struct with Error method. - W0 W - W1, W2 *W - // Slices - SI []int - SICap []int - SIEmpty []int - SB []bool - // Arrays - AI [3]int - // Maps - MSI map[string]int - MSIone map[string]int // one element, for deterministic output - MSIEmpty map[string]int - MXI map[interface{}]int - MII map[int]int - MI32S map[int32]string - MI64S map[int64]string - MUI32S map[uint32]string - MUI64S map[uint64]string - MI8S map[int8]string - MUI8S map[uint8]string - SMSI []map[string]int - // Empty interfaces; used to see if we can dig inside one. - Empty0 interface{} // nil - Empty1 interface{} - Empty2 interface{} - Empty3 interface{} - Empty4 interface{} - // Non-empty interfaces. - NonEmptyInterface I - NonEmptyInterfacePtS *I - NonEmptyInterfaceNil I - NonEmptyInterfaceTypedNil I - // Stringer. - Str fmt.Stringer - Err error - // Pointers - PI *int - PS *string - PSI *[]int - NIL *int - // Function (not method) - BinaryFunc func(string, string) string - VariadicFunc func(...string) string - VariadicFuncInt func(int, ...string) string - NilOKFunc func(*int) bool - ErrFunc func() (string, error) - PanicFunc func() string - // Template to test evaluation of templates. - Tmpl *Template - // Unexported field; cannot be accessed by template. - unexported int -} - -type S []string - -func (S) Method0() string { - return "M0" -} - -type U struct { - V string -} - -type V struct { - j int -} - -func (v *V) String() string { - if v == nil { - return "nilV" - } - return fmt.Sprintf("<%d>", v.j) -} - -type W struct { - k int -} - -func (w *W) Error() string { - if w == nil { - return "nilW" - } - return fmt.Sprintf("[%d]", w.k) -} - -var siVal = I(S{"a", "b"}) - -var tVal = &T{ - True: true, - I: 17, - U16: 16, - X: "x", - S: "xyz", - U: &U{"v"}, - V0: V{6666}, - V1: &V{7777}, // leave V2 as nil - W0: W{888}, - W1: &W{999}, // leave W2 as nil - SI: []int{3, 4, 5}, - SICap: make([]int, 5, 10), - AI: [3]int{3, 4, 5}, - SB: []bool{true, false}, - MSI: map[string]int{"one": 1, "two": 2, "three": 3}, - MSIone: map[string]int{"one": 1}, - MXI: map[interface{}]int{"one": 1}, - MII: map[int]int{1: 1}, - MI32S: map[int32]string{1: "one", 2: "two"}, - MI64S: map[int64]string{2: "i642", 3: "i643"}, - MUI32S: map[uint32]string{2: "u322", 3: "u323"}, - MUI64S: map[uint64]string{2: "ui642", 3: "ui643"}, - MI8S: map[int8]string{2: "i82", 3: "i83"}, - MUI8S: map[uint8]string{2: "u82", 3: "u83"}, - SMSI: []map[string]int{ - {"one": 1, "two": 2}, - {"eleven": 11, "twelve": 12}, - }, - Empty1: 3, - Empty2: "empty2", - Empty3: []int{7, 8}, - Empty4: &U{"UinEmpty"}, - NonEmptyInterface: &T{X: "x"}, - NonEmptyInterfacePtS: &siVal, - NonEmptyInterfaceTypedNil: (*T)(nil), - Str: bytes.NewBuffer([]byte("foozle")), - Err: errors.New("erroozle"), - PI: newInt(23), - PS: newString("a string"), - PSI: newIntSlice(21, 22, 23), - BinaryFunc: func(a, b string) string { return fmt.Sprintf("[%s=%s]", a, b) }, - VariadicFunc: func(s ...string) string { return fmt.Sprint("<", strings.Join(s, "+"), ">") }, - VariadicFuncInt: func(a int, s ...string) string { return fmt.Sprint(a, "=<", strings.Join(s, "+"), ">") }, - NilOKFunc: func(s *int) bool { return s == nil }, - ErrFunc: func() (string, error) { return "bla", nil }, - PanicFunc: func() string { panic("test panic") }, - Tmpl: Must(New("x").Parse("test template")), // "x" is the value of .X -} - -var tSliceOfNil = []*T{nil} - -// A non-empty interface. -type I interface { - Method0() string -} - -var iVal I = tVal - -// Helpers for creation. -func newInt(n int) *int { - return &n -} - -func newString(s string) *string { - return &s -} - -func newIntSlice(n ...int) *[]int { - p := new([]int) - *p = make([]int, len(n)) - copy(*p, n) - return p -} - -// Simple methods with and without arguments. -func (t *T) Method0() string { - return "M0" -} - -func (t *T) Method1(a int) int { - return a -} - -func (t *T) Method2(a uint16, b string) string { - return fmt.Sprintf("Method2: %d %s", a, b) -} - -func (t *T) Method3(v interface{}) string { - return fmt.Sprintf("Method3: %v", v) -} - -func (t *T) Copy() *T { - n := new(T) - *n = *t - return n -} - -func (t *T) MAdd(a int, b []int) []int { - v := make([]int, len(b)) - for i, x := range b { - v[i] = x + a - } - return v -} - -var myError = errors.New("my error") - -// MyError returns a value and an error according to its argument. -func (t *T) MyError(error bool) (bool, error) { - if error { - return true, myError - } - return false, nil -} - -// A few methods to test chaining. -func (t *T) GetU() *U { - return t.U -} - -func (u *U) TrueFalse(b bool) string { - if b { - return "true" - } - return "" -} - -func typeOf(arg interface{}) string { - return fmt.Sprintf("%T", arg) -} - -type execTest struct { - name string - input string - output string - data interface{} - ok bool -} - -// bigInt and bigUint are hex string representing numbers either side -// of the max int boundary. -// We do it this way so the test doesn't depend on ints being 32 bits. -var ( - bigInt = fmt.Sprintf("0x%x", int(1<<uint(reflect.TypeOf(0).Bits()-1)-1)) - bigUint = fmt.Sprintf("0x%x", uint(1<<uint(reflect.TypeOf(0).Bits()-1))) -) - -var execTests = []execTest{ - // Trivial cases. - {"empty", "", "", nil, true}, - {"text", "some text", "some text", nil, true}, - {"nil action", "{{nil}}", "", nil, false}, - - // Ideal constants. - {"ideal int", "{{typeOf 3}}", "int", 0, true}, - {"ideal float", "{{typeOf 1.0}}", "float64", 0, true}, - {"ideal exp float", "{{typeOf 1e1}}", "float64", 0, true}, - {"ideal complex", "{{typeOf 1i}}", "complex128", 0, true}, - {"ideal int", "{{typeOf " + bigInt + "}}", "int", 0, true}, - {"ideal too big", "{{typeOf " + bigUint + "}}", "", 0, false}, - {"ideal nil without type", "{{nil}}", "", 0, false}, - - // Fields of structs. - {".X", "-{{.X}}-", "-x-", tVal, true}, - {".U.V", "-{{.U.V}}-", "-v-", tVal, true}, - {".unexported", "{{.unexported}}", "", tVal, false}, - - // Fields on maps. - {"map .one", "{{.MSI.one}}", "1", tVal, true}, - {"map .two", "{{.MSI.two}}", "2", tVal, true}, - {"map .NO", "{{.MSI.NO}}", "<no value>", tVal, true}, - {"map .one interface", "{{.MXI.one}}", "1", tVal, true}, - {"map .WRONG args", "{{.MSI.one 1}}", "", tVal, false}, - {"map .WRONG type", "{{.MII.one}}", "", tVal, false}, - - // Dots of all kinds to test basic evaluation. - {"dot int", "<{{.}}>", "<13>", 13, true}, - {"dot uint", "<{{.}}>", "<14>", uint(14), true}, - {"dot float", "<{{.}}>", "<15.1>", 15.1, true}, - {"dot bool", "<{{.}}>", "<true>", true, true}, - {"dot complex", "<{{.}}>", "<(16.2-17i)>", 16.2 - 17i, true}, - {"dot string", "<{{.}}>", "<hello>", "hello", true}, - {"dot slice", "<{{.}}>", "<[-1 -2 -3]>", []int{-1, -2, -3}, true}, - {"dot map", "<{{.}}>", "<map[two:22]>", map[string]int{"two": 22}, true}, - {"dot struct", "<{{.}}>", "<{7 seven}>", struct { - a int - b string - }{7, "seven"}, true}, - - // Variables. - {"$ int", "{{$}}", "123", 123, true}, - {"$.I", "{{$.I}}", "17", tVal, true}, - {"$.U.V", "{{$.U.V}}", "v", tVal, true}, - {"declare in action", "{{$x := $.U.V}}{{$x}}", "v", tVal, true}, - {"simple assignment", "{{$x := 2}}{{$x = 3}}{{$x}}", "3", tVal, true}, - {"nested assignment", - "{{$x := 2}}{{if true}}{{$x = 3}}{{end}}{{$x}}", - "3", tVal, true}, - {"nested assignment changes the last declaration", - "{{$x := 1}}{{if true}}{{$x := 2}}{{if true}}{{$x = 3}}{{end}}{{end}}{{$x}}", - "1", tVal, true}, - - // Type with String method. - {"V{6666}.String()", "-{{.V0}}-", "-<6666>-", tVal, true}, - {"&V{7777}.String()", "-{{.V1}}-", "-<7777>-", tVal, true}, - {"(*V)(nil).String()", "-{{.V2}}-", "-nilV-", tVal, true}, - - // Type with Error method. - {"W{888}.Error()", "-{{.W0}}-", "-[888]-", tVal, true}, - {"&W{999}.Error()", "-{{.W1}}-", "-[999]-", tVal, true}, - {"(*W)(nil).Error()", "-{{.W2}}-", "-nilW-", tVal, true}, - - // Pointers. - {"*int", "{{.PI}}", "23", tVal, true}, - {"*string", "{{.PS}}", "a string", tVal, true}, - {"*[]int", "{{.PSI}}", "[21 22 23]", tVal, true}, - {"*[]int[1]", "{{index .PSI 1}}", "22", tVal, true}, - {"NIL", "{{.NIL}}", "<nil>", tVal, true}, - - // Empty interfaces holding values. - {"empty nil", "{{.Empty0}}", "<no value>", tVal, true}, - {"empty with int", "{{.Empty1}}", "3", tVal, true}, - {"empty with string", "{{.Empty2}}", "empty2", tVal, true}, - {"empty with slice", "{{.Empty3}}", "[7 8]", tVal, true}, - {"empty with struct", "{{.Empty4}}", "{UinEmpty}", tVal, true}, - {"empty with struct, field", "{{.Empty4.V}}", "UinEmpty", tVal, true}, - - // Edge cases with <no value> with an interface value - {"field on interface", "{{.foo}}", "<no value>", nil, true}, - {"field on parenthesized interface", "{{(.).foo}}", "<no value>", nil, true}, - - // Issue 31810: Parenthesized first element of pipeline with arguments. - // See also TestIssue31810. - {"unparenthesized non-function", "{{1 2}}", "", nil, false}, - {"parenthesized non-function", "{{(1) 2}}", "", nil, false}, - {"parenthesized non-function with no args", "{{(1)}}", "1", nil, true}, // This is fine. - - // Method calls. - {".Method0", "-{{.Method0}}-", "-M0-", tVal, true}, - {".Method1(1234)", "-{{.Method1 1234}}-", "-1234-", tVal, true}, - {".Method1(.I)", "-{{.Method1 .I}}-", "-17-", tVal, true}, - {".Method2(3, .X)", "-{{.Method2 3 .X}}-", "-Method2: 3 x-", tVal, true}, - {".Method2(.U16, `str`)", "-{{.Method2 .U16 `str`}}-", "-Method2: 16 str-", tVal, true}, - {".Method2(.U16, $x)", "{{if $x := .X}}-{{.Method2 .U16 $x}}{{end}}-", "-Method2: 16 x-", tVal, true}, - {".Method3(nil constant)", "-{{.Method3 nil}}-", "-Method3: <nil>-", tVal, true}, - {".Method3(nil value)", "-{{.Method3 .MXI.unset}}-", "-Method3: <nil>-", tVal, true}, - {"method on var", "{{if $x := .}}-{{$x.Method2 .U16 $x.X}}{{end}}-", "-Method2: 16 x-", tVal, true}, - {"method on chained var", - "{{range .MSIone}}{{if $.U.TrueFalse $.True}}{{$.U.TrueFalse $.True}}{{else}}WRONG{{end}}{{end}}", - "true", tVal, true}, - {"chained method", - "{{range .MSIone}}{{if $.GetU.TrueFalse $.True}}{{$.U.TrueFalse $.True}}{{else}}WRONG{{end}}{{end}}", - "true", tVal, true}, - {"chained method on variable", - "{{with $x := .}}{{with .SI}}{{$.GetU.TrueFalse $.True}}{{end}}{{end}}", - "true", tVal, true}, - {".NilOKFunc not nil", "{{call .NilOKFunc .PI}}", "false", tVal, true}, - {".NilOKFunc nil", "{{call .NilOKFunc nil}}", "true", tVal, true}, - {"method on nil value from slice", "-{{range .}}{{.Method1 1234}}{{end}}-", "-1234-", tSliceOfNil, true}, - {"method on typed nil interface value", "{{.NonEmptyInterfaceTypedNil.Method0}}", "M0", tVal, true}, - - // Function call builtin. - {".BinaryFunc", "{{call .BinaryFunc `1` `2`}}", "[1=2]", tVal, true}, - {".VariadicFunc0", "{{call .VariadicFunc}}", "<>", tVal, true}, - {".VariadicFunc2", "{{call .VariadicFunc `he` `llo`}}", "<he+llo>", tVal, true}, - {".VariadicFuncInt", "{{call .VariadicFuncInt 33 `he` `llo`}}", "33=<he+llo>", tVal, true}, - {"if .BinaryFunc call", "{{ if .BinaryFunc}}{{call .BinaryFunc `1` `2`}}{{end}}", "[1=2]", tVal, true}, - {"if not .BinaryFunc call", "{{ if not .BinaryFunc}}{{call .BinaryFunc `1` `2`}}{{else}}No{{end}}", "No", tVal, true}, - {"Interface Call", `{{stringer .S}}`, "foozle", map[string]interface{}{"S": bytes.NewBufferString("foozle")}, true}, - {".ErrFunc", "{{call .ErrFunc}}", "bla", tVal, true}, - {"call nil", "{{call nil}}", "", tVal, false}, - - // Erroneous function calls (check args). - {".BinaryFuncTooFew", "{{call .BinaryFunc `1`}}", "", tVal, false}, - {".BinaryFuncTooMany", "{{call .BinaryFunc `1` `2` `3`}}", "", tVal, false}, - {".BinaryFuncBad0", "{{call .BinaryFunc 1 3}}", "", tVal, false}, - {".BinaryFuncBad1", "{{call .BinaryFunc `1` 3}}", "", tVal, false}, - {".VariadicFuncBad0", "{{call .VariadicFunc 3}}", "", tVal, false}, - {".VariadicFuncIntBad0", "{{call .VariadicFuncInt}}", "", tVal, false}, - {".VariadicFuncIntBad`", "{{call .VariadicFuncInt `x`}}", "", tVal, false}, - {".VariadicFuncNilBad", "{{call .VariadicFunc nil}}", "", tVal, false}, - - // Pipelines. - {"pipeline", "-{{.Method0 | .Method2 .U16}}-", "-Method2: 16 M0-", tVal, true}, - {"pipeline func", "-{{call .VariadicFunc `llo` | call .VariadicFunc `he` }}-", "-<he+<llo>>-", tVal, true}, - - // Nil values aren't missing arguments. - {"nil pipeline", "{{ .Empty0 | call .NilOKFunc }}", "true", tVal, true}, - {"nil call arg", "{{ call .NilOKFunc .Empty0 }}", "true", tVal, true}, - {"bad nil pipeline", "{{ .Empty0 | .VariadicFunc }}", "", tVal, false}, - - // Parenthesized expressions - {"parens in pipeline", "{{printf `%d %d %d` (1) (2 | add 3) (add 4 (add 5 6))}}", "1 5 15", tVal, true}, - - // Parenthesized expressions with field accesses - {"parens: $ in paren", "{{($).X}}", "x", tVal, true}, - {"parens: $.GetU in paren", "{{($.GetU).V}}", "v", tVal, true}, - {"parens: $ in paren in pipe", "{{($ | echo).X}}", "x", tVal, true}, - {"parens: spaces and args", `{{(makemap "up" "down" "left" "right").left}}`, "right", tVal, true}, - - // If. - {"if true", "{{if true}}TRUE{{end}}", "TRUE", tVal, true}, - {"if false", "{{if false}}TRUE{{else}}FALSE{{end}}", "FALSE", tVal, true}, - {"if nil", "{{if nil}}TRUE{{end}}", "", tVal, false}, - {"if on typed nil interface value", "{{if .NonEmptyInterfaceTypedNil}}TRUE{{ end }}", "", tVal, true}, - {"if 1", "{{if 1}}NON-ZERO{{else}}ZERO{{end}}", "NON-ZERO", tVal, true}, - {"if 0", "{{if 0}}NON-ZERO{{else}}ZERO{{end}}", "ZERO", tVal, true}, - {"if 1.5", "{{if 1.5}}NON-ZERO{{else}}ZERO{{end}}", "NON-ZERO", tVal, true}, - {"if 0.0", "{{if .FloatZero}}NON-ZERO{{else}}ZERO{{end}}", "ZERO", tVal, true}, - {"if 1.5i", "{{if 1.5i}}NON-ZERO{{else}}ZERO{{end}}", "NON-ZERO", tVal, true}, - {"if 0.0i", "{{if .ComplexZero}}NON-ZERO{{else}}ZERO{{end}}", "ZERO", tVal, true}, - {"if emptystring", "{{if ``}}NON-EMPTY{{else}}EMPTY{{end}}", "EMPTY", tVal, true}, - {"if string", "{{if `notempty`}}NON-EMPTY{{else}}EMPTY{{end}}", "NON-EMPTY", tVal, true}, - {"if emptyslice", "{{if .SIEmpty}}NON-EMPTY{{else}}EMPTY{{end}}", "EMPTY", tVal, true}, - {"if slice", "{{if .SI}}NON-EMPTY{{else}}EMPTY{{end}}", "NON-EMPTY", tVal, true}, - {"if emptymap", "{{if .MSIEmpty}}NON-EMPTY{{else}}EMPTY{{end}}", "EMPTY", tVal, true}, - {"if map", "{{if .MSI}}NON-EMPTY{{else}}EMPTY{{end}}", "NON-EMPTY", tVal, true}, - {"if map unset", "{{if .MXI.none}}NON-ZERO{{else}}ZERO{{end}}", "ZERO", tVal, true}, - {"if map not unset", "{{if not .MXI.none}}ZERO{{else}}NON-ZERO{{end}}", "ZERO", tVal, true}, - {"if $x with $y int", "{{if $x := true}}{{with $y := .I}}{{$x}},{{$y}}{{end}}{{end}}", "true,17", tVal, true}, - {"if $x with $x int", "{{if $x := true}}{{with $x := .I}}{{$x}},{{end}}{{$x}}{{end}}", "17,true", tVal, true}, - {"if else if", "{{if false}}FALSE{{else if true}}TRUE{{end}}", "TRUE", tVal, true}, - {"if else chain", "{{if eq 1 3}}1{{else if eq 2 3}}2{{else if eq 3 3}}3{{end}}", "3", tVal, true}, - - // Print etc. - {"print", `{{print "hello, print"}}`, "hello, print", tVal, true}, - {"print 123", `{{print 1 2 3}}`, "1 2 3", tVal, true}, - {"print nil", `{{print nil}}`, "<nil>", tVal, true}, - {"println", `{{println 1 2 3}}`, "1 2 3\n", tVal, true}, - {"printf int", `{{printf "%04x" 127}}`, "007f", tVal, true}, - {"printf float", `{{printf "%g" 3.5}}`, "3.5", tVal, true}, - {"printf complex", `{{printf "%g" 1+7i}}`, "(1+7i)", tVal, true}, - {"printf string", `{{printf "%s" "hello"}}`, "hello", tVal, true}, - {"printf function", `{{printf "%#q" zeroArgs}}`, "`zeroArgs`", tVal, true}, - {"printf field", `{{printf "%s" .U.V}}`, "v", tVal, true}, - {"printf method", `{{printf "%s" .Method0}}`, "M0", tVal, true}, - {"printf dot", `{{with .I}}{{printf "%d" .}}{{end}}`, "17", tVal, true}, - {"printf var", `{{with $x := .I}}{{printf "%d" $x}}{{end}}`, "17", tVal, true}, - {"printf lots", `{{printf "%d %s %g %s" 127 "hello" 7-3i .Method0}}`, "127 hello (7-3i) M0", tVal, true}, - - // HTML. - {"html", `{{html "<script>alert(\"XSS\");</script>"}}`, - "<script>alert("XSS");</script>", nil, true}, - {"html pipeline", `{{printf "<script>alert(\"XSS\");</script>" | html}}`, - "<script>alert("XSS");</script>", nil, true}, - {"html", `{{html .PS}}`, "a string", tVal, true}, - {"html typed nil", `{{html .NIL}}`, "<nil>", tVal, true}, - {"html untyped nil", `{{html .Empty0}}`, "<no value>", tVal, true}, - - // JavaScript. - {"js", `{{js .}}`, `It\'d be nice.`, `It'd be nice.`, true}, - - // URL query. - {"urlquery", `{{"http://www.example.org/"|urlquery}}`, "http%3A%2F%2Fwww.example.org%2F", nil, true}, - - // Booleans - {"not", "{{not true}} {{not false}}", "false true", nil, true}, - {"and", "{{and false 0}} {{and 1 0}} {{and 0 true}} {{and 1 1}}", "false 0 0 1", nil, true}, - {"or", "{{or 0 0}} {{or 1 0}} {{or 0 true}} {{or 1 1}}", "0 1 true 1", nil, true}, - {"or short-circuit", "{{or 0 1 (die)}}", "1", nil, true}, - {"and short-circuit", "{{and 1 0 (die)}}", "0", nil, true}, - {"or short-circuit2", "{{or 0 0 (die)}}", "", nil, false}, - {"and short-circuit2", "{{and 1 1 (die)}}", "", nil, false}, - {"boolean if", "{{if and true 1 `hi`}}TRUE{{else}}FALSE{{end}}", "TRUE", tVal, true}, - {"boolean if not", "{{if and true 1 `hi` | not}}TRUE{{else}}FALSE{{end}}", "FALSE", nil, true}, - - // Indexing. - {"slice[0]", "{{index .SI 0}}", "3", tVal, true}, - {"slice[1]", "{{index .SI 1}}", "4", tVal, true}, - {"slice[HUGE]", "{{index .SI 10}}", "", tVal, false}, - {"slice[WRONG]", "{{index .SI `hello`}}", "", tVal, false}, - {"slice[nil]", "{{index .SI nil}}", "", tVal, false}, - {"map[one]", "{{index .MSI `one`}}", "1", tVal, true}, - {"map[two]", "{{index .MSI `two`}}", "2", tVal, true}, - {"map[NO]", "{{index .MSI `XXX`}}", "0", tVal, true}, - {"map[nil]", "{{index .MSI nil}}", "", tVal, false}, - {"map[``]", "{{index .MSI ``}}", "0", tVal, true}, - {"map[WRONG]", "{{index .MSI 10}}", "", tVal, false}, - {"double index", "{{index .SMSI 1 `eleven`}}", "11", tVal, true}, - {"nil[1]", "{{index nil 1}}", "", tVal, false}, - {"map MI64S", "{{index .MI64S 2}}", "i642", tVal, true}, - {"map MI32S", "{{index .MI32S 2}}", "two", tVal, true}, - {"map MUI64S", "{{index .MUI64S 3}}", "ui643", tVal, true}, - {"map MI8S", "{{index .MI8S 3}}", "i83", tVal, true}, - {"map MUI8S", "{{index .MUI8S 2}}", "u82", tVal, true}, - {"index of an interface field", "{{index .Empty3 0}}", "7", tVal, true}, - - // Slicing. - {"slice[:]", "{{slice .SI}}", "[3 4 5]", tVal, true}, - {"slice[1:]", "{{slice .SI 1}}", "[4 5]", tVal, true}, - {"slice[1:2]", "{{slice .SI 1 2}}", "[4]", tVal, true}, - {"slice[-1:]", "{{slice .SI -1}}", "", tVal, false}, - {"slice[1:-2]", "{{slice .SI 1 -2}}", "", tVal, false}, - {"slice[1:2:-1]", "{{slice .SI 1 2 -1}}", "", tVal, false}, - {"slice[2:1]", "{{slice .SI 2 1}}", "", tVal, false}, - {"slice[2:2:1]", "{{slice .SI 2 2 1}}", "", tVal, false}, - {"out of range", "{{slice .SI 4 5}}", "", tVal, false}, - {"out of range", "{{slice .SI 2 2 5}}", "", tVal, false}, - {"len(s) < indexes < cap(s)", "{{slice .SICap 6 10}}", "[0 0 0 0]", tVal, true}, - {"len(s) < indexes < cap(s)", "{{slice .SICap 6 10 10}}", "[0 0 0 0]", tVal, true}, - {"indexes > cap(s)", "{{slice .SICap 10 11}}", "", tVal, false}, - {"indexes > cap(s)", "{{slice .SICap 6 10 11}}", "", tVal, false}, - {"array[:]", "{{slice .AI}}", "[3 4 5]", tVal, true}, - {"array[1:]", "{{slice .AI 1}}", "[4 5]", tVal, true}, - {"array[1:2]", "{{slice .AI 1 2}}", "[4]", tVal, true}, - {"string[:]", "{{slice .S}}", "xyz", tVal, true}, - {"string[0:1]", "{{slice .S 0 1}}", "x", tVal, true}, - {"string[1:]", "{{slice .S 1}}", "yz", tVal, true}, - {"string[1:2]", "{{slice .S 1 2}}", "y", tVal, true}, - {"out of range", "{{slice .S 1 5}}", "", tVal, false}, - {"3-index slice of string", "{{slice .S 1 2 2}}", "", tVal, false}, - {"slice of an interface field", "{{slice .Empty3 0 1}}", "[7]", tVal, true}, - - // Len. - {"slice", "{{len .SI}}", "3", tVal, true}, - {"map", "{{len .MSI }}", "3", tVal, true}, - {"len of int", "{{len 3}}", "", tVal, false}, - {"len of nothing", "{{len .Empty0}}", "", tVal, false}, - {"len of an interface field", "{{len .Empty3}}", "2", tVal, true}, - - // With. - {"with true", "{{with true}}{{.}}{{end}}", "true", tVal, true}, - {"with false", "{{with false}}{{.}}{{else}}FALSE{{end}}", "FALSE", tVal, true}, - {"with 1", "{{with 1}}{{.}}{{else}}ZERO{{end}}", "1", tVal, true}, - {"with 0", "{{with 0}}{{.}}{{else}}ZERO{{end}}", "ZERO", tVal, true}, - {"with 1.5", "{{with 1.5}}{{.}}{{else}}ZERO{{end}}", "1.5", tVal, true}, - {"with 0.0", "{{with .FloatZero}}{{.}}{{else}}ZERO{{end}}", "ZERO", tVal, true}, - {"with 1.5i", "{{with 1.5i}}{{.}}{{else}}ZERO{{end}}", "(0+1.5i)", tVal, true}, - {"with 0.0i", "{{with .ComplexZero}}{{.}}{{else}}ZERO{{end}}", "ZERO", tVal, true}, - {"with emptystring", "{{with ``}}{{.}}{{else}}EMPTY{{end}}", "EMPTY", tVal, true}, - {"with string", "{{with `notempty`}}{{.}}{{else}}EMPTY{{end}}", "notempty", tVal, true}, - {"with emptyslice", "{{with .SIEmpty}}{{.}}{{else}}EMPTY{{end}}", "EMPTY", tVal, true}, - {"with slice", "{{with .SI}}{{.}}{{else}}EMPTY{{end}}", "[3 4 5]", tVal, true}, - {"with emptymap", "{{with .MSIEmpty}}{{.}}{{else}}EMPTY{{end}}", "EMPTY", tVal, true}, - {"with map", "{{with .MSIone}}{{.}}{{else}}EMPTY{{end}}", "map[one:1]", tVal, true}, - {"with empty interface, struct field", "{{with .Empty4}}{{.V}}{{end}}", "UinEmpty", tVal, true}, - {"with $x int", "{{with $x := .I}}{{$x}}{{end}}", "17", tVal, true}, - {"with $x struct.U.V", "{{with $x := $}}{{$x.U.V}}{{end}}", "v", tVal, true}, - {"with variable and action", "{{with $x := $}}{{$y := $.U.V}}{{$y}}{{end}}", "v", tVal, true}, - {"with on typed nil interface value", "{{with .NonEmptyInterfaceTypedNil}}TRUE{{ end }}", "", tVal, true}, - - // Range. - {"range []int", "{{range .SI}}-{{.}}-{{end}}", "-3--4--5-", tVal, true}, - {"range empty no else", "{{range .SIEmpty}}-{{.}}-{{end}}", "", tVal, true}, - {"range []int else", "{{range .SI}}-{{.}}-{{else}}EMPTY{{end}}", "-3--4--5-", tVal, true}, - {"range empty else", "{{range .SIEmpty}}-{{.}}-{{else}}EMPTY{{end}}", "EMPTY", tVal, true}, - {"range []int break else", "{{range .SI}}-{{.}}-{{break}}NOTREACHED{{else}}EMPTY{{end}}", "-3-", tVal, true}, - {"range []int continue else", "{{range .SI}}-{{.}}-{{continue}}NOTREACHED{{else}}EMPTY{{end}}", "-3--4--5-", tVal, true}, - {"range []bool", "{{range .SB}}-{{.}}-{{end}}", "-true--false-", tVal, true}, - {"range []int method", "{{range .SI | .MAdd .I}}-{{.}}-{{end}}", "-20--21--22-", tVal, true}, - {"range map", "{{range .MSI}}-{{.}}-{{end}}", "-1--3--2-", tVal, true}, - {"range empty map no else", "{{range .MSIEmpty}}-{{.}}-{{end}}", "", tVal, true}, - {"range map else", "{{range .MSI}}-{{.}}-{{else}}EMPTY{{end}}", "-1--3--2-", tVal, true}, - {"range empty map else", "{{range .MSIEmpty}}-{{.}}-{{else}}EMPTY{{end}}", "EMPTY", tVal, true}, - {"range empty interface", "{{range .Empty3}}-{{.}}-{{else}}EMPTY{{end}}", "-7--8-", tVal, true}, - {"range empty nil", "{{range .Empty0}}-{{.}}-{{end}}", "", tVal, true}, - {"range $x SI", "{{range $x := .SI}}<{{$x}}>{{end}}", "<3><4><5>", tVal, true}, - {"range $x $y SI", "{{range $x, $y := .SI}}<{{$x}}={{$y}}>{{end}}", "<0=3><1=4><2=5>", tVal, true}, - {"range $x MSIone", "{{range $x := .MSIone}}<{{$x}}>{{end}}", "<1>", tVal, true}, - {"range $x $y MSIone", "{{range $x, $y := .MSIone}}<{{$x}}={{$y}}>{{end}}", "<one=1>", tVal, true}, - {"range $x PSI", "{{range $x := .PSI}}<{{$x}}>{{end}}", "<21><22><23>", tVal, true}, - {"declare in range", "{{range $x := .PSI}}<{{$foo:=$x}}{{$x}}>{{end}}", "<21><22><23>", tVal, true}, - {"range count", `{{range $i, $x := count 5}}[{{$i}}]{{$x}}{{end}}`, "[0]a[1]b[2]c[3]d[4]e", tVal, true}, - {"range nil count", `{{range $i, $x := count 0}}{{else}}empty{{end}}`, "empty", tVal, true}, - - // Cute examples. - {"or as if true", `{{or .SI "slice is empty"}}`, "[3 4 5]", tVal, true}, - {"or as if false", `{{or .SIEmpty "slice is empty"}}`, "slice is empty", tVal, true}, - - // Error handling. - {"error method, error", "{{.MyError true}}", "", tVal, false}, - {"error method, no error", "{{.MyError false}}", "false", tVal, true}, - - // Numbers - {"decimal", "{{print 1234}}", "1234", tVal, true}, - {"decimal _", "{{print 12_34}}", "1234", tVal, true}, - {"binary", "{{print 0b101}}", "5", tVal, true}, - {"binary _", "{{print 0b_1_0_1}}", "5", tVal, true}, - {"BINARY", "{{print 0B101}}", "5", tVal, true}, - {"octal0", "{{print 0377}}", "255", tVal, true}, - {"octal", "{{print 0o377}}", "255", tVal, true}, - {"octal _", "{{print 0o_3_7_7}}", "255", tVal, true}, - {"OCTAL", "{{print 0O377}}", "255", tVal, true}, - {"hex", "{{print 0x123}}", "291", tVal, true}, - {"hex _", "{{print 0x1_23}}", "291", tVal, true}, - {"HEX", "{{print 0X123ABC}}", "1194684", tVal, true}, - {"float", "{{print 123.4}}", "123.4", tVal, true}, - {"float _", "{{print 0_0_1_2_3.4}}", "123.4", tVal, true}, - {"hex float", "{{print +0x1.ep+2}}", "7.5", tVal, true}, - {"hex float _", "{{print +0x_1.e_0p+0_2}}", "7.5", tVal, true}, - {"HEX float", "{{print +0X1.EP+2}}", "7.5", tVal, true}, - {"print multi", "{{print 1_2_3_4 7.5_00_00_00}}", "1234 7.5", tVal, true}, - {"print multi2", "{{print 1234 0x0_1.e_0p+02}}", "1234 7.5", tVal, true}, - - // Fixed bugs. - // Must separate dot and receiver; otherwise args are evaluated with dot set to variable. - {"bug0", "{{range .MSIone}}{{if $.Method1 .}}X{{end}}{{end}}", "X", tVal, true}, - // Do not loop endlessly in indirect for non-empty interfaces. - // The bug appears with *interface only; looped forever. - {"bug1", "{{.Method0}}", "M0", &iVal, true}, - // Was taking address of interface field, so method set was empty. - {"bug2", "{{$.NonEmptyInterface.Method0}}", "M0", tVal, true}, - // Struct values were not legal in with - mere oversight. - {"bug3", "{{with $}}{{.Method0}}{{end}}", "M0", tVal, true}, - // Nil interface values in if. - {"bug4", "{{if .Empty0}}non-nil{{else}}nil{{end}}", "nil", tVal, true}, - // Stringer. - {"bug5", "{{.Str}}", "foozle", tVal, true}, - {"bug5a", "{{.Err}}", "erroozle", tVal, true}, - // Args need to be indirected and dereferenced sometimes. - {"bug6a", "{{vfunc .V0 .V1}}", "vfunc", tVal, true}, - {"bug6b", "{{vfunc .V0 .V0}}", "vfunc", tVal, true}, - {"bug6c", "{{vfunc .V1 .V0}}", "vfunc", tVal, true}, - {"bug6d", "{{vfunc .V1 .V1}}", "vfunc", tVal, true}, - // Legal parse but illegal execution: non-function should have no arguments. - {"bug7a", "{{3 2}}", "", tVal, false}, - {"bug7b", "{{$x := 1}}{{$x 2}}", "", tVal, false}, - {"bug7c", "{{$x := 1}}{{3 | $x}}", "", tVal, false}, - // Pipelined arg was not being type-checked. - {"bug8a", "{{3|oneArg}}", "", tVal, false}, - {"bug8b", "{{4|dddArg 3}}", "", tVal, false}, - // A bug was introduced that broke map lookups for lower-case names. - {"bug9", "{{.cause}}", "neglect", map[string]string{"cause": "neglect"}, true}, - // Field chain starting with function did not work. - {"bug10", "{{mapOfThree.three}}-{{(mapOfThree).three}}", "3-3", 0, true}, - // Dereferencing nil pointer while evaluating function arguments should not panic. Issue 7333. - {"bug11", "{{valueString .PS}}", "", T{}, false}, - // 0xef gave constant type float64. Issue 8622. - {"bug12xe", "{{printf `%T` 0xef}}", "int", T{}, true}, - {"bug12xE", "{{printf `%T` 0xEE}}", "int", T{}, true}, - {"bug12Xe", "{{printf `%T` 0Xef}}", "int", T{}, true}, - {"bug12XE", "{{printf `%T` 0XEE}}", "int", T{}, true}, - // Chained nodes did not work as arguments. Issue 8473. - {"bug13", "{{print (.Copy).I}}", "17", tVal, true}, - // Didn't protect against nil or literal values in field chains. - {"bug14a", "{{(nil).True}}", "", tVal, false}, - {"bug14b", "{{$x := nil}}{{$x.anything}}", "", tVal, false}, - {"bug14c", `{{$x := (1.0)}}{{$y := ("hello")}}{{$x.anything}}{{$y.true}}`, "", tVal, false}, - // Didn't call validateType on function results. Issue 10800. - {"bug15", "{{valueString returnInt}}", "", tVal, false}, - // Variadic function corner cases. Issue 10946. - {"bug16a", "{{true|printf}}", "", tVal, false}, - {"bug16b", "{{1|printf}}", "", tVal, false}, - {"bug16c", "{{1.1|printf}}", "", tVal, false}, - {"bug16d", "{{'x'|printf}}", "", tVal, false}, - {"bug16e", "{{0i|printf}}", "", tVal, false}, - {"bug16f", "{{true|twoArgs \"xxx\"}}", "", tVal, false}, - {"bug16g", "{{\"aaa\" |twoArgs \"bbb\"}}", "twoArgs=bbbaaa", tVal, true}, - {"bug16h", "{{1|oneArg}}", "", tVal, false}, - {"bug16i", "{{\"aaa\"|oneArg}}", "oneArg=aaa", tVal, true}, - {"bug16j", "{{1+2i|printf \"%v\"}}", "(1+2i)", tVal, true}, - {"bug16k", "{{\"aaa\"|printf }}", "aaa", tVal, true}, - {"bug17a", "{{.NonEmptyInterface.X}}", "x", tVal, true}, - {"bug17b", "-{{.NonEmptyInterface.Method1 1234}}-", "-1234-", tVal, true}, - {"bug17c", "{{len .NonEmptyInterfacePtS}}", "2", tVal, true}, - {"bug17d", "{{index .NonEmptyInterfacePtS 0}}", "a", tVal, true}, - {"bug17e", "{{range .NonEmptyInterfacePtS}}-{{.}}-{{end}}", "-a--b-", tVal, true}, - - // More variadic function corner cases. Some runes would get evaluated - // as constant floats instead of ints. Issue 34483. - {"bug18a", "{{eq . '.'}}", "true", '.', true}, - {"bug18b", "{{eq . 'e'}}", "true", 'e', true}, - {"bug18c", "{{eq . 'P'}}", "true", 'P', true}, -} - -func zeroArgs() string { - return "zeroArgs" -} - -func oneArg(a string) string { - return "oneArg=" + a -} - -func twoArgs(a, b string) string { - return "twoArgs=" + a + b -} - -func dddArg(a int, b ...string) string { - return fmt.Sprintln(a, b) -} - -// count returns a channel that will deliver n sequential 1-letter strings starting at "a" -func count(n int) chan string { - if n == 0 { - return nil - } - c := make(chan string) - go func() { - for i := 0; i < n; i++ { - c <- "abcdefghijklmnop"[i : i+1] - } - close(c) - }() - return c -} - -// vfunc takes a *V and a V -func vfunc(V, *V) string { - return "vfunc" -} - -// valueString takes a string, not a pointer. -func valueString(v string) string { - return "value is ignored" -} - -// returnInt returns an int -func returnInt() int { - return 7 -} - -func add(args ...int) int { - sum := 0 - for _, x := range args { - sum += x - } - return sum -} - -func echo(arg interface{}) interface{} { - return arg -} - -func makemap(arg ...string) map[string]string { - if len(arg)%2 != 0 { - panic("bad makemap") - } - m := make(map[string]string) - for i := 0; i < len(arg); i += 2 { - m[arg[i]] = arg[i+1] - } - return m -} - -func stringer(s fmt.Stringer) string { - return s.String() -} - -func mapOfThree() interface{} { - return map[string]int{"three": 3} -} - -func testExecute(execTests []execTest, template *Template, t *testing.T) { - b := new(bytes.Buffer) - funcs := FuncMap{ - "add": add, - "count": count, - "dddArg": dddArg, - "die": func() bool { panic("die") }, - "echo": echo, - "makemap": makemap, - "mapOfThree": mapOfThree, - "oneArg": oneArg, - "returnInt": returnInt, - "stringer": stringer, - "twoArgs": twoArgs, - "typeOf": typeOf, - "valueString": valueString, - "vfunc": vfunc, - "zeroArgs": zeroArgs, - } - for _, test := range execTests { - var tmpl *Template - var err error - if template == nil { - tmpl, err = New(test.name).Funcs(funcs).Parse(test.input) - } else { - tmpl, err = template.New(test.name).Funcs(funcs).Parse(test.input) - } - if err != nil { - t.Errorf("%s: parse error: %s", test.name, err) - continue - } - b.Reset() - err = tmpl.Execute(b, test.data) - switch { - case !test.ok && err == nil: - t.Errorf("%s: expected error; got none", test.name) - continue - case test.ok && err != nil: - t.Errorf("%s: unexpected execute error: %s", test.name, err) - continue - case !test.ok && err != nil: - // expected error, got one - if *debug { - fmt.Printf("%s: %s\n\t%s\n", test.name, test.input, err) - } - } - result := b.String() - if result != test.output { - t.Errorf("%s: expected\n\t%q\ngot\n\t%q", test.name, test.output, result) - } - } -} - -func TestExecute(t *testing.T) { - testExecute(execTests, nil, t) -} - -var delimPairs = []string{ - "", "", // default - "{{", "}}", // same as default - "<<", ">>", // distinct - "|", "|", // same - "(日)", "(本)", // peculiar -} - -func TestDelims(t *testing.T) { - const hello = "Hello, world" - var value = struct{ Str string }{hello} - for i := 0; i < len(delimPairs); i += 2 { - text := ".Str" - left := delimPairs[i+0] - trueLeft := left - right := delimPairs[i+1] - trueRight := right - if left == "" { // default case - trueLeft = "{{" - } - if right == "" { // default case - trueRight = "}}" - } - text = trueLeft + text + trueRight - // Now add a comment - text += trueLeft + "/*comment*/" + trueRight - // Now add an action containing a string. - text += trueLeft + `"` + trueLeft + `"` + trueRight - // At this point text looks like `{{.Str}}{{/*comment*/}}{{"{{"}}`. - tmpl, err := New("delims").Delims(left, right).Parse(text) - if err != nil { - t.Fatalf("delim %q text %q parse err %s", left, text, err) - } - var b = new(bytes.Buffer) - err = tmpl.Execute(b, value) - if err != nil { - t.Fatalf("delim %q exec err %s", left, err) - } - if b.String() != hello+trueLeft { - t.Errorf("expected %q got %q", hello+trueLeft, b.String()) - } - } -} - -// Check that an error from a method flows back to the top. -func TestExecuteError(t *testing.T) { - b := new(bytes.Buffer) - tmpl := New("error") - _, err := tmpl.Parse("{{.MyError true}}") - if err != nil { - t.Fatalf("parse error: %s", err) - } - err = tmpl.Execute(b, tVal) - if err == nil { - t.Errorf("expected error; got none") - } else if !strings.Contains(err.Error(), myError.Error()) { - if *debug { - fmt.Printf("test execute error: %s\n", err) - } - t.Errorf("expected myError; got %s", err) - } -} - -const execErrorText = `line 1 -line 2 -line 3 -{{template "one" .}} -{{define "one"}}{{template "two" .}}{{end}} -{{define "two"}}{{template "three" .}}{{end}} -{{define "three"}}{{index "hi" $}}{{end}}` - -// Check that an error from a nested template contains all the relevant information. -func TestExecError(t *testing.T) { - tmpl, err := New("top").Parse(execErrorText) - if err != nil { - t.Fatal("parse error:", err) - } - var b bytes.Buffer - err = tmpl.Execute(&b, 5) // 5 is out of range indexing "hi" - if err == nil { - t.Fatal("expected error") - } - const want = `template: top:7:20: executing "three" at <index "hi" $>: error calling index: index out of range: 5` - got := err.Error() - if got != want { - t.Errorf("expected\n%q\ngot\n%q", want, got) - } -} - -type CustomError struct{} - -func (*CustomError) Error() string { return "heyo !" } - -// Check that a custom error can be returned. -func TestExecError_CustomError(t *testing.T) { - failingFunc := func() (string, error) { - return "", &CustomError{} - } - tmpl := Must(New("top").Funcs(FuncMap{ - "err": failingFunc, - }).Parse("{{ err }}")) - - var b bytes.Buffer - err := tmpl.Execute(&b, nil) - - var e *CustomError - if !errors.As(err, &e) { - t.Fatalf("expected custom error; got %s", err) - } -} - -func TestJSEscaping(t *testing.T) { - testCases := []struct { - in, exp string - }{ - {`a`, `a`}, - {`'foo`, `\'foo`}, - {`Go "jump" \`, `Go \"jump\" \\`}, - {`Yukihiro says "今日は世界"`, `Yukihiro says \"今日は世界\"`}, - {"unprintable \uFDFF", `unprintable \uFDFF`}, - {`<html>`, `\u003Chtml\u003E`}, - {`no = in attributes`, `no \u003D in attributes`}, - {`' does not become HTML entity`, `\u0026#x27; does not become HTML entity`}, - } - for _, tc := range testCases { - s := JSEscapeString(tc.in) - if s != tc.exp { - t.Errorf("JS escaping [%s] got [%s] want [%s]", tc.in, s, tc.exp) - } - } -} - -// A nice example: walk a binary tree. - -type Tree struct { - Val int - Left, Right *Tree -} - -// Use different delimiters to test Set.Delims. -// Also test the trimming of leading and trailing spaces. -const treeTemplate = ` - (- define "tree" -) - [ - (- .Val -) - (- with .Left -) - (template "tree" . -) - (- end -) - (- with .Right -) - (- template "tree" . -) - (- end -) - ] - (- end -) -` - -func TestTree(t *testing.T) { - var tree = &Tree{ - 1, - &Tree{ - 2, &Tree{ - 3, - &Tree{ - 4, nil, nil, - }, - nil, - }, - &Tree{ - 5, - &Tree{ - 6, nil, nil, - }, - nil, - }, - }, - &Tree{ - 7, - &Tree{ - 8, - &Tree{ - 9, nil, nil, - }, - nil, - }, - &Tree{ - 10, - &Tree{ - 11, nil, nil, - }, - nil, - }, - }, - } - tmpl, err := New("root").Delims("(", ")").Parse(treeTemplate) - if err != nil { - t.Fatal("parse error:", err) - } - var b bytes.Buffer - const expect = "[1[2[3[4]][5[6]]][7[8[9]][10[11]]]]" - // First by looking up the template. - err = tmpl.Lookup("tree").Execute(&b, tree) - if err != nil { - t.Fatal("exec error:", err) - } - result := b.String() - if result != expect { - t.Errorf("expected %q got %q", expect, result) - } - // Then direct to execution. - b.Reset() - err = tmpl.ExecuteTemplate(&b, "tree", tree) - if err != nil { - t.Fatal("exec error:", err) - } - result = b.String() - if result != expect { - t.Errorf("expected %q got %q", expect, result) - } -} - -func TestExecuteOnNewTemplate(t *testing.T) { - // This is issue 3872. - New("Name").Templates() - // This is issue 11379. - new(Template).Templates() - new(Template).Parse("") - new(Template).New("abc").Parse("") - new(Template).Execute(nil, nil) // returns an error (but does not crash) - new(Template).ExecuteTemplate(nil, "XXX", nil) // returns an error (but does not crash) -} - -const testTemplates = `{{define "one"}}one{{end}}{{define "two"}}two{{end}}` - -func TestMessageForExecuteEmpty(t *testing.T) { - // Test a truly empty template. - tmpl := New("empty") - var b bytes.Buffer - err := tmpl.Execute(&b, 0) - if err == nil { - t.Fatal("expected initial error") - } - got := err.Error() - want := `template: empty: "empty" is an incomplete or empty template` - if got != want { - t.Errorf("expected error %s got %s", want, got) - } - // Add a non-empty template to check that the error is helpful. - tests, err := New("").Parse(testTemplates) - if err != nil { - t.Fatal(err) - } - tmpl.AddParseTree("secondary", tests.Tree) - err = tmpl.Execute(&b, 0) - if err == nil { - t.Fatal("expected second error") - } - got = err.Error() - want = `template: empty: "empty" is an incomplete or empty template` - if got != want { - t.Errorf("expected error %s got %s", want, got) - } - // Make sure we can execute the secondary. - err = tmpl.ExecuteTemplate(&b, "secondary", 0) - if err != nil { - t.Fatal(err) - } -} - -func TestFinalForPrintf(t *testing.T) { - tmpl, err := New("").Parse(`{{"x" | printf}}`) - if err != nil { - t.Fatal(err) - } - var b bytes.Buffer - err = tmpl.Execute(&b, 0) - if err != nil { - t.Fatal(err) - } -} - -type cmpTest struct { - expr string - truth string - ok bool -} - -var cmpTests = []cmpTest{ - {"eq true true", "true", true}, - {"eq true false", "false", true}, - {"eq 1+2i 1+2i", "true", true}, - {"eq 1+2i 1+3i", "false", true}, - {"eq 1.5 1.5", "true", true}, - {"eq 1.5 2.5", "false", true}, - {"eq 1 1", "true", true}, - {"eq 1 2", "false", true}, - {"eq `xy` `xy`", "true", true}, - {"eq `xy` `xyz`", "false", true}, - {"eq .Uthree .Uthree", "true", true}, - {"eq .Uthree .Ufour", "false", true}, - {"eq 3 4 5 6 3", "true", true}, - {"eq 3 4 5 6 7", "false", true}, - {"ne true true", "false", true}, - {"ne true false", "true", true}, - {"ne 1+2i 1+2i", "false", true}, - {"ne 1+2i 1+3i", "true", true}, - {"ne 1.5 1.5", "false", true}, - {"ne 1.5 2.5", "true", true}, - {"ne 1 1", "false", true}, - {"ne 1 2", "true", true}, - {"ne `xy` `xy`", "false", true}, - {"ne `xy` `xyz`", "true", true}, - {"ne .Uthree .Uthree", "false", true}, - {"ne .Uthree .Ufour", "true", true}, - {"lt 1.5 1.5", "false", true}, - {"lt 1.5 2.5", "true", true}, - {"lt 1 1", "false", true}, - {"lt 1 2", "true", true}, - {"lt `xy` `xy`", "false", true}, - {"lt `xy` `xyz`", "true", true}, - {"lt .Uthree .Uthree", "false", true}, - {"lt .Uthree .Ufour", "true", true}, - {"le 1.5 1.5", "true", true}, - {"le 1.5 2.5", "true", true}, - {"le 2.5 1.5", "false", true}, - {"le 1 1", "true", true}, - {"le 1 2", "true", true}, - {"le 2 1", "false", true}, - {"le `xy` `xy`", "true", true}, - {"le `xy` `xyz`", "true", true}, - {"le `xyz` `xy`", "false", true}, - {"le .Uthree .Uthree", "true", true}, - {"le .Uthree .Ufour", "true", true}, - {"le .Ufour .Uthree", "false", true}, - {"gt 1.5 1.5", "false", true}, - {"gt 1.5 2.5", "false", true}, - {"gt 1 1", "false", true}, - {"gt 2 1", "true", true}, - {"gt 1 2", "false", true}, - {"gt `xy` `xy`", "false", true}, - {"gt `xy` `xyz`", "false", true}, - {"gt .Uthree .Uthree", "false", true}, - {"gt .Uthree .Ufour", "false", true}, - {"gt .Ufour .Uthree", "true", true}, - {"ge 1.5 1.5", "true", true}, - {"ge 1.5 2.5", "false", true}, - {"ge 2.5 1.5", "true", true}, - {"ge 1 1", "true", true}, - {"ge 1 2", "false", true}, - {"ge 2 1", "true", true}, - {"ge `xy` `xy`", "true", true}, - {"ge `xy` `xyz`", "false", true}, - {"ge `xyz` `xy`", "true", true}, - {"ge .Uthree .Uthree", "true", true}, - {"ge .Uthree .Ufour", "false", true}, - {"ge .Ufour .Uthree", "true", true}, - // Mixing signed and unsigned integers. - {"eq .Uthree .Three", "true", true}, - {"eq .Three .Uthree", "true", true}, - {"le .Uthree .Three", "true", true}, - {"le .Three .Uthree", "true", true}, - {"ge .Uthree .Three", "true", true}, - {"ge .Three .Uthree", "true", true}, - {"lt .Uthree .Three", "false", true}, - {"lt .Three .Uthree", "false", true}, - {"gt .Uthree .Three", "false", true}, - {"gt .Three .Uthree", "false", true}, - {"eq .Ufour .Three", "false", true}, - {"lt .Ufour .Three", "false", true}, - {"gt .Ufour .Three", "true", true}, - {"eq .NegOne .Uthree", "false", true}, - {"eq .Uthree .NegOne", "false", true}, - {"ne .NegOne .Uthree", "true", true}, - {"ne .Uthree .NegOne", "true", true}, - {"lt .NegOne .Uthree", "true", true}, - {"lt .Uthree .NegOne", "false", true}, - {"le .NegOne .Uthree", "true", true}, - {"le .Uthree .NegOne", "false", true}, - {"gt .NegOne .Uthree", "false", true}, - {"gt .Uthree .NegOne", "true", true}, - {"ge .NegOne .Uthree", "false", true}, - {"ge .Uthree .NegOne", "true", true}, - {"eq (index `x` 0) 'x'", "true", true}, // The example that triggered this rule. - {"eq (index `x` 0) 'y'", "false", true}, - {"eq .V1 .V2", "true", true}, - {"eq .Ptr .Ptr", "true", true}, - {"eq .Ptr .NilPtr", "false", true}, - {"eq .NilPtr .NilPtr", "true", true}, - {"eq .Iface1 .Iface1", "true", true}, - {"eq .Iface1 .NilIface", "false", true}, - {"eq .NilIface .NilIface", "true", true}, - {"eq .NilIface .Iface1", "false", true}, - {"eq .NilIface 0", "false", true}, - {"eq 0 .NilIface", "false", true}, - // Errors - {"eq `xy` 1", "", false}, // Different types. - {"eq 2 2.0", "", false}, // Different types. - {"lt true true", "", false}, // Unordered types. - {"lt 1+0i 1+0i", "", false}, // Unordered types. - {"eq .Ptr 1", "", false}, // Incompatible types. - {"eq .Ptr .NegOne", "", false}, // Incompatible types. - {"eq .Map .Map", "", false}, // Uncomparable types. - {"eq .Map .V1", "", false}, // Uncomparable types. -} - -func TestComparison(t *testing.T) { - b := new(bytes.Buffer) - var cmpStruct = struct { - Uthree, Ufour uint - NegOne, Three int - Ptr, NilPtr *int - Map map[int]int - V1, V2 V - Iface1, NilIface fmt.Stringer - }{ - Uthree: 3, - Ufour: 4, - NegOne: -1, - Three: 3, - Ptr: new(int), - Iface1: b, - } - for _, test := range cmpTests { - text := fmt.Sprintf("{{if %s}}true{{else}}false{{end}}", test.expr) - tmpl, err := New("empty").Parse(text) - if err != nil { - t.Fatalf("%q: %s", test.expr, err) - } - b.Reset() - err = tmpl.Execute(b, &cmpStruct) - if test.ok && err != nil { - t.Errorf("%s errored incorrectly: %s", test.expr, err) - continue - } - if !test.ok && err == nil { - t.Errorf("%s did not error", test.expr) - continue - } - if b.String() != test.truth { - t.Errorf("%s: want %s; got %s", test.expr, test.truth, b.String()) - } - } -} - -func TestMissingMapKey(t *testing.T) { - data := map[string]int{ - "x": 99, - } - tmpl, err := New("t1").Parse("{{.x}} {{.y}}") - if err != nil { - t.Fatal(err) - } - var b bytes.Buffer - // By default, just get "<no value>" - err = tmpl.Execute(&b, data) - if err != nil { - t.Fatal(err) - } - want := "99 <no value>" - got := b.String() - if got != want { - t.Errorf("got %q; expected %q", got, want) - } - // Same if we set the option explicitly to the default. - tmpl.Option("missingkey=default") - b.Reset() - err = tmpl.Execute(&b, data) - if err != nil { - t.Fatal("default:", err) - } - want = "99 <no value>" - got = b.String() - if got != want { - t.Errorf("got %q; expected %q", got, want) - } - // Next we ask for a zero value - tmpl.Option("missingkey=zero") - b.Reset() - err = tmpl.Execute(&b, data) - if err != nil { - t.Fatal("zero:", err) - } - want = "99 0" - got = b.String() - if got != want { - t.Errorf("got %q; expected %q", got, want) - } - // Now we ask for an error. - tmpl.Option("missingkey=error") - err = tmpl.Execute(&b, data) - if err == nil { - t.Errorf("expected error; got none") - } - // same Option, but now a nil interface: ask for an error - err = tmpl.Execute(&b, nil) - t.Log(err) - if err == nil { - t.Errorf("expected error for nil-interface; got none") - } -} - -// Test that the error message for multiline unterminated string -// refers to the line number of the opening quote. -func TestUnterminatedStringError(t *testing.T) { - _, err := New("X").Parse("hello\n\n{{`unterminated\n\n\n\n}}\n some more\n\n") - if err == nil { - t.Fatal("expected error") - } - str := err.Error() - if !strings.Contains(str, "X:3: unterminated raw quoted string") { - t.Fatalf("unexpected error: %s", str) - } -} - -const alwaysErrorText = "always be failing" - -var alwaysError = errors.New(alwaysErrorText) - -type ErrorWriter int - -func (e ErrorWriter) Write(p []byte) (int, error) { - return 0, alwaysError -} - -func TestExecuteGivesExecError(t *testing.T) { - // First, a non-execution error shouldn't be an ExecError. - tmpl, err := New("X").Parse("hello") - if err != nil { - t.Fatal(err) - } - err = tmpl.Execute(ErrorWriter(0), 0) - if err == nil { - t.Fatal("expected error; got none") - } - if err.Error() != alwaysErrorText { - t.Errorf("expected %q error; got %q", alwaysErrorText, err) - } - // This one should be an ExecError. - tmpl, err = New("X").Parse("hello, {{.X.Y}}") - if err != nil { - t.Fatal(err) - } - err = tmpl.Execute(ioutil.Discard, 0) - if err == nil { - t.Fatal("expected error; got none") - } - eerr, ok := err.(ExecError) - if !ok { - t.Fatalf("did not expect ExecError %s", eerr) - } - expect := "field X in type int" - if !strings.Contains(err.Error(), expect) { - t.Errorf("expected %q; got %q", expect, err) - } -} - -func funcNameTestFunc() int { - return 0 -} - -func TestGoodFuncNames(t *testing.T) { - names := []string{ - "_", - "a", - "a1", - "a1", - "Ӵ", - } - for _, name := range names { - tmpl := New("X").Funcs( - FuncMap{ - name: funcNameTestFunc, - }, - ) - if tmpl == nil { - t.Fatalf("nil result for %q", name) - } - } -} - -func TestBadFuncNames(t *testing.T) { - names := []string{ - "", - "2", - "a-b", - } - for _, name := range names { - testBadFuncName(name, t) - } -} - -func testBadFuncName(name string, t *testing.T) { - t.Helper() - defer func() { - recover() - }() - New("X").Funcs( - FuncMap{ - name: funcNameTestFunc, - }, - ) - // If we get here, the name did not cause a panic, which is how Funcs - // reports an error. - t.Errorf("%q succeeded incorrectly as function name", name) -} - -func TestBlock(t *testing.T) { - const ( - input = `a({{block "inner" .}}bar({{.}})baz{{end}})b` - want = `a(bar(hello)baz)b` - overlay = `{{define "inner"}}foo({{.}})bar{{end}}` - want2 = `a(foo(goodbye)bar)b` - ) - tmpl, err := New("outer").Parse(input) - if err != nil { - t.Fatal(err) - } - tmpl2, err := Must(tmpl.Clone()).Parse(overlay) - if err != nil { - t.Fatal(err) - } - - var buf bytes.Buffer - if err := tmpl.Execute(&buf, "hello"); err != nil { - t.Fatal(err) - } - if got := buf.String(); got != want { - t.Errorf("got %q, want %q", got, want) - } - - buf.Reset() - if err := tmpl2.Execute(&buf, "goodbye"); err != nil { - t.Fatal(err) - } - if got := buf.String(); got != want2 { - t.Errorf("got %q, want %q", got, want2) - } -} - -func TestEvalFieldErrors(t *testing.T) { - tests := []struct { - name, src string - value interface{} - want string - }{ - { - // Check that calling an invalid field on nil pointer - // prints a field error instead of a distracting nil - // pointer error. https://golang.org/issue/15125 - "MissingFieldOnNil", - "{{.MissingField}}", - (*T)(nil), - "can't evaluate field MissingField in type *template.T", - }, - { - "MissingFieldOnNonNil", - "{{.MissingField}}", - &T{}, - "can't evaluate field MissingField in type *template.T", - }, - { - "ExistingFieldOnNil", - "{{.X}}", - (*T)(nil), - "nil pointer evaluating *template.T.X", - }, - { - "MissingKeyOnNilMap", - "{{.MissingKey}}", - (*map[string]string)(nil), - "nil pointer evaluating *map[string]string.MissingKey", - }, - { - "MissingKeyOnNilMapPtr", - "{{.MissingKey}}", - (*map[string]string)(nil), - "nil pointer evaluating *map[string]string.MissingKey", - }, - { - "MissingKeyOnMapPtrToNil", - "{{.MissingKey}}", - &map[string]string{}, - "<nil>", - }, - } - for _, tc := range tests { - t.Run(tc.name, func(t *testing.T) { - tmpl := Must(New("tmpl").Parse(tc.src)) - err := tmpl.Execute(ioutil.Discard, tc.value) - got := "<nil>" - if err != nil { - got = err.Error() - } - if !strings.HasSuffix(got, tc.want) { - t.Fatalf("got error %q, want %q", got, tc.want) - } - }) - } -} - -func TestMaxExecDepth(t *testing.T) { - if testing.Short() { - t.Skip("skipping in -short mode") - } - tmpl := Must(New("tmpl").Parse(`{{template "tmpl" .}}`)) - err := tmpl.Execute(ioutil.Discard, nil) - got := "<nil>" - if err != nil { - got = err.Error() - } - const want = "exceeded maximum template depth" - if !strings.Contains(got, want) { - t.Errorf("got error %q; want %q", got, want) - } -} - -func TestAddrOfIndex(t *testing.T) { - // golang.org/issue/14916. - // Before index worked on reflect.Values, the .String could not be - // found on the (incorrectly unaddressable) V value, - // in contrast to range, which worked fine. - // Also testing that passing a reflect.Value to tmpl.Execute works. - texts := []string{ - `{{range .}}{{.String}}{{end}}`, - `{{with index . 0}}{{.String}}{{end}}`, - } - for _, text := range texts { - tmpl := Must(New("tmpl").Parse(text)) - var buf bytes.Buffer - err := tmpl.Execute(&buf, reflect.ValueOf([]V{{1}})) - if err != nil { - t.Fatalf("%s: Execute: %v", text, err) - } - if buf.String() != "<1>" { - t.Fatalf("%s: template output = %q, want %q", text, &buf, "<1>") - } - } -} - -func TestInterfaceValues(t *testing.T) { - // golang.org/issue/17714. - // Before index worked on reflect.Values, interface values - // were always implicitly promoted to the underlying value, - // except that nil interfaces were promoted to the zero reflect.Value. - // Eliminating a round trip to interface{} and back to reflect.Value - // eliminated this promotion, breaking these cases. - tests := []struct { - text string - out string - }{ - {`{{index .Nil 1}}`, "ERROR: index of untyped nil"}, - {`{{index .Slice 2}}`, "2"}, - {`{{index .Slice .Two}}`, "2"}, - {`{{call .Nil 1}}`, "ERROR: call of nil"}, - {`{{call .PlusOne 1}}`, "2"}, - {`{{call .PlusOne .One}}`, "2"}, - {`{{and (index .Slice 0) true}}`, "0"}, - {`{{and .Zero true}}`, "0"}, - {`{{and (index .Slice 1) false}}`, "false"}, - {`{{and .One false}}`, "false"}, - {`{{or (index .Slice 0) false}}`, "false"}, - {`{{or .Zero false}}`, "false"}, - {`{{or (index .Slice 1) true}}`, "1"}, - {`{{or .One true}}`, "1"}, - {`{{not (index .Slice 0)}}`, "true"}, - {`{{not .Zero}}`, "true"}, - {`{{not (index .Slice 1)}}`, "false"}, - {`{{not .One}}`, "false"}, - {`{{eq (index .Slice 0) .Zero}}`, "true"}, - {`{{eq (index .Slice 1) .One}}`, "true"}, - {`{{ne (index .Slice 0) .Zero}}`, "false"}, - {`{{ne (index .Slice 1) .One}}`, "false"}, - {`{{ge (index .Slice 0) .One}}`, "false"}, - {`{{ge (index .Slice 1) .Zero}}`, "true"}, - {`{{gt (index .Slice 0) .One}}`, "false"}, - {`{{gt (index .Slice 1) .Zero}}`, "true"}, - {`{{le (index .Slice 0) .One}}`, "true"}, - {`{{le (index .Slice 1) .Zero}}`, "false"}, - {`{{lt (index .Slice 0) .One}}`, "true"}, - {`{{lt (index .Slice 1) .Zero}}`, "false"}, - } - - for _, tt := range tests { - tmpl := Must(New("tmpl").Parse(tt.text)) - var buf bytes.Buffer - err := tmpl.Execute(&buf, map[string]interface{}{ - "PlusOne": func(n int) int { - return n + 1 - }, - "Slice": []int{0, 1, 2, 3}, - "One": 1, - "Two": 2, - "Nil": nil, - "Zero": 0, - }) - if strings.HasPrefix(tt.out, "ERROR:") { - e := strings.TrimSpace(strings.TrimPrefix(tt.out, "ERROR:")) - if err == nil || !strings.Contains(err.Error(), e) { - t.Errorf("%s: Execute: %v, want error %q", tt.text, err, e) - } - continue - } - if err != nil { - t.Errorf("%s: Execute: %v", tt.text, err) - continue - } - if buf.String() != tt.out { - t.Errorf("%s: template output = %q, want %q", tt.text, &buf, tt.out) - } - } -} - -// Check that panics during calls are recovered and returned as errors. -func TestExecutePanicDuringCall(t *testing.T) { - funcs := map[string]interface{}{ - "doPanic": func() string { - panic("custom panic string") - }, - } - tests := []struct { - name string - input string - data interface{} - wantErr string - }{ - { - "direct func call panics", - "{{doPanic}}", (*T)(nil), - `template: t:1:2: executing "t" at <doPanic>: error calling doPanic: custom panic string`, - }, - { - "indirect func call panics", - "{{call doPanic}}", (*T)(nil), - `template: t:1:7: executing "t" at <doPanic>: error calling doPanic: custom panic string`, - }, - { - "direct method call panics", - "{{.GetU}}", (*T)(nil), - `template: t:1:2: executing "t" at <.GetU>: error calling GetU: runtime error: invalid memory address or nil pointer dereference`, - }, - { - "indirect method call panics", - "{{call .GetU}}", (*T)(nil), - `template: t:1:7: executing "t" at <.GetU>: error calling GetU: runtime error: invalid memory address or nil pointer dereference`, - }, - { - "func field call panics", - "{{call .PanicFunc}}", tVal, - `template: t:1:2: executing "t" at <call .PanicFunc>: error calling call: test panic`, - }, - { - "method call on nil interface", - "{{.NonEmptyInterfaceNil.Method0}}", tVal, - `template: t:1:23: executing "t" at <.NonEmptyInterfaceNil.Method0>: nil pointer evaluating template.I.Method0`, - }, - } - for _, tc := range tests { - b := new(bytes.Buffer) - tmpl, err := New("t").Funcs(funcs).Parse(tc.input) - if err != nil { - t.Fatalf("parse error: %s", err) - } - err = tmpl.Execute(b, tc.data) - if err == nil { - t.Errorf("%s: expected error; got none", tc.name) - } else if !strings.Contains(err.Error(), tc.wantErr) { - if *debug { - fmt.Printf("%s: test execute error: %s\n", tc.name, err) - } - t.Errorf("%s: expected error:\n%s\ngot:\n%s", tc.name, tc.wantErr, err) - } - } -} - -// Issue 31810. Check that a parenthesized first argument behaves properly. -func TestIssue31810(t *testing.T) { - // A simple value with no arguments is fine. - var b bytes.Buffer - const text = "{{ (.) }}" - tmpl, err := New("").Parse(text) - if err != nil { - t.Error(err) - } - err = tmpl.Execute(&b, "result") - if err != nil { - t.Error(err) - } - if b.String() != "result" { - t.Errorf("%s got %q, expected %q", text, b.String(), "result") - } - - // Even a plain function fails - need to use call. - f := func() string { return "result" } - b.Reset() - err = tmpl.Execute(&b, f) - if err == nil { - t.Error("expected error with no call, got none") - } - - // Works if the function is explicitly called. - const textCall = "{{ (call .) }}" - tmpl, err = New("").Parse(textCall) - b.Reset() - err = tmpl.Execute(&b, f) - if err != nil { - t.Error(err) - } - if b.String() != "result" { - t.Errorf("%s got %q, expected %q", textCall, b.String(), "result") - } -} - -// Issue 43065, range over send only channel -func TestIssue43065(t *testing.T) { - var b bytes.Buffer - tmp := Must(New("").Parse(`{{range .}}{{end}}`)) - ch := make(chan<- int) - err := tmp.Execute(&b, ch) - if err == nil { - t.Error("expected err got nil") - } else if !strings.Contains(err.Error(), "range over send-only channel") { - t.Errorf("%s", err) - } -} - -// Issue 39807: data race in html/template & text/template -func TestIssue39807(t *testing.T) { - var wg sync.WaitGroup - - tplFoo, err := New("foo").Parse(`{{ template "bar" . }}`) - if err != nil { - t.Error(err) - } - - tplBar, err := New("bar").Parse("bar") - if err != nil { - t.Error(err) - } - - gofuncs := 10 - numTemplates := 10 - - for i := 1; i <= gofuncs; i++ { - wg.Add(1) - go func() { - defer wg.Done() - for j := 0; j < numTemplates; j++ { - _, err := tplFoo.AddParseTree(tplBar.Name(), tplBar.Tree) - if err != nil { - t.Error(err) - } - err = tplFoo.Execute(ioutil.Discard, nil) - if err != nil { - t.Error(err) - } - } - }() - } - - wg.Wait() -}
diff --git a/go.dev/cmd/internal/text/template/funcs.go b/go.dev/cmd/internal/text/template/funcs.go deleted file mode 100644 index 11e2e90..0000000 --- a/go.dev/cmd/internal/text/template/funcs.go +++ /dev/null
@@ -1,753 +0,0 @@ -// Copyright 2011 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package template - -import ( - "bytes" - "errors" - "fmt" - "io" - "net/url" - "reflect" - "strings" - "sync" - "unicode" - "unicode/utf8" -) - -// FuncMap is the type of the map defining the mapping from names to functions. -// Each function must have either a single return value, or two return values of -// which the second has type error. In that case, if the second (error) -// return value evaluates to non-nil during execution, execution terminates and -// Execute returns that error. -// -// Errors returned by Execute wrap the underlying error; call errors.As to -// uncover them. -// -// When template execution invokes a function with an argument list, that list -// must be assignable to the function's parameter types. Functions meant to -// apply to arguments of arbitrary type can use parameters of type interface{} or -// of type reflect.Value. Similarly, functions meant to return a result of arbitrary -// type can return interface{} or reflect.Value. -type FuncMap map[string]interface{} - -// builtins returns the FuncMap. -// It is not a global variable so the linker can dead code eliminate -// more when this isn't called. See golang.org/issue/36021. -// TODO: revert this back to a global map once golang.org/issue/2559 is fixed. -func builtins() FuncMap { - return FuncMap{ - "and": and, - "call": call, - "html": HTMLEscaper, - "index": index, - "slice": slice, - "js": JSEscaper, - "len": length, - "not": not, - "or": or, - "print": fmt.Sprint, - "printf": fmt.Sprintf, - "println": fmt.Sprintln, - "urlquery": URLQueryEscaper, - - // Comparisons - "eq": eq, // == - "ge": ge, // >= - "gt": gt, // > - "le": le, // <= - "lt": lt, // < - "ne": ne, // != - } -} - -var builtinFuncsOnce struct { - sync.Once - v map[string]reflect.Value -} - -// builtinFuncsOnce lazily computes & caches the builtinFuncs map. -// TODO: revert this back to a global map once golang.org/issue/2559 is fixed. -func builtinFuncs() map[string]reflect.Value { - builtinFuncsOnce.Do(func() { - builtinFuncsOnce.v = createValueFuncs(builtins()) - }) - return builtinFuncsOnce.v -} - -// createValueFuncs turns a FuncMap into a map[string]reflect.Value -func createValueFuncs(funcMap FuncMap) map[string]reflect.Value { - m := make(map[string]reflect.Value) - addValueFuncs(m, funcMap) - return m -} - -// addValueFuncs adds to values the functions in funcs, converting them to reflect.Values. -func addValueFuncs(out map[string]reflect.Value, in FuncMap) { - for name, fn := range in { - if !goodName(name) { - panic(fmt.Errorf("function name %q is not a valid identifier", name)) - } - v := reflect.ValueOf(fn) - if v.Kind() != reflect.Func { - panic("value for " + name + " not a function") - } - if !goodFunc(v.Type()) { - panic(fmt.Errorf("can't install method/function %q with %d results", name, v.Type().NumOut())) - } - out[name] = v - } -} - -// addFuncs adds to values the functions in funcs. It does no checking of the input - -// call addValueFuncs first. -func addFuncs(out, in FuncMap) { - for name, fn := range in { - out[name] = fn - } -} - -// goodFunc reports whether the function or method has the right result signature. -func goodFunc(typ reflect.Type) bool { - // We allow functions with 1 result or 2 results where the second is an error. - switch { - case typ.NumOut() == 1: - return true - case typ.NumOut() == 2 && typ.Out(1) == errorType: - return true - } - return false -} - -// goodName reports whether the function name is a valid identifier. -func goodName(name string) bool { - if name == "" { - return false - } - for i, r := range name { - switch { - case r == '_': - case i == 0 && !unicode.IsLetter(r): - return false - case !unicode.IsLetter(r) && !unicode.IsDigit(r): - return false - } - } - return true -} - -// findFunction looks for a function in the template, and global map. -func findFunction(name string, tmpl *Template) (v reflect.Value, isBuiltin, ok bool) { - if tmpl != nil && tmpl.common != nil { - tmpl.muFuncs.RLock() - defer tmpl.muFuncs.RUnlock() - if fn := tmpl.execFuncs[name]; fn.IsValid() { - return fn, false, true - } - } - if fn := builtinFuncs()[name]; fn.IsValid() { - return fn, true, true - } - return reflect.Value{}, false, false -} - -// prepareArg checks if value can be used as an argument of type argType, and -// converts an invalid value to appropriate zero if possible. -func prepareArg(value reflect.Value, argType reflect.Type) (reflect.Value, error) { - if !value.IsValid() { - if !canBeNil(argType) { - return reflect.Value{}, fmt.Errorf("value is nil; should be of type %s", argType) - } - value = reflect.Zero(argType) - } - if value.Type().AssignableTo(argType) { - return value, nil - } - if intLike(value.Kind()) && intLike(argType.Kind()) && value.Type().ConvertibleTo(argType) { - value = value.Convert(argType) - return value, nil - } - return reflect.Value{}, fmt.Errorf("value has type %s; should be %s", value.Type(), argType) -} - -func intLike(typ reflect.Kind) bool { - switch typ { - case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: - return true - case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: - return true - } - return false -} - -// indexArg checks if a reflect.Value can be used as an index, and converts it to int if possible. -func indexArg(index reflect.Value, cap int) (int, error) { - var x int64 - switch index.Kind() { - case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: - x = index.Int() - case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: - x = int64(index.Uint()) - case reflect.Invalid: - return 0, fmt.Errorf("cannot index slice/array with nil") - default: - return 0, fmt.Errorf("cannot index slice/array with type %s", index.Type()) - } - if x < 0 || int(x) < 0 || int(x) > cap { - return 0, fmt.Errorf("index out of range: %d", x) - } - return int(x), nil -} - -// Indexing. - -// index returns the result of indexing its first argument by the following -// arguments. Thus "index x 1 2 3" is, in Go syntax, x[1][2][3]. Each -// indexed item must be a map, slice, or array. -func index(item reflect.Value, indexes ...reflect.Value) (reflect.Value, error) { - item = indirectInterface(item) - if !item.IsValid() { - return reflect.Value{}, fmt.Errorf("index of untyped nil") - } - for _, index := range indexes { - index = indirectInterface(index) - var isNil bool - if item, isNil = indirect(item); isNil { - return reflect.Value{}, fmt.Errorf("index of nil pointer") - } - switch item.Kind() { - case reflect.Array, reflect.Slice, reflect.String: - x, err := indexArg(index, item.Len()) - if err != nil { - return reflect.Value{}, err - } - item = item.Index(x) - case reflect.Map: - index, err := prepareArg(index, item.Type().Key()) - if err != nil { - return reflect.Value{}, err - } - if x := item.MapIndex(index); x.IsValid() { - item = x - } else { - item = reflect.Zero(item.Type().Elem()) - } - case reflect.Invalid: - // the loop holds invariant: item.IsValid() - panic("unreachable") - default: - return reflect.Value{}, fmt.Errorf("can't index item of type %s", item.Type()) - } - } - return item, nil -} - -// Slicing. - -// slice returns the result of slicing its first argument by the remaining -// arguments. Thus "slice x 1 2" is, in Go syntax, x[1:2], while "slice x" -// is x[:], "slice x 1" is x[1:], and "slice x 1 2 3" is x[1:2:3]. The first -// argument must be a string, slice, or array. -func slice(item reflect.Value, indexes ...reflect.Value) (reflect.Value, error) { - item = indirectInterface(item) - if !item.IsValid() { - return reflect.Value{}, fmt.Errorf("slice of untyped nil") - } - if len(indexes) > 3 { - return reflect.Value{}, fmt.Errorf("too many slice indexes: %d", len(indexes)) - } - var cap int - switch item.Kind() { - case reflect.String: - if len(indexes) == 3 { - return reflect.Value{}, fmt.Errorf("cannot 3-index slice a string") - } - cap = item.Len() - case reflect.Array, reflect.Slice: - cap = item.Cap() - default: - return reflect.Value{}, fmt.Errorf("can't slice item of type %s", item.Type()) - } - // set default values for cases item[:], item[i:]. - idx := [3]int{0, item.Len()} - for i, index := range indexes { - x, err := indexArg(index, cap) - if err != nil { - return reflect.Value{}, err - } - idx[i] = x - } - // given item[i:j], make sure i <= j. - if idx[0] > idx[1] { - return reflect.Value{}, fmt.Errorf("invalid slice index: %d > %d", idx[0], idx[1]) - } - if len(indexes) < 3 { - return item.Slice(idx[0], idx[1]), nil - } - // given item[i:j:k], make sure i <= j <= k. - if idx[1] > idx[2] { - return reflect.Value{}, fmt.Errorf("invalid slice index: %d > %d", idx[1], idx[2]) - } - return item.Slice3(idx[0], idx[1], idx[2]), nil -} - -// Length - -// length returns the length of the item, with an error if it has no defined length. -func length(item reflect.Value) (int, error) { - item, isNil := indirect(item) - if isNil { - return 0, fmt.Errorf("len of nil pointer") - } - switch item.Kind() { - case reflect.Array, reflect.Chan, reflect.Map, reflect.Slice, reflect.String: - return item.Len(), nil - } - return 0, fmt.Errorf("len of type %s", item.Type()) -} - -// Function invocation - -// call returns the result of evaluating the first argument as a function. -// The function must return 1 result, or 2 results, the second of which is an error. -func call(fn reflect.Value, args ...reflect.Value) (reflect.Value, error) { - fn = indirectInterface(fn) - if !fn.IsValid() { - return reflect.Value{}, fmt.Errorf("call of nil") - } - typ := fn.Type() - if typ.Kind() != reflect.Func { - return reflect.Value{}, fmt.Errorf("non-function of type %s", typ) - } - if !goodFunc(typ) { - return reflect.Value{}, fmt.Errorf("function called with %d args; should be 1 or 2", typ.NumOut()) - } - numIn := typ.NumIn() - var dddType reflect.Type - if typ.IsVariadic() { - if len(args) < numIn-1 { - return reflect.Value{}, fmt.Errorf("wrong number of args: got %d want at least %d", len(args), numIn-1) - } - dddType = typ.In(numIn - 1).Elem() - } else { - if len(args) != numIn { - return reflect.Value{}, fmt.Errorf("wrong number of args: got %d want %d", len(args), numIn) - } - } - argv := make([]reflect.Value, len(args)) - for i, arg := range args { - arg = indirectInterface(arg) - // Compute the expected type. Clumsy because of variadics. - argType := dddType - if !typ.IsVariadic() || i < numIn-1 { - argType = typ.In(i) - } - - var err error - if argv[i], err = prepareArg(arg, argType); err != nil { - return reflect.Value{}, fmt.Errorf("arg %d: %w", i, err) - } - } - return safeCall(fn, argv) -} - -// safeCall runs fun.Call(args), and returns the resulting value and error, if -// any. If the call panics, the panic value is returned as an error. -func safeCall(fun reflect.Value, args []reflect.Value) (val reflect.Value, err error) { - defer func() { - if r := recover(); r != nil { - if e, ok := r.(error); ok { - err = e - } else { - err = fmt.Errorf("%v", r) - } - } - }() - ret := fun.Call(args) - if len(ret) == 2 && !ret[1].IsNil() { - return ret[0], ret[1].Interface().(error) - } - return ret[0], nil -} - -// Boolean logic. - -func truth(arg reflect.Value) bool { - t, _ := isTrue(indirectInterface(arg)) - return t -} - -// and computes the Boolean AND of its arguments, returning -// the first false argument it encounters, or the last argument. -func and(arg0 reflect.Value, args ...reflect.Value) reflect.Value { - panic("unreachable") // implemented as a special case in evalCall -} - -// or computes the Boolean OR of its arguments, returning -// the first true argument it encounters, or the last argument. -func or(arg0 reflect.Value, args ...reflect.Value) reflect.Value { - panic("unreachable") // implemented as a special case in evalCall -} - -// not returns the Boolean negation of its argument. -func not(arg reflect.Value) bool { - return !truth(arg) -} - -// Comparison. - -// TODO: Perhaps allow comparison between signed and unsigned integers. - -var ( - errBadComparisonType = errors.New("invalid type for comparison") - errBadComparison = errors.New("incompatible types for comparison") - errNoComparison = errors.New("missing argument for comparison") -) - -type kind int - -const ( - invalidKind kind = iota - boolKind - complexKind - intKind - floatKind - stringKind - uintKind -) - -func basicKind(v reflect.Value) (kind, error) { - switch v.Kind() { - case reflect.Bool: - return boolKind, nil - case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: - return intKind, nil - case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: - return uintKind, nil - case reflect.Float32, reflect.Float64: - return floatKind, nil - case reflect.Complex64, reflect.Complex128: - return complexKind, nil - case reflect.String: - return stringKind, nil - } - return invalidKind, errBadComparisonType -} - -// eq evaluates the comparison a == b || a == c || ... -func eq(arg1 reflect.Value, arg2 ...reflect.Value) (bool, error) { - arg1 = indirectInterface(arg1) - if arg1 != zero { - if t1 := arg1.Type(); !t1.Comparable() { - return false, fmt.Errorf("uncomparable type %s: %v", t1, arg1) - } - } - if len(arg2) == 0 { - return false, errNoComparison - } - k1, _ := basicKind(arg1) - for _, arg := range arg2 { - arg = indirectInterface(arg) - k2, _ := basicKind(arg) - truth := false - if k1 != k2 { - // Special case: Can compare integer values regardless of type's sign. - switch { - case k1 == intKind && k2 == uintKind: - truth = arg1.Int() >= 0 && uint64(arg1.Int()) == arg.Uint() - case k1 == uintKind && k2 == intKind: - truth = arg.Int() >= 0 && arg1.Uint() == uint64(arg.Int()) - default: - if arg1 != zero && arg != zero { - return false, errBadComparison - } - } - } else { - switch k1 { - case boolKind: - truth = arg1.Bool() == arg.Bool() - case complexKind: - truth = arg1.Complex() == arg.Complex() - case floatKind: - truth = arg1.Float() == arg.Float() - case intKind: - truth = arg1.Int() == arg.Int() - case stringKind: - truth = arg1.String() == arg.String() - case uintKind: - truth = arg1.Uint() == arg.Uint() - default: - if arg == zero || arg1 == zero { - truth = arg1 == arg - } else { - if t2 := arg.Type(); !t2.Comparable() { - return false, fmt.Errorf("uncomparable type %s: %v", t2, arg) - } - truth = arg1.Interface() == arg.Interface() - } - } - } - if truth { - return true, nil - } - } - return false, nil -} - -// ne evaluates the comparison a != b. -func ne(arg1, arg2 reflect.Value) (bool, error) { - // != is the inverse of ==. - equal, err := eq(arg1, arg2) - return !equal, err -} - -// lt evaluates the comparison a < b. -func lt(arg1, arg2 reflect.Value) (bool, error) { - arg1 = indirectInterface(arg1) - k1, err := basicKind(arg1) - if err != nil { - return false, err - } - arg2 = indirectInterface(arg2) - k2, err := basicKind(arg2) - if err != nil { - return false, err - } - truth := false - if k1 != k2 { - // Special case: Can compare integer values regardless of type's sign. - switch { - case k1 == intKind && k2 == uintKind: - truth = arg1.Int() < 0 || uint64(arg1.Int()) < arg2.Uint() - case k1 == uintKind && k2 == intKind: - truth = arg2.Int() >= 0 && arg1.Uint() < uint64(arg2.Int()) - default: - return false, errBadComparison - } - } else { - switch k1 { - case boolKind, complexKind: - return false, errBadComparisonType - case floatKind: - truth = arg1.Float() < arg2.Float() - case intKind: - truth = arg1.Int() < arg2.Int() - case stringKind: - truth = arg1.String() < arg2.String() - case uintKind: - truth = arg1.Uint() < arg2.Uint() - default: - panic("invalid kind") - } - } - return truth, nil -} - -// le evaluates the comparison <= b. -func le(arg1, arg2 reflect.Value) (bool, error) { - // <= is < or ==. - lessThan, err := lt(arg1, arg2) - if lessThan || err != nil { - return lessThan, err - } - return eq(arg1, arg2) -} - -// gt evaluates the comparison a > b. -func gt(arg1, arg2 reflect.Value) (bool, error) { - // > is the inverse of <=. - lessOrEqual, err := le(arg1, arg2) - if err != nil { - return false, err - } - return !lessOrEqual, nil -} - -// ge evaluates the comparison a >= b. -func ge(arg1, arg2 reflect.Value) (bool, error) { - // >= is the inverse of <. - lessThan, err := lt(arg1, arg2) - if err != nil { - return false, err - } - return !lessThan, nil -} - -// HTML escaping. - -var ( - htmlQuot = []byte(""") // shorter than """ - htmlApos = []byte("'") // shorter than "'" and apos was not in HTML until HTML5 - htmlAmp = []byte("&") - htmlLt = []byte("<") - htmlGt = []byte(">") - htmlNull = []byte("\uFFFD") -) - -// HTMLEscape writes to w the escaped HTML equivalent of the plain text data b. -func HTMLEscape(w io.Writer, b []byte) { - last := 0 - for i, c := range b { - var html []byte - switch c { - case '\000': - html = htmlNull - case '"': - html = htmlQuot - case '\'': - html = htmlApos - case '&': - html = htmlAmp - case '<': - html = htmlLt - case '>': - html = htmlGt - default: - continue - } - w.Write(b[last:i]) - w.Write(html) - last = i + 1 - } - w.Write(b[last:]) -} - -// HTMLEscapeString returns the escaped HTML equivalent of the plain text data s. -func HTMLEscapeString(s string) string { - // Avoid allocation if we can. - if !strings.ContainsAny(s, "'\"&<>\000") { - return s - } - var b bytes.Buffer - HTMLEscape(&b, []byte(s)) - return b.String() -} - -// HTMLEscaper returns the escaped HTML equivalent of the textual -// representation of its arguments. -func HTMLEscaper(args ...interface{}) string { - return HTMLEscapeString(evalArgs(args)) -} - -// JavaScript escaping. - -var ( - jsLowUni = []byte(`\u00`) - hex = []byte("0123456789ABCDEF") - - jsBackslash = []byte(`\\`) - jsApos = []byte(`\'`) - jsQuot = []byte(`\"`) - jsLt = []byte(`\u003C`) - jsGt = []byte(`\u003E`) - jsAmp = []byte(`\u0026`) - jsEq = []byte(`\u003D`) -) - -// JSEscape writes to w the escaped JavaScript equivalent of the plain text data b. -func JSEscape(w io.Writer, b []byte) { - last := 0 - for i := 0; i < len(b); i++ { - c := b[i] - - if !jsIsSpecial(rune(c)) { - // fast path: nothing to do - continue - } - w.Write(b[last:i]) - - if c < utf8.RuneSelf { - // Quotes, slashes and angle brackets get quoted. - // Control characters get written as \u00XX. - switch c { - case '\\': - w.Write(jsBackslash) - case '\'': - w.Write(jsApos) - case '"': - w.Write(jsQuot) - case '<': - w.Write(jsLt) - case '>': - w.Write(jsGt) - case '&': - w.Write(jsAmp) - case '=': - w.Write(jsEq) - default: - w.Write(jsLowUni) - t, b := c>>4, c&0x0f - w.Write(hex[t : t+1]) - w.Write(hex[b : b+1]) - } - } else { - // Unicode rune. - r, size := utf8.DecodeRune(b[i:]) - if unicode.IsPrint(r) { - w.Write(b[i : i+size]) - } else { - fmt.Fprintf(w, "\\u%04X", r) - } - i += size - 1 - } - last = i + 1 - } - w.Write(b[last:]) -} - -// JSEscapeString returns the escaped JavaScript equivalent of the plain text data s. -func JSEscapeString(s string) string { - // Avoid allocation if we can. - if strings.IndexFunc(s, jsIsSpecial) < 0 { - return s - } - var b bytes.Buffer - JSEscape(&b, []byte(s)) - return b.String() -} - -func jsIsSpecial(r rune) bool { - switch r { - case '\\', '\'', '"', '<', '>', '&', '=': - return true - } - return r < ' ' || utf8.RuneSelf <= r -} - -// JSEscaper returns the escaped JavaScript equivalent of the textual -// representation of its arguments. -func JSEscaper(args ...interface{}) string { - return JSEscapeString(evalArgs(args)) -} - -// URLQueryEscaper returns the escaped value of the textual representation of -// its arguments in a form suitable for embedding in a URL query. -func URLQueryEscaper(args ...interface{}) string { - return url.QueryEscape(evalArgs(args)) -} - -// evalArgs formats the list of arguments into a string. It is therefore equivalent to -// fmt.Sprint(args...) -// except that each argument is indirected (if a pointer), as required, -// using the same rules as the default string evaluation during template -// execution. -func evalArgs(args []interface{}) string { - ok := false - var s string - // Fast path for simple common case. - if len(args) == 1 { - s, ok = args[0].(string) - } - if !ok { - for i, arg := range args { - a, ok := printableValue(reflect.ValueOf(arg)) - if ok { - args[i] = a - } // else let fmt do its thing - } - s = fmt.Sprint(args...) - } - return s -}
diff --git a/go.dev/cmd/internal/text/template/funcs.go.save b/go.dev/cmd/internal/text/template/funcs.go.save deleted file mode 100644 index c3f6d47..0000000 --- a/go.dev/cmd/internal/text/template/funcs.go.save +++ /dev/null
@@ -1,771 +0,0 @@ -// Copyright 2011 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package template - -import ( - "bytes" - "errors" - "fmt" - "io" - "net/url" - "reflect" - "strings" - "sync" - "unicode" - "unicode/utf8" -) - -// FuncMap is the type of the map defining the mapping from names to functions. -// Each function must have either a single return value, or two return values of -// which the second has type error. In that case, if the second (error) -// return value evaluates to non-nil during execution, execution terminates and -// Execute returns that error. -// -// Errors returned by Execute wrap the underlying error; call errors.As to -// uncover them. -// -// When template execution invokes a function with an argument list, that list -// must be assignable to the function's parameter types. Functions meant to -// apply to arguments of arbitrary type can use parameters of type interface{} or -// of type reflect.Value. Similarly, functions meant to return a result of arbitrary -// type can return interface{} or reflect.Value. -type FuncMap map[string]interface{} - -// builtins returns the FuncMap. -// It is not a global variable so the linker can dead code eliminate -// more when this isn't called. See golang.org/issue/36021. -// TODO: revert this back to a global map once golang.org/issue/2559 is fixed. -func builtins() FuncMap { - return FuncMap{ - "and": and, - "call": call, - "html": HTMLEscaper, - "index": index, - "slice": slice, - "js": JSEscaper, - "len": length, - "not": not, - "or": or, - "print": fmt.Sprint, - "printf": fmt.Sprintf, - "println": fmt.Sprintln, - "urlquery": URLQueryEscaper, - - // Comparisons - "eq": eq, // == - "ge": ge, // >= - "gt": gt, // > - "le": le, // <= - "lt": lt, // < - "ne": ne, // != - } -} - -var builtinFuncsOnce struct { - sync.Once - v map[string]reflect.Value -} - -// builtinFuncsOnce lazily computes & caches the builtinFuncs map. -// TODO: revert this back to a global map once golang.org/issue/2559 is fixed. -func builtinFuncs() map[string]reflect.Value { - builtinFuncsOnce.Do(func() { - builtinFuncsOnce.v = createValueFuncs(builtins()) - }) - return builtinFuncsOnce.v -} - -// createValueFuncs turns a FuncMap into a map[string]reflect.Value -func createValueFuncs(funcMap FuncMap) map[string]reflect.Value { - m := make(map[string]reflect.Value) - addValueFuncs(m, funcMap) - return m -} - -// addValueFuncs adds to values the functions in funcs, converting them to reflect.Values. -func addValueFuncs(out map[string]reflect.Value, in FuncMap) { - for name, fn := range in { - if !goodName(name) { - panic(fmt.Errorf("function name %q is not a valid identifier", name)) - } - v := reflect.ValueOf(fn) - if v.Kind() != reflect.Func { - panic("value for " + name + " not a function") - } - if !goodFunc(v.Type()) { - panic(fmt.Errorf("can't install method/function %q with %d results", name, v.Type().NumOut())) - } - out[name] = v - } -} - -// addFuncs adds to values the functions in funcs. It does no checking of the input - -// call addValueFuncs first. -func addFuncs(out, in FuncMap) { - for name, fn := range in { - out[name] = fn - } -} - -// goodFunc reports whether the function or method has the right result signature. -func goodFunc(typ reflect.Type) bool { - // We allow functions with 1 result or 2 results where the second is an error. - switch { - case typ.NumOut() == 1: - return true - case typ.NumOut() == 2 && typ.Out(1) == errorType: - return true - } - return false -} - -// goodName reports whether the function name is a valid identifier. -func goodName(name string) bool { - if name == "" { - return false - } - for i, r := range name { - switch { - case r == '_': - case i == 0 && !unicode.IsLetter(r): - return false - case !unicode.IsLetter(r) && !unicode.IsDigit(r): - return false - } - } - return true -} - -// findFunction looks for a function in the template, and global map. -func findFunction(name string, tmpl *Template) (reflect.Value, bool) { - if tmpl != nil && tmpl.common != nil { - tmpl.muFuncs.RLock() - defer tmpl.muFuncs.RUnlock() - if fn := tmpl.execFuncs[name]; fn.IsValid() { - return fn, true - } - } - if fn := builtinFuncs()[name]; fn.IsValid() { - return fn, true - } - return reflect.Value{}, false -} - -// prepareArg checks if value can be used as an argument of type argType, and -// converts an invalid value to appropriate zero if possible. -func prepareArg(value reflect.Value, argType reflect.Type) (reflect.Value, error) { - if !value.IsValid() { - if !canBeNil(argType) { - return reflect.Value{}, fmt.Errorf("value is nil; should be of type %s", argType) - } - value = reflect.Zero(argType) - } - if value.Type().AssignableTo(argType) { - return value, nil - } - if intLike(value.Kind()) && intLike(argType.Kind()) && value.Type().ConvertibleTo(argType) { - value = value.Convert(argType) - return value, nil - } - return reflect.Value{}, fmt.Errorf("value has type %s; should be %s", value.Type(), argType) -} - -func intLike(typ reflect.Kind) bool { - switch typ { - case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: - return true - case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: - return true - } - return false -} - -// indexArg checks if a reflect.Value can be used as an index, and converts it to int if possible. -func indexArg(index reflect.Value, cap int) (int, error) { - var x int64 - switch index.Kind() { - case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: - x = index.Int() - case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: - x = int64(index.Uint()) - case reflect.Invalid: - return 0, fmt.Errorf("cannot index slice/array with nil") - default: - return 0, fmt.Errorf("cannot index slice/array with type %s", index.Type()) - } - if x < 0 || int(x) < 0 || int(x) > cap { - return 0, fmt.Errorf("index out of range: %d", x) - } - return int(x), nil -} - -// Indexing. - -// index returns the result of indexing its first argument by the following -// arguments. Thus "index x 1 2 3" is, in Go syntax, x[1][2][3]. Each -// indexed item must be a map, slice, or array. -func index(item reflect.Value, indexes ...reflect.Value) (reflect.Value, error) { - item = indirectInterface(item) - if !item.IsValid() { - return reflect.Value{}, fmt.Errorf("index of untyped nil") - } - for _, index := range indexes { - index = indirectInterface(index) - var isNil bool - if item, isNil = indirect(item); isNil { - return reflect.Value{}, fmt.Errorf("index of nil pointer") - } - switch item.Kind() { - case reflect.Array, reflect.Slice, reflect.String: - x, err := indexArg(index, item.Len()) - if err != nil { - return reflect.Value{}, err - } - item = item.Index(x) - case reflect.Map: - index, err := prepareArg(index, item.Type().Key()) - if err != nil { - return reflect.Value{}, err - } - if x := item.MapIndex(index); x.IsValid() { - item = x - } else { - item = reflect.Zero(item.Type().Elem()) - } - case reflect.Invalid: - // the loop holds invariant: item.IsValid() - panic("unreachable") - default: - return reflect.Value{}, fmt.Errorf("can't index item of type %s", item.Type()) - } - } - return item, nil -} - -// Slicing. - -// slice returns the result of slicing its first argument by the remaining -// arguments. Thus "slice x 1 2" is, in Go syntax, x[1:2], while "slice x" -// is x[:], "slice x 1" is x[1:], and "slice x 1 2 3" is x[1:2:3]. The first -// argument must be a string, slice, or array. -func slice(item reflect.Value, indexes ...reflect.Value) (reflect.Value, error) { - item = indirectInterface(item) - if !item.IsValid() { - return reflect.Value{}, fmt.Errorf("slice of untyped nil") - } - if len(indexes) > 3 { - return reflect.Value{}, fmt.Errorf("too many slice indexes: %d", len(indexes)) - } - var cap int - switch item.Kind() { - case reflect.String: - if len(indexes) == 3 { - return reflect.Value{}, fmt.Errorf("cannot 3-index slice a string") - } - cap = item.Len() - case reflect.Array, reflect.Slice: - cap = item.Cap() - default: - return reflect.Value{}, fmt.Errorf("can't slice item of type %s", item.Type()) - } - // set default values for cases item[:], item[i:]. - idx := [3]int{0, item.Len()} - for i, index := range indexes { - x, err := indexArg(index, cap) - if err != nil { - return reflect.Value{}, err - } - idx[i] = x - } - // given item[i:j], make sure i <= j. - if idx[0] > idx[1] { - return reflect.Value{}, fmt.Errorf("invalid slice index: %d > %d", idx[0], idx[1]) - } - if len(indexes) < 3 { - return item.Slice(idx[0], idx[1]), nil - } - // given item[i:j:k], make sure i <= j <= k. - if idx[1] > idx[2] { - return reflect.Value{}, fmt.Errorf("invalid slice index: %d > %d", idx[1], idx[2]) - } - return item.Slice3(idx[0], idx[1], idx[2]), nil -} - -// Length - -// length returns the length of the item, with an error if it has no defined length. -func length(item reflect.Value) (int, error) { - item, isNil := indirect(item) - if isNil { - return 0, fmt.Errorf("len of nil pointer") - } - switch item.Kind() { - case reflect.Array, reflect.Chan, reflect.Map, reflect.Slice, reflect.String: - return item.Len(), nil - } - return 0, fmt.Errorf("len of type %s", item.Type()) -} - -// Function invocation - -// call returns the result of evaluating the first argument as a function. -// The function must return 1 result, or 2 results, the second of which is an error. -func call(fn reflect.Value, args ...reflect.Value) (reflect.Value, error) { - fn = indirectInterface(fn) - if !fn.IsValid() { - return reflect.Value{}, fmt.Errorf("call of nil") - } - typ := fn.Type() - if typ.Kind() != reflect.Func { - return reflect.Value{}, fmt.Errorf("non-function of type %s", typ) - } - if !goodFunc(typ) { - return reflect.Value{}, fmt.Errorf("function called with %d args; should be 1 or 2", typ.NumOut()) - } - numIn := typ.NumIn() - var dddType reflect.Type - if typ.IsVariadic() { - if len(args) < numIn-1 { - return reflect.Value{}, fmt.Errorf("wrong number of args: got %d want at least %d", len(args), numIn-1) - } - dddType = typ.In(numIn - 1).Elem() - } else { - if len(args) != numIn { - return reflect.Value{}, fmt.Errorf("wrong number of args: got %d want %d", len(args), numIn) - } - } - argv := make([]reflect.Value, len(args)) - for i, arg := range args { - arg = indirectInterface(arg) - // Compute the expected type. Clumsy because of variadics. - argType := dddType - if !typ.IsVariadic() || i < numIn-1 { - argType = typ.In(i) - } - - var err error - if argv[i], err = prepareArg(arg, argType); err != nil { - return reflect.Value{}, fmt.Errorf("arg %d: %w", i, err) - } - } - return safeCall(fn, argv) -} - -// safeCall runs fun.Call(args), and returns the resulting value and error, if -// any. If the call panics, the panic value is returned as an error. -func safeCall(fun reflect.Value, args []reflect.Value) (val reflect.Value, err error) { - defer func() { - if r := recover(); r != nil { - if e, ok := r.(error); ok { - err = e - } else { - err = fmt.Errorf("%v", r) - } - } - }() - ret := fun.Call(args) - if len(ret) == 2 && !ret[1].IsNil() { - return ret[0], ret[1].Interface().(error) - } - return ret[0], nil -} - -// Boolean logic. - -func truth(arg reflect.Value) bool { - t, _ := isTrue(indirectInterface(arg)) - return t -} - -// and computes the Boolean AND of its arguments, returning -// the first false argument it encounters, or the last argument. -func and(arg0 reflect.Value, args ...reflect.Value) reflect.Value { - if !truth(arg0) { - return arg0 - } - for i := range args { - arg0 = args[i] - if !truth(arg0) { - break - } - } - return arg0 -} - -// or computes the Boolean OR of its arguments, returning -// the first true argument it encounters, or the last argument. -func or(arg0 reflect.Value, args ...reflect.Value) reflect.Value { - if truth(arg0) { - return arg0 - } - for i := range args { - arg0 = args[i] - if truth(arg0) { - break - } - } - return arg0 -} - -// not returns the Boolean negation of its argument. -func not(arg reflect.Value) bool { - return !truth(arg) -} - -// Comparison. - -// TODO: Perhaps allow comparison between signed and unsigned integers. - -var ( - errBadComparisonType = errors.New("invalid type for comparison") - errBadComparison = errors.New("incompatible types for comparison") - errNoComparison = errors.New("missing argument for comparison") -) - -type kind int - -const ( - invalidKind kind = iota - boolKind - complexKind - intKind - floatKind - stringKind - uintKind -) - -func basicKind(v reflect.Value) (kind, error) { - switch v.Kind() { - case reflect.Bool: - return boolKind, nil - case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: - return intKind, nil - case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: - return uintKind, nil - case reflect.Float32, reflect.Float64: - return floatKind, nil - case reflect.Complex64, reflect.Complex128: - return complexKind, nil - case reflect.String: - return stringKind, nil - } - return invalidKind, errBadComparisonType -} - -// eq evaluates the comparison a == b || a == c || ... -func eq(arg1 reflect.Value, arg2 ...reflect.Value) (bool, error) { - arg1 = indirectInterface(arg1) - if arg1 != zero { - if t1 := arg1.Type(); !t1.Comparable() { - return false, fmt.Errorf("uncomparable type %s: %v", t1, arg1) - } - } - if len(arg2) == 0 { - return false, errNoComparison - } - k1, _ := basicKind(arg1) - for _, arg := range arg2 { - arg = indirectInterface(arg) - k2, _ := basicKind(arg) - truth := false - if k1 != k2 { - // Special case: Can compare integer values regardless of type's sign. - switch { - case k1 == intKind && k2 == uintKind: - truth = arg1.Int() >= 0 && uint64(arg1.Int()) == arg.Uint() - case k1 == uintKind && k2 == intKind: - truth = arg.Int() >= 0 && arg1.Uint() == uint64(arg.Int()) - default: - if arg1 != zero && arg != zero { - return false, errBadComparison - } - } - } else { - switch k1 { - case boolKind: - truth = arg1.Bool() == arg.Bool() - case complexKind: - truth = arg1.Complex() == arg.Complex() - case floatKind: - truth = arg1.Float() == arg.Float() - case intKind: - truth = arg1.Int() == arg.Int() - case stringKind: - truth = arg1.String() == arg.String() - case uintKind: - truth = arg1.Uint() == arg.Uint() - default: - if arg == zero { - truth = arg1 == arg - } else { - if t2 := arg.Type(); !t2.Comparable() { - return false, fmt.Errorf("uncomparable type %s: %v", t2, arg) - } - truth = arg1.Interface() == arg.Interface() - } - } - } - if truth { - return true, nil - } - } - return false, nil -} - -// ne evaluates the comparison a != b. -func ne(arg1, arg2 reflect.Value) (bool, error) { - // != is the inverse of ==. - equal, err := eq(arg1, arg2) - return !equal, err -} - -// lt evaluates the comparison a < b. -func lt(arg1, arg2 reflect.Value) (bool, error) { - arg1 = indirectInterface(arg1) - k1, err := basicKind(arg1) - if err != nil { - return false, err - } - arg2 = indirectInterface(arg2) - k2, err := basicKind(arg2) - if err != nil { - return false, err - } - truth := false - if k1 != k2 { - // Special case: Can compare integer values regardless of type's sign. - switch { - case k1 == intKind && k2 == uintKind: - truth = arg1.Int() < 0 || uint64(arg1.Int()) < arg2.Uint() - case k1 == uintKind && k2 == intKind: - truth = arg2.Int() >= 0 && arg1.Uint() < uint64(arg2.Int()) - default: - return false, errBadComparison - } - } else { - switch k1 { - case boolKind, complexKind: - return false, errBadComparisonType - case floatKind: - truth = arg1.Float() < arg2.Float() - case intKind: - truth = arg1.Int() < arg2.Int() - case stringKind: - truth = arg1.String() < arg2.String() - case uintKind: - truth = arg1.Uint() < arg2.Uint() - default: - panic("invalid kind") - } - } - return truth, nil -} - -// le evaluates the comparison <= b. -func le(arg1, arg2 reflect.Value) (bool, error) { - // <= is < or ==. - lessThan, err := lt(arg1, arg2) - if lessThan || err != nil { - return lessThan, err - } - return eq(arg1, arg2) -} - -// gt evaluates the comparison a > b. -func gt(arg1, arg2 reflect.Value) (bool, error) { - // > is the inverse of <=. - lessOrEqual, err := le(arg1, arg2) - if err != nil { - return false, err - } - return !lessOrEqual, nil -} - -// ge evaluates the comparison a >= b. -func ge(arg1, arg2 reflect.Value) (bool, error) { - // >= is the inverse of <. - lessThan, err := lt(arg1, arg2) - if err != nil { - return false, err - } - return !lessThan, nil -} - -// HTML escaping. - -var ( - htmlQuot = []byte(""") // shorter than """ - htmlApos = []byte("'") // shorter than "'" and apos was not in HTML until HTML5 - htmlAmp = []byte("&") - htmlLt = []byte("<") - htmlGt = []byte(">") - htmlNull = []byte("\uFFFD") -) - -// HTMLEscape writes to w the escaped HTML equivalent of the plain text data b. -func HTMLEscape(w io.Writer, b []byte) { - last := 0 - for i, c := range b { - var html []byte - switch c { - case '\000': - html = htmlNull - case '"': - html = htmlQuot - case '\'': - html = htmlApos - case '&': - html = htmlAmp - case '<': - html = htmlLt - case '>': - html = htmlGt - default: - continue - } - w.Write(b[last:i]) - w.Write(html) - last = i + 1 - } - w.Write(b[last:]) -} - -// HTMLEscapeString returns the escaped HTML equivalent of the plain text data s. -func HTMLEscapeString(s string) string { - // Avoid allocation if we can. - if !strings.ContainsAny(s, "'\"&<>\000") { - return s - } - var b bytes.Buffer - HTMLEscape(&b, []byte(s)) - return b.String() -} - -// HTMLEscaper returns the escaped HTML equivalent of the textual -// representation of its arguments. -func HTMLEscaper(args ...interface{}) string { - return HTMLEscapeString(evalArgs(args)) -} - -// JavaScript escaping. - -var ( - jsLowUni = []byte(`\u00`) - hex = []byte("0123456789ABCDEF") - - jsBackslash = []byte(`\\`) - jsApos = []byte(`\'`) - jsQuot = []byte(`\"`) - jsLt = []byte(`\u003C`) - jsGt = []byte(`\u003E`) - jsAmp = []byte(`\u0026`) - jsEq = []byte(`\u003D`) -) - -// JSEscape writes to w the escaped JavaScript equivalent of the plain text data b. -func JSEscape(w io.Writer, b []byte) { - last := 0 - for i := 0; i < len(b); i++ { - c := b[i] - - if !jsIsSpecial(rune(c)) { - // fast path: nothing to do - continue - } - w.Write(b[last:i]) - - if c < utf8.RuneSelf { - // Quotes, slashes and angle brackets get quoted. - // Control characters get written as \u00XX. - switch c { - case '\\': - w.Write(jsBackslash) - case '\'': - w.Write(jsApos) - case '"': - w.Write(jsQuot) - case '<': - w.Write(jsLt) - case '>': - w.Write(jsGt) - case '&': - w.Write(jsAmp) - case '=': - w.Write(jsEq) - default: - w.Write(jsLowUni) - t, b := c>>4, c&0x0f - w.Write(hex[t : t+1]) - w.Write(hex[b : b+1]) - } - } else { - // Unicode rune. - r, size := utf8.DecodeRune(b[i:]) - if unicode.IsPrint(r) { - w.Write(b[i : i+size]) - } else { - fmt.Fprintf(w, "\\u%04X", r) - } - i += size - 1 - } - last = i + 1 - } - w.Write(b[last:]) -} - -// JSEscapeString returns the escaped JavaScript equivalent of the plain text data s. -func JSEscapeString(s string) string { - // Avoid allocation if we can. - if strings.IndexFunc(s, jsIsSpecial) < 0 { - return s - } - var b bytes.Buffer - JSEscape(&b, []byte(s)) - return b.String() -} - -func jsIsSpecial(r rune) bool { - switch r { - case '\\', '\'', '"', '<', '>', '&', '=': - return true - } - return r < ' ' || utf8.RuneSelf <= r -} - -// JSEscaper returns the escaped JavaScript equivalent of the textual -// representation of its arguments. -func JSEscaper(args ...interface{}) string { - return JSEscapeString(evalArgs(args)) -} - -// URLQueryEscaper returns the escaped value of the textual representation of -// its arguments in a form suitable for embedding in a URL query. -func URLQueryEscaper(args ...interface{}) string { - return url.QueryEscape(evalArgs(args)) -} - -// evalArgs formats the list of arguments into a string. It is therefore equivalent to -// fmt.Sprint(args...) -// except that each argument is indirected (if a pointer), as required, -// using the same rules as the default string evaluation during template -// execution. -func evalArgs(args []interface{}) string { - ok := false - var s string - // Fast path for simple common case. - if len(args) == 1 { - s, ok = args[0].(string) - } - if !ok { - for i, arg := range args { - a, ok := printableValue(reflect.ValueOf(arg)) - if ok { - args[i] = a - } // else let fmt do its thing - } - s = fmt.Sprint(args...) - } - return s -}
diff --git a/go.dev/cmd/internal/text/template/helper.go b/go.dev/cmd/internal/text/template/helper.go deleted file mode 100644 index e2ce480..0000000 --- a/go.dev/cmd/internal/text/template/helper.go +++ /dev/null
@@ -1,135 +0,0 @@ -// Copyright 2011 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -// Helper functions to make constructing templates easier. - -package template - -import ( - "fmt" - "io/ioutil" - "path/filepath" -) - -// Functions and methods to parse templates. - -// Must is a helper that wraps a call to a function returning (*Template, error) -// and panics if the error is non-nil. It is intended for use in variable -// initializations such as -// var t = template.Must(template.New("name").Parse("text")) -func Must(t *Template, err error) *Template { - if err != nil { - panic(err) - } - return t -} - -// ParseFiles creates a new Template and parses the template definitions from -// the named files. The returned template's name will have the base name and -// parsed contents of the first file. There must be at least one file. -// If an error occurs, parsing stops and the returned *Template is nil. -// -// When parsing multiple files with the same name in different directories, -// the last one mentioned will be the one that results. -// For instance, ParseFiles("a/foo", "b/foo") stores "b/foo" as the template -// named "foo", while "a/foo" is unavailable. -func ParseFiles(filenames ...string) (*Template, error) { - return parseFiles(nil, readFileOS, filenames...) -} - -// ParseFiles parses the named files and associates the resulting templates with -// t. If an error occurs, parsing stops and the returned template is nil; -// otherwise it is t. There must be at least one file. -// Since the templates created by ParseFiles are named by the base -// names of the argument files, t should usually have the name of one -// of the (base) names of the files. If it does not, depending on t's -// contents before calling ParseFiles, t.Execute may fail. In that -// case use t.ExecuteTemplate to execute a valid template. -// -// When parsing multiple files with the same name in different directories, -// the last one mentioned will be the one that results. -func (t *Template) ParseFiles(filenames ...string) (*Template, error) { - t.init() - return parseFiles(t, readFileOS, filenames...) -} - -// parseFiles is the helper for the method and function. If the argument -// template is nil, it is created from the first file. -func parseFiles(t *Template, readFile func(string) (string, []byte, error), filenames ...string) (*Template, error) { - if len(filenames) == 0 { - // Not really a problem, but be consistent. - return nil, fmt.Errorf("template: no files named in call to ParseFiles") - } - for _, filename := range filenames { - name, b, err := readFile(filename) - if err != nil { - return nil, err - } - s := string(b) - // First template becomes return value if not already defined, - // and we use that one for subsequent New calls to associate - // all the templates together. Also, if this file has the same name - // as t, this file becomes the contents of t, so - // t, err := New(name).Funcs(xxx).ParseFiles(name) - // works. Otherwise we create a new template associated with t. - var tmpl *Template - if t == nil { - t = New(name) - } - if name == t.Name() { - tmpl = t - } else { - tmpl = t.New(name) - } - _, err = tmpl.Parse(s) - if err != nil { - return nil, err - } - } - return t, nil -} - -// ParseGlob creates a new Template and parses the template definitions from -// the files identified by the pattern. The files are matched according to the -// semantics of filepath.Match, and the pattern must match at least one file. -// The returned template will have the (base) name and (parsed) contents of the -// first file matched by the pattern. ParseGlob is equivalent to calling -// ParseFiles with the list of files matched by the pattern. -// -// When parsing multiple files with the same name in different directories, -// the last one mentioned will be the one that results. -func ParseGlob(pattern string) (*Template, error) { - return parseGlob(nil, pattern) -} - -// ParseGlob parses the template definitions in the files identified by the -// pattern and associates the resulting templates with t. The files are matched -// according to the semantics of filepath.Match, and the pattern must match at -// least one file. ParseGlob is equivalent to calling t.ParseFiles with the -// list of files matched by the pattern. -// -// When parsing multiple files with the same name in different directories, -// the last one mentioned will be the one that results. -func (t *Template) ParseGlob(pattern string) (*Template, error) { - t.init() - return parseGlob(t, pattern) -} - -// parseGlob is the implementation of the function and method ParseGlob. -func parseGlob(t *Template, pattern string) (*Template, error) { - filenames, err := filepath.Glob(pattern) - if err != nil { - return nil, err - } - if len(filenames) == 0 { - return nil, fmt.Errorf("template: pattern matches no files: %#q", pattern) - } - return parseFiles(t, readFileOS, filenames...) -} - -func readFileOS(file string) (name string, b []byte, err error) { - name = filepath.Base(file) - b, err = ioutil.ReadFile(file) - return -}
diff --git a/go.dev/cmd/internal/text/template/multi_test.go b/go.dev/cmd/internal/text/template/multi_test.go deleted file mode 100644 index 8507c63..0000000 --- a/go.dev/cmd/internal/text/template/multi_test.go +++ /dev/null
@@ -1,425 +0,0 @@ -// Copyright 2011 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package template - -// Tests for multiple-template parsing and execution. - -import ( - "bytes" - "fmt" - "testing" - - "golang.org/x/website/go.dev/cmd/internal/text/template/parse" -) - -const ( - noError = true - hasError = false -) - -type multiParseTest struct { - name string - input string - ok bool - names []string - results []string -} - -var multiParseTests = []multiParseTest{ - {"empty", "", noError, - nil, - nil}, - {"one", `{{define "foo"}} FOO {{end}}`, noError, - []string{"foo"}, - []string{" FOO "}}, - {"two", `{{define "foo"}} FOO {{end}}{{define "bar"}} BAR {{end}}`, noError, - []string{"foo", "bar"}, - []string{" FOO ", " BAR "}}, - // errors - {"missing end", `{{define "foo"}} FOO `, hasError, - nil, - nil}, - {"malformed name", `{{define "foo}} FOO `, hasError, - nil, - nil}, -} - -func TestMultiParse(t *testing.T) { - for _, test := range multiParseTests { - template, err := New("root").Parse(test.input) - switch { - case err == nil && !test.ok: - t.Errorf("%q: expected error; got none", test.name) - continue - case err != nil && test.ok: - t.Errorf("%q: unexpected error: %v", test.name, err) - continue - case err != nil && !test.ok: - // expected error, got one - if *debug { - fmt.Printf("%s: %s\n\t%s\n", test.name, test.input, err) - } - continue - } - if template == nil { - continue - } - if len(template.tmpl) != len(test.names)+1 { // +1 for root - t.Errorf("%s: wrong number of templates; wanted %d got %d", test.name, len(test.names), len(template.tmpl)) - continue - } - for i, name := range test.names { - tmpl, ok := template.tmpl[name] - if !ok { - t.Errorf("%s: can't find template %q", test.name, name) - continue - } - result := tmpl.Root.String() - if result != test.results[i] { - t.Errorf("%s=(%q): got\n\t%v\nexpected\n\t%v", test.name, test.input, result, test.results[i]) - } - } - } -} - -var multiExecTests = []execTest{ - {"empty", "", "", nil, true}, - {"text", "some text", "some text", nil, true}, - {"invoke x", `{{template "x" .SI}}`, "TEXT", tVal, true}, - {"invoke x no args", `{{template "x"}}`, "TEXT", tVal, true}, - {"invoke dot int", `{{template "dot" .I}}`, "17", tVal, true}, - {"invoke dot []int", `{{template "dot" .SI}}`, "[3 4 5]", tVal, true}, - {"invoke dotV", `{{template "dotV" .U}}`, "v", tVal, true}, - {"invoke nested int", `{{template "nested" .I}}`, "17", tVal, true}, - {"variable declared by template", `{{template "nested" $x:=.SI}},{{index $x 1}}`, "[3 4 5],4", tVal, true}, - - // User-defined function: test argument evaluator. - {"testFunc literal", `{{oneArg "joe"}}`, "oneArg=joe", tVal, true}, - {"testFunc .", `{{oneArg .}}`, "oneArg=joe", "joe", true}, -} - -// These strings are also in testdata/*. -const multiText1 = ` - {{define "x"}}TEXT{{end}} - {{define "dotV"}}{{.V}}{{end}} -` - -const multiText2 = ` - {{define "dot"}}{{.}}{{end}} - {{define "nested"}}{{template "dot" .}}{{end}} -` - -func TestMultiExecute(t *testing.T) { - // Declare a couple of templates first. - template, err := New("root").Parse(multiText1) - if err != nil { - t.Fatalf("parse error for 1: %s", err) - } - _, err = template.Parse(multiText2) - if err != nil { - t.Fatalf("parse error for 2: %s", err) - } - testExecute(multiExecTests, template, t) -} - -func TestParseFiles(t *testing.T) { - _, err := ParseFiles("DOES NOT EXIST") - if err == nil { - t.Error("expected error for non-existent file; got none") - } - template := New("root") - _, err = template.ParseFiles("testdata/file1.tmpl", "testdata/file2.tmpl") - if err != nil { - t.Fatalf("error parsing files: %v", err) - } - testExecute(multiExecTests, template, t) -} - -func TestParseGlob(t *testing.T) { - _, err := ParseGlob("DOES NOT EXIST") - if err == nil { - t.Error("expected error for non-existent file; got none") - } - _, err = New("error").ParseGlob("[x") - if err == nil { - t.Error("expected error for bad pattern; got none") - } - template := New("root") - _, err = template.ParseGlob("testdata/file*.tmpl") - if err != nil { - t.Fatalf("error parsing files: %v", err) - } - testExecute(multiExecTests, template, t) -} - -// In these tests, actual content (not just template definitions) comes from the parsed files. - -var templateFileExecTests = []execTest{ - {"test", `{{template "tmpl1.tmpl"}}{{template "tmpl2.tmpl"}}`, "template1\n\ny\ntemplate2\n\nx\n", 0, true}, -} - -func TestParseFilesWithData(t *testing.T) { - template, err := New("root").ParseFiles("testdata/tmpl1.tmpl", "testdata/tmpl2.tmpl") - if err != nil { - t.Fatalf("error parsing files: %v", err) - } - testExecute(templateFileExecTests, template, t) -} - -func TestParseGlobWithData(t *testing.T) { - template, err := New("root").ParseGlob("testdata/tmpl*.tmpl") - if err != nil { - t.Fatalf("error parsing files: %v", err) - } - testExecute(templateFileExecTests, template, t) -} - -const ( - cloneText1 = `{{define "a"}}{{template "b"}}{{template "c"}}{{end}}` - cloneText2 = `{{define "b"}}b{{end}}` - cloneText3 = `{{define "c"}}root{{end}}` - cloneText4 = `{{define "c"}}clone{{end}}` -) - -func TestClone(t *testing.T) { - // Create some templates and clone the root. - root, err := New("root").Parse(cloneText1) - if err != nil { - t.Fatal(err) - } - _, err = root.Parse(cloneText2) - if err != nil { - t.Fatal(err) - } - clone := Must(root.Clone()) - // Add variants to both. - _, err = root.Parse(cloneText3) - if err != nil { - t.Fatal(err) - } - _, err = clone.Parse(cloneText4) - if err != nil { - t.Fatal(err) - } - // Verify that the clone is self-consistent. - for k, v := range clone.tmpl { - if k == clone.name && v.tmpl[k] != clone { - t.Error("clone does not contain root") - } - if v != v.tmpl[v.name] { - t.Errorf("clone does not contain self for %q", k) - } - } - // Execute root. - var b bytes.Buffer - err = root.ExecuteTemplate(&b, "a", 0) - if err != nil { - t.Fatal(err) - } - if b.String() != "broot" { - t.Errorf("expected %q got %q", "broot", b.String()) - } - // Execute copy. - b.Reset() - err = clone.ExecuteTemplate(&b, "a", 0) - if err != nil { - t.Fatal(err) - } - if b.String() != "bclone" { - t.Errorf("expected %q got %q", "bclone", b.String()) - } -} - -func TestAddParseTree(t *testing.T) { - // Create some templates. - root, err := New("root").Parse(cloneText1) - if err != nil { - t.Fatal(err) - } - _, err = root.Parse(cloneText2) - if err != nil { - t.Fatal(err) - } - // Add a new parse tree. - tree, err := parse.Parse("cloneText3", cloneText3, "", "", nil, builtins()) - if err != nil { - t.Fatal(err) - } - added, err := root.AddParseTree("c", tree["c"]) - if err != nil { - t.Fatal(err) - } - // Execute. - var b bytes.Buffer - err = added.ExecuteTemplate(&b, "a", 0) - if err != nil { - t.Fatal(err) - } - if b.String() != "broot" { - t.Errorf("expected %q got %q", "broot", b.String()) - } -} - -// Issue 7032 -func TestAddParseTreeToUnparsedTemplate(t *testing.T) { - master := "{{define \"master\"}}{{end}}" - tmpl := New("master") - tree, err := parse.Parse("master", master, "", "", nil) - if err != nil { - t.Fatalf("unexpected parse err: %v", err) - } - masterTree := tree["master"] - tmpl.AddParseTree("master", masterTree) // used to panic -} - -func TestRedefinition(t *testing.T) { - var tmpl *Template - var err error - if tmpl, err = New("tmpl1").Parse(`{{define "test"}}foo{{end}}`); err != nil { - t.Fatalf("parse 1: %v", err) - } - if _, err = tmpl.Parse(`{{define "test"}}bar{{end}}`); err != nil { - t.Fatalf("got error %v, expected nil", err) - } - if _, err = tmpl.New("tmpl2").Parse(`{{define "test"}}bar{{end}}`); err != nil { - t.Fatalf("got error %v, expected nil", err) - } -} - -// Issue 10879 -func TestEmptyTemplateCloneCrash(t *testing.T) { - t1 := New("base") - t1.Clone() // used to panic -} - -// Issue 10910, 10926 -func TestTemplateLookUp(t *testing.T) { - t1 := New("foo") - if t1.Lookup("foo") != nil { - t.Error("Lookup returned non-nil value for undefined template foo") - } - t1.New("bar") - if t1.Lookup("bar") != nil { - t.Error("Lookup returned non-nil value for undefined template bar") - } - t1.Parse(`{{define "foo"}}test{{end}}`) - if t1.Lookup("foo") == nil { - t.Error("Lookup returned nil value for defined template") - } -} - -func TestNew(t *testing.T) { - // template with same name already exists - t1, _ := New("test").Parse(`{{define "test"}}foo{{end}}`) - t2 := t1.New("test") - - if t1.common != t2.common { - t.Errorf("t1 & t2 didn't share common struct; got %v != %v", t1.common, t2.common) - } - if t1.Tree == nil { - t.Error("defined template got nil Tree") - } - if t2.Tree != nil { - t.Error("undefined template got non-nil Tree") - } - - containsT1 := false - for _, tmpl := range t1.Templates() { - if tmpl == t2 { - t.Error("Templates included undefined template") - } - if tmpl == t1 { - containsT1 = true - } - } - if !containsT1 { - t.Error("Templates didn't include defined template") - } -} - -func TestParse(t *testing.T) { - // In multiple calls to Parse with the same receiver template, only one call - // can contain text other than space, comments, and template definitions - t1 := New("test") - if _, err := t1.Parse(`{{define "test"}}{{end}}`); err != nil { - t.Fatalf("parsing test: %s", err) - } - if _, err := t1.Parse(`{{define "test"}}{{/* this is a comment */}}{{end}}`); err != nil { - t.Fatalf("parsing test: %s", err) - } - if _, err := t1.Parse(`{{define "test"}}foo{{end}}`); err != nil { - t.Fatalf("parsing test: %s", err) - } -} - -func TestEmptyTemplate(t *testing.T) { - cases := []struct { - defn []string - in string - want string - }{ - {[]string{"x", "y"}, "", "y"}, - {[]string{""}, "once", ""}, - {[]string{"", ""}, "twice", ""}, - {[]string{"{{.}}", "{{.}}"}, "twice", "twice"}, - {[]string{"{{/* a comment */}}", "{{/* a comment */}}"}, "comment", ""}, - {[]string{"{{.}}", ""}, "twice", ""}, - } - - for i, c := range cases { - root := New("root") - - var ( - m *Template - err error - ) - for _, d := range c.defn { - m, err = root.New(c.in).Parse(d) - if err != nil { - t.Fatal(err) - } - } - buf := &bytes.Buffer{} - if err := m.Execute(buf, c.in); err != nil { - t.Error(i, err) - continue - } - if buf.String() != c.want { - t.Errorf("expected string %q: got %q", c.want, buf.String()) - } - } -} - -// Issue 19249 was a regression in 1.8 caused by the handling of empty -// templates added in that release, which got different answers depending -// on the order templates appeared in the internal map. -func TestIssue19294(t *testing.T) { - // The empty block in "xhtml" should be replaced during execution - // by the contents of "stylesheet", but if the internal map associating - // names with templates is built in the wrong order, the empty block - // looks non-empty and this doesn't happen. - var inlined = map[string]string{ - "stylesheet": `{{define "stylesheet"}}stylesheet{{end}}`, - "xhtml": `{{block "stylesheet" .}}{{end}}`, - } - all := []string{"stylesheet", "xhtml"} - for i := 0; i < 100; i++ { - res, err := New("title.xhtml").Parse(`{{template "xhtml" .}}`) - if err != nil { - t.Fatal(err) - } - for _, name := range all { - _, err := res.New(name).Parse(inlined[name]) - if err != nil { - t.Fatal(err) - } - } - var buf bytes.Buffer - res.Execute(&buf, 0) - if buf.String() != "stylesheet" { - t.Fatalf("iteration %d: got %q; expected %q", i, buf.String(), "stylesheet") - } - } -}
diff --git a/go.dev/cmd/internal/text/template/option.go b/go.dev/cmd/internal/text/template/option.go deleted file mode 100644 index addce2d..0000000 --- a/go.dev/cmd/internal/text/template/option.go +++ /dev/null
@@ -1,74 +0,0 @@ -// Copyright 2015 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -// This file contains the code to handle template options. - -package template - -import "strings" - -// missingKeyAction defines how to respond to indexing a map with a key that is not present. -type missingKeyAction int - -const ( - mapInvalid missingKeyAction = iota // Return an invalid reflect.Value. - mapZeroValue // Return the zero value for the map element. - mapError // Error out -) - -type option struct { - missingKey missingKeyAction -} - -// Option sets options for the template. Options are described by -// strings, either a simple string or "key=value". There can be at -// most one equals sign in an option string. If the option string -// is unrecognized or otherwise invalid, Option panics. -// -// Known options: -// -// missingkey: Control the behavior during execution if a map is -// indexed with a key that is not present in the map. -// "missingkey=default" or "missingkey=invalid" -// The default behavior: Do nothing and continue execution. -// If printed, the result of the index operation is the string -// "<no value>". -// "missingkey=zero" -// The operation returns the zero value for the map type's element. -// "missingkey=error" -// Execution stops immediately with an error. -// -func (t *Template) Option(opt ...string) *Template { - t.init() - for _, s := range opt { - t.setOption(s) - } - return t -} - -func (t *Template) setOption(opt string) { - if opt == "" { - panic("empty option string") - } - elems := strings.Split(opt, "=") - switch len(elems) { - case 2: - // key=value - switch elems[0] { - case "missingkey": - switch elems[1] { - case "invalid", "default": - t.option.missingKey = mapInvalid - return - case "zero": - t.option.missingKey = mapZeroValue - return - case "error": - t.option.missingKey = mapError - return - } - } - } - panic("unrecognized option: " + opt) -}
diff --git a/go.dev/cmd/internal/text/template/parse/lex.go b/go.dev/cmd/internal/text/template/parse/lex.go deleted file mode 100644 index 95e3377..0000000 --- a/go.dev/cmd/internal/text/template/parse/lex.go +++ /dev/null
@@ -1,682 +0,0 @@ -// Copyright 2011 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package parse - -import ( - "fmt" - "strings" - "unicode" - "unicode/utf8" -) - -// item represents a token or text string returned from the scanner. -type item struct { - typ itemType // The type of this item. - pos Pos // The starting position, in bytes, of this item in the input string. - val string // The value of this item. - line int // The line number at the start of this item. -} - -func (i item) String() string { - switch { - case i.typ == itemEOF: - return "EOF" - case i.typ == itemError: - return i.val - case i.typ > itemKeyword: - return fmt.Sprintf("<%s>", i.val) - case len(i.val) > 10: - return fmt.Sprintf("%.10q...", i.val) - } - return fmt.Sprintf("%q", i.val) -} - -// itemType identifies the type of lex items. -type itemType int - -const ( - itemError itemType = iota // error occurred; value is text of error - itemBool // boolean constant - itemChar // printable ASCII character; grab bag for comma etc. - itemCharConstant // character constant - itemComment // comment text - itemComplex // complex constant (1+2i); imaginary is just a number - itemAssign // equals ('=') introducing an assignment - itemDeclare // colon-equals (':=') introducing a declaration - itemEOF - itemField // alphanumeric identifier starting with '.' - itemIdentifier // alphanumeric identifier not starting with '.' - itemLeftDelim // left action delimiter - itemLeftParen // '(' inside action - itemNumber // simple number, including imaginary - itemPipe // pipe symbol - itemRawString // raw quoted string (includes quotes) - itemRightDelim // right action delimiter - itemRightParen // ')' inside action - itemSpace // run of spaces separating arguments - itemString // quoted string (includes quotes) - itemText // plain text - itemVariable // variable starting with '$', such as '$' or '$1' or '$hello' - // Keywords appear after all the rest. - itemKeyword // used only to delimit the keywords - itemBlock // block keyword - itemBreak // break keyword - itemContinue // continue keyword - itemDot // the cursor, spelled '.' - itemDefine // define keyword - itemElse // else keyword - itemEnd // end keyword - itemIf // if keyword - itemNil // the untyped nil constant, easiest to treat as a keyword - itemRange // range keyword - itemTemplate // template keyword - itemWith // with keyword -) - -var key = map[string]itemType{ - ".": itemDot, - "block": itemBlock, - "break": itemBreak, - "continue": itemContinue, - "define": itemDefine, - "else": itemElse, - "end": itemEnd, - "if": itemIf, - "range": itemRange, - "nil": itemNil, - "template": itemTemplate, - "with": itemWith, -} - -const eof = -1 - -// Trimming spaces. -// If the action begins "{{- " rather than "{{", then all space/tab/newlines -// preceding the action are trimmed; conversely if it ends " -}}" the -// leading spaces are trimmed. This is done entirely in the lexer; the -// parser never sees it happen. We require an ASCII space (' ', \t, \r, \n) -// to be present to avoid ambiguity with things like "{{-3}}". It reads -// better with the space present anyway. For simplicity, only ASCII -// does the job. -const ( - spaceChars = " \t\r\n" // These are the space characters defined by Go itself. - trimMarker = '-' // Attached to left/right delimiter, trims trailing spaces from preceding/following text. - trimMarkerLen = Pos(1 + 1) // marker plus space before or after -) - -// stateFn represents the state of the scanner as a function that returns the next state. -type stateFn func(*lexer) stateFn - -// lexer holds the state of the scanner. -type lexer struct { - name string // the name of the input; used only for error reports - input string // the string being scanned - leftDelim string // start of action - rightDelim string // end of action - emitComment bool // emit itemComment tokens. - pos Pos // current position in the input - start Pos // start position of this item - width Pos // width of last rune read from input - items chan item // channel of scanned items - parenDepth int // nesting depth of ( ) exprs - line int // 1+number of newlines seen - startLine int // start line of this item - breakOK bool // break keyword allowed - continueOK bool // continue keyword allowed -} - -// next returns the next rune in the input. -func (l *lexer) next() rune { - if int(l.pos) >= len(l.input) { - l.width = 0 - return eof - } - r, w := utf8.DecodeRuneInString(l.input[l.pos:]) - l.width = Pos(w) - l.pos += l.width - if r == '\n' { - l.line++ - } - return r -} - -// peek returns but does not consume the next rune in the input. -func (l *lexer) peek() rune { - r := l.next() - l.backup() - return r -} - -// backup steps back one rune. Can only be called once per call of next. -func (l *lexer) backup() { - l.pos -= l.width - // Correct newline count. - if l.width == 1 && l.input[l.pos] == '\n' { - l.line-- - } -} - -// emit passes an item back to the client. -func (l *lexer) emit(t itemType) { - l.items <- item{t, l.start, l.input[l.start:l.pos], l.startLine} - l.start = l.pos - l.startLine = l.line -} - -// ignore skips over the pending input before this point. -func (l *lexer) ignore() { - l.line += strings.Count(l.input[l.start:l.pos], "\n") - l.start = l.pos - l.startLine = l.line -} - -// accept consumes the next rune if it's from the valid set. -func (l *lexer) accept(valid string) bool { - if strings.ContainsRune(valid, l.next()) { - return true - } - l.backup() - return false -} - -// acceptRun consumes a run of runes from the valid set. -func (l *lexer) acceptRun(valid string) { - for strings.ContainsRune(valid, l.next()) { - } - l.backup() -} - -// errorf returns an error token and terminates the scan by passing -// back a nil pointer that will be the next state, terminating l.nextItem. -func (l *lexer) errorf(format string, args ...interface{}) stateFn { - l.items <- item{itemError, l.start, fmt.Sprintf(format, args...), l.startLine} - return nil -} - -// nextItem returns the next item from the input. -// Called by the parser, not in the lexing goroutine. -func (l *lexer) nextItem() item { - return <-l.items -} - -// drain drains the output so the lexing goroutine will exit. -// Called by the parser, not in the lexing goroutine. -func (l *lexer) drain() { - for range l.items { - } -} - -// lex creates a new scanner for the input string. -func lex(name, input, left, right string, emitComment bool) *lexer { - if left == "" { - left = leftDelim - } - if right == "" { - right = rightDelim - } - l := &lexer{ - name: name, - input: input, - leftDelim: left, - rightDelim: right, - emitComment: emitComment, - items: make(chan item), - line: 1, - startLine: 1, - } - go l.run() - return l -} - -// run runs the state machine for the lexer. -func (l *lexer) run() { - for state := lexText; state != nil; { - state = state(l) - } - close(l.items) -} - -// state functions - -const ( - leftDelim = "{{" - rightDelim = "}}" - leftComment = "/*" - rightComment = "*/" -) - -// lexText scans until an opening action delimiter, "{{". -func lexText(l *lexer) stateFn { - l.width = 0 - if x := strings.Index(l.input[l.pos:], l.leftDelim); x >= 0 { - ldn := Pos(len(l.leftDelim)) - l.pos += Pos(x) - trimLength := Pos(0) - if hasLeftTrimMarker(l.input[l.pos+ldn:]) { - trimLength = rightTrimLength(l.input[l.start:l.pos]) - } - l.pos -= trimLength - if l.pos > l.start { - l.line += strings.Count(l.input[l.start:l.pos], "\n") - l.emit(itemText) - } - l.pos += trimLength - l.ignore() - return lexLeftDelim - } - l.pos = Pos(len(l.input)) - // Correctly reached EOF. - if l.pos > l.start { - l.line += strings.Count(l.input[l.start:l.pos], "\n") - l.emit(itemText) - } - l.emit(itemEOF) - return nil -} - -// rightTrimLength returns the length of the spaces at the end of the string. -func rightTrimLength(s string) Pos { - return Pos(len(s) - len(strings.TrimRight(s, spaceChars))) -} - -// atRightDelim reports whether the lexer is at a right delimiter, possibly preceded by a trim marker. -func (l *lexer) atRightDelim() (delim, trimSpaces bool) { - if hasRightTrimMarker(l.input[l.pos:]) && strings.HasPrefix(l.input[l.pos+trimMarkerLen:], l.rightDelim) { // With trim marker. - return true, true - } - if strings.HasPrefix(l.input[l.pos:], l.rightDelim) { // Without trim marker. - return true, false - } - return false, false -} - -// leftTrimLength returns the length of the spaces at the beginning of the string. -func leftTrimLength(s string) Pos { - return Pos(len(s) - len(strings.TrimLeft(s, spaceChars))) -} - -// lexLeftDelim scans the left delimiter, which is known to be present, possibly with a trim marker. -func lexLeftDelim(l *lexer) stateFn { - l.pos += Pos(len(l.leftDelim)) - trimSpace := hasLeftTrimMarker(l.input[l.pos:]) - afterMarker := Pos(0) - if trimSpace { - afterMarker = trimMarkerLen - } - if strings.HasPrefix(l.input[l.pos+afterMarker:], leftComment) { - l.pos += afterMarker - l.ignore() - return lexComment - } - l.emit(itemLeftDelim) - l.pos += afterMarker - l.ignore() - l.parenDepth = 0 - return lexInsideAction -} - -// lexComment scans a comment. The left comment marker is known to be present. -func lexComment(l *lexer) stateFn { - l.pos += Pos(len(leftComment)) - i := strings.Index(l.input[l.pos:], rightComment) - if i < 0 { - return l.errorf("unclosed comment") - } - l.pos += Pos(i + len(rightComment)) - delim, trimSpace := l.atRightDelim() - if !delim { - return l.errorf("comment ends before closing delimiter") - } - if l.emitComment { - l.emit(itemComment) - } - if trimSpace { - l.pos += trimMarkerLen - } - l.pos += Pos(len(l.rightDelim)) - if trimSpace { - l.pos += leftTrimLength(l.input[l.pos:]) - } - l.ignore() - return lexText -} - -// lexRightDelim scans the right delimiter, which is known to be present, possibly with a trim marker. -func lexRightDelim(l *lexer) stateFn { - trimSpace := hasRightTrimMarker(l.input[l.pos:]) - if trimSpace { - l.pos += trimMarkerLen - l.ignore() - } - l.pos += Pos(len(l.rightDelim)) - l.emit(itemRightDelim) - if trimSpace { - l.pos += leftTrimLength(l.input[l.pos:]) - l.ignore() - } - return lexText -} - -// lexInsideAction scans the elements inside action delimiters. -func lexInsideAction(l *lexer) stateFn { - // Either number, quoted string, or identifier. - // Spaces separate arguments; runs of spaces turn into itemSpace. - // Pipe symbols separate and are emitted. - delim, _ := l.atRightDelim() - if delim { - if l.parenDepth == 0 { - return lexRightDelim - } - return l.errorf("unclosed left paren") - } - switch r := l.next(); { - case r == eof: - return l.errorf("unclosed action") - case isSpace(r): - l.backup() // Put space back in case we have " -}}". - return lexSpace - case r == '=': - l.emit(itemAssign) - case r == ':': - if l.next() != '=' { - return l.errorf("expected :=") - } - l.emit(itemDeclare) - case r == '|': - l.emit(itemPipe) - case r == '"': - return lexQuote - case r == '`': - return lexRawQuote - case r == '$': - return lexVariable - case r == '\'': - return lexChar - case r == '.': - // special look-ahead for ".field" so we don't break l.backup(). - if l.pos < Pos(len(l.input)) { - r := l.input[l.pos] - if r < '0' || '9' < r { - return lexField - } - } - fallthrough // '.' can start a number. - case r == '+' || r == '-' || ('0' <= r && r <= '9'): - l.backup() - return lexNumber - case isAlphaNumeric(r): - l.backup() - return lexIdentifier - case r == '(': - l.emit(itemLeftParen) - l.parenDepth++ - case r == ')': - l.emit(itemRightParen) - l.parenDepth-- - if l.parenDepth < 0 { - return l.errorf("unexpected right paren %#U", r) - } - case r <= unicode.MaxASCII && unicode.IsPrint(r): - l.emit(itemChar) - default: - return l.errorf("unrecognized character in action: %#U", r) - } - return lexInsideAction -} - -// lexSpace scans a run of space characters. -// We have not consumed the first space, which is known to be present. -// Take care if there is a trim-marked right delimiter, which starts with a space. -func lexSpace(l *lexer) stateFn { - var r rune - var numSpaces int - for { - r = l.peek() - if !isSpace(r) { - break - } - l.next() - numSpaces++ - } - // Be careful about a trim-marked closing delimiter, which has a minus - // after a space. We know there is a space, so check for the '-' that might follow. - if hasRightTrimMarker(l.input[l.pos-1:]) && strings.HasPrefix(l.input[l.pos-1+trimMarkerLen:], l.rightDelim) { - l.backup() // Before the space. - if numSpaces == 1 { - return lexRightDelim // On the delim, so go right to that. - } - } - l.emit(itemSpace) - return lexInsideAction -} - -// lexIdentifier scans an alphanumeric. -func lexIdentifier(l *lexer) stateFn { -Loop: - for { - switch r := l.next(); { - case isAlphaNumeric(r): - // absorb. - default: - l.backup() - word := l.input[l.start:l.pos] - if !l.atTerminator() { - return l.errorf("bad character %#U", r) - } - switch { - case key[word] > itemKeyword: - item := key[word] - if item == itemBreak && !l.breakOK || item == itemContinue && !l.continueOK { - l.emit(itemIdentifier) - } else { - l.emit(item) - } - case word[0] == '.': - l.emit(itemField) - case word == "true", word == "false": - l.emit(itemBool) - default: - l.emit(itemIdentifier) - } - break Loop - } - } - return lexInsideAction -} - -// lexField scans a field: .Alphanumeric. -// The . has been scanned. -func lexField(l *lexer) stateFn { - return lexFieldOrVariable(l, itemField) -} - -// lexVariable scans a Variable: $Alphanumeric. -// The $ has been scanned. -func lexVariable(l *lexer) stateFn { - if l.atTerminator() { // Nothing interesting follows -> "$". - l.emit(itemVariable) - return lexInsideAction - } - return lexFieldOrVariable(l, itemVariable) -} - -// lexVariable scans a field or variable: [.$]Alphanumeric. -// The . or $ has been scanned. -func lexFieldOrVariable(l *lexer, typ itemType) stateFn { - if l.atTerminator() { // Nothing interesting follows -> "." or "$". - if typ == itemVariable { - l.emit(itemVariable) - } else { - l.emit(itemDot) - } - return lexInsideAction - } - var r rune - for { - r = l.next() - if !isAlphaNumeric(r) { - l.backup() - break - } - } - if !l.atTerminator() { - return l.errorf("bad character %#U", r) - } - l.emit(typ) - return lexInsideAction -} - -// atTerminator reports whether the input is at valid termination character to -// appear after an identifier. Breaks .X.Y into two pieces. Also catches cases -// like "$x+2" not being acceptable without a space, in case we decide one -// day to implement arithmetic. -func (l *lexer) atTerminator() bool { - r := l.peek() - if isSpace(r) { - return true - } - switch r { - case eof, '.', ',', '|', ':', ')', '(': - return true - } - // Does r start the delimiter? This can be ambiguous (with delim=="//", $x/2 will - // succeed but should fail) but only in extremely rare cases caused by willfully - // bad choice of delimiter. - if rd, _ := utf8.DecodeRuneInString(l.rightDelim); rd == r { - return true - } - return false -} - -// lexChar scans a character constant. The initial quote is already -// scanned. Syntax checking is done by the parser. -func lexChar(l *lexer) stateFn { -Loop: - for { - switch l.next() { - case '\\': - if r := l.next(); r != eof && r != '\n' { - break - } - fallthrough - case eof, '\n': - return l.errorf("unterminated character constant") - case '\'': - break Loop - } - } - l.emit(itemCharConstant) - return lexInsideAction -} - -// lexNumber scans a number: decimal, octal, hex, float, or imaginary. This -// isn't a perfect number scanner - for instance it accepts "." and "0x0.2" -// and "089" - but when it's wrong the input is invalid and the parser (via -// strconv) will notice. -func lexNumber(l *lexer) stateFn { - if !l.scanNumber() { - return l.errorf("bad number syntax: %q", l.input[l.start:l.pos]) - } - if sign := l.peek(); sign == '+' || sign == '-' { - // Complex: 1+2i. No spaces, must end in 'i'. - if !l.scanNumber() || l.input[l.pos-1] != 'i' { - return l.errorf("bad number syntax: %q", l.input[l.start:l.pos]) - } - l.emit(itemComplex) - } else { - l.emit(itemNumber) - } - return lexInsideAction -} - -func (l *lexer) scanNumber() bool { - // Optional leading sign. - l.accept("+-") - // Is it hex? - digits := "0123456789_" - if l.accept("0") { - // Note: Leading 0 does not mean octal in floats. - if l.accept("xX") { - digits = "0123456789abcdefABCDEF_" - } else if l.accept("oO") { - digits = "01234567_" - } else if l.accept("bB") { - digits = "01_" - } - } - l.acceptRun(digits) - if l.accept(".") { - l.acceptRun(digits) - } - if len(digits) == 10+1 && l.accept("eE") { - l.accept("+-") - l.acceptRun("0123456789_") - } - if len(digits) == 16+6+1 && l.accept("pP") { - l.accept("+-") - l.acceptRun("0123456789_") - } - // Is it imaginary? - l.accept("i") - // Next thing mustn't be alphanumeric. - if isAlphaNumeric(l.peek()) { - l.next() - return false - } - return true -} - -// lexQuote scans a quoted string. -func lexQuote(l *lexer) stateFn { -Loop: - for { - switch l.next() { - case '\\': - if r := l.next(); r != eof && r != '\n' { - break - } - fallthrough - case eof, '\n': - return l.errorf("unterminated quoted string") - case '"': - break Loop - } - } - l.emit(itemString) - return lexInsideAction -} - -// lexRawQuote scans a raw quoted string. -func lexRawQuote(l *lexer) stateFn { -Loop: - for { - switch l.next() { - case eof: - return l.errorf("unterminated raw quoted string") - case '`': - break Loop - } - } - l.emit(itemRawString) - return lexInsideAction -} - -// isSpace reports whether r is a space character. -func isSpace(r rune) bool { - return r == ' ' || r == '\t' || r == '\r' || r == '\n' -} - -// isAlphaNumeric reports whether r is an alphabetic, digit, or underscore. -func isAlphaNumeric(r rune) bool { - return r == '_' || unicode.IsLetter(r) || unicode.IsDigit(r) -} - -func hasLeftTrimMarker(s string) bool { - return len(s) >= 2 && s[0] == trimMarker && isSpace(rune(s[1])) -} - -func hasRightTrimMarker(s string) bool { - return len(s) >= 2 && isSpace(rune(s[0])) && s[1] == trimMarker -}
diff --git a/go.dev/cmd/internal/text/template/parse/lex_test.go b/go.dev/cmd/internal/text/template/parse/lex_test.go deleted file mode 100644 index df6aabf..0000000 --- a/go.dev/cmd/internal/text/template/parse/lex_test.go +++ /dev/null
@@ -1,559 +0,0 @@ -// Copyright 2011 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package parse - -import ( - "fmt" - "testing" -) - -// Make the types prettyprint. -var itemName = map[itemType]string{ - itemError: "error", - itemBool: "bool", - itemChar: "char", - itemCharConstant: "charconst", - itemComment: "comment", - itemComplex: "complex", - itemDeclare: ":=", - itemEOF: "EOF", - itemField: "field", - itemIdentifier: "identifier", - itemLeftDelim: "left delim", - itemLeftParen: "(", - itemNumber: "number", - itemPipe: "pipe", - itemRawString: "raw string", - itemRightDelim: "right delim", - itemRightParen: ")", - itemSpace: "space", - itemString: "string", - itemVariable: "variable", - - // keywords - itemDot: ".", - itemBlock: "block", - itemBreak: "break", - itemContinue: "continue", - itemDefine: "define", - itemElse: "else", - itemIf: "if", - itemEnd: "end", - itemNil: "nil", - itemRange: "range", - itemTemplate: "template", - itemWith: "with", -} - -func (i itemType) String() string { - s := itemName[i] - if s == "" { - return fmt.Sprintf("item%d", int(i)) - } - return s -} - -type lexTest struct { - name string - input string - items []item -} - -func mkItem(typ itemType, text string) item { - return item{ - typ: typ, - val: text, - } -} - -var ( - tDot = mkItem(itemDot, ".") - tBlock = mkItem(itemBlock, "block") - tEOF = mkItem(itemEOF, "") - tFor = mkItem(itemIdentifier, "for") - tLeft = mkItem(itemLeftDelim, "{{") - tLpar = mkItem(itemLeftParen, "(") - tPipe = mkItem(itemPipe, "|") - tQuote = mkItem(itemString, `"abc \n\t\" "`) - tRange = mkItem(itemRange, "range") - tRight = mkItem(itemRightDelim, "}}") - tRpar = mkItem(itemRightParen, ")") - tSpace = mkItem(itemSpace, " ") - raw = "`" + `abc\n\t\" ` + "`" - rawNL = "`now is{{\n}}the time`" // Contains newline inside raw quote. - tRawQuote = mkItem(itemRawString, raw) - tRawQuoteNL = mkItem(itemRawString, rawNL) -) - -var lexTests = []lexTest{ - {"empty", "", []item{tEOF}}, - {"spaces", " \t\n", []item{mkItem(itemText, " \t\n"), tEOF}}, - {"text", `now is the time`, []item{mkItem(itemText, "now is the time"), tEOF}}, - {"text with comment", "hello-{{/* this is a comment */}}-world", []item{ - mkItem(itemText, "hello-"), - mkItem(itemComment, "/* this is a comment */"), - mkItem(itemText, "-world"), - tEOF, - }}, - {"punctuation", "{{,@% }}", []item{ - tLeft, - mkItem(itemChar, ","), - mkItem(itemChar, "@"), - mkItem(itemChar, "%"), - tSpace, - tRight, - tEOF, - }}, - {"parens", "{{((3))}}", []item{ - tLeft, - tLpar, - tLpar, - mkItem(itemNumber, "3"), - tRpar, - tRpar, - tRight, - tEOF, - }}, - {"empty action", `{{}}`, []item{tLeft, tRight, tEOF}}, - {"for", `{{for}}`, []item{tLeft, tFor, tRight, tEOF}}, - {"block", `{{block "foo" .}}`, []item{ - tLeft, tBlock, tSpace, mkItem(itemString, `"foo"`), tSpace, tDot, tRight, tEOF, - }}, - {"quote", `{{"abc \n\t\" "}}`, []item{tLeft, tQuote, tRight, tEOF}}, - {"raw quote", "{{" + raw + "}}", []item{tLeft, tRawQuote, tRight, tEOF}}, - {"raw quote with newline", "{{" + rawNL + "}}", []item{tLeft, tRawQuoteNL, tRight, tEOF}}, - {"numbers", "{{1 02 0x14 0X14 -7.2i 1e3 1E3 +1.2e-4 4.2i 1+2i 1_2 0x1.e_fp4 0X1.E_FP4}}", []item{ - tLeft, - mkItem(itemNumber, "1"), - tSpace, - mkItem(itemNumber, "02"), - tSpace, - mkItem(itemNumber, "0x14"), - tSpace, - mkItem(itemNumber, "0X14"), - tSpace, - mkItem(itemNumber, "-7.2i"), - tSpace, - mkItem(itemNumber, "1e3"), - tSpace, - mkItem(itemNumber, "1E3"), - tSpace, - mkItem(itemNumber, "+1.2e-4"), - tSpace, - mkItem(itemNumber, "4.2i"), - tSpace, - mkItem(itemComplex, "1+2i"), - tSpace, - mkItem(itemNumber, "1_2"), - tSpace, - mkItem(itemNumber, "0x1.e_fp4"), - tSpace, - mkItem(itemNumber, "0X1.E_FP4"), - tRight, - tEOF, - }}, - {"characters", `{{'a' '\n' '\'' '\\' '\u00FF' '\xFF' '本'}}`, []item{ - tLeft, - mkItem(itemCharConstant, `'a'`), - tSpace, - mkItem(itemCharConstant, `'\n'`), - tSpace, - mkItem(itemCharConstant, `'\''`), - tSpace, - mkItem(itemCharConstant, `'\\'`), - tSpace, - mkItem(itemCharConstant, `'\u00FF'`), - tSpace, - mkItem(itemCharConstant, `'\xFF'`), - tSpace, - mkItem(itemCharConstant, `'本'`), - tRight, - tEOF, - }}, - {"bools", "{{true false}}", []item{ - tLeft, - mkItem(itemBool, "true"), - tSpace, - mkItem(itemBool, "false"), - tRight, - tEOF, - }}, - {"dot", "{{.}}", []item{ - tLeft, - tDot, - tRight, - tEOF, - }}, - {"nil", "{{nil}}", []item{ - tLeft, - mkItem(itemNil, "nil"), - tRight, - tEOF, - }}, - {"dots", "{{.x . .2 .x.y.z}}", []item{ - tLeft, - mkItem(itemField, ".x"), - tSpace, - tDot, - tSpace, - mkItem(itemNumber, ".2"), - tSpace, - mkItem(itemField, ".x"), - mkItem(itemField, ".y"), - mkItem(itemField, ".z"), - tRight, - tEOF, - }}, - {"keywords", "{{range if else end with}}", []item{ - tLeft, - mkItem(itemRange, "range"), - tSpace, - mkItem(itemIf, "if"), - tSpace, - mkItem(itemElse, "else"), - tSpace, - mkItem(itemEnd, "end"), - tSpace, - mkItem(itemWith, "with"), - tRight, - tEOF, - }}, - {"variables", "{{$c := printf $ $hello $23 $ $var.Field .Method}}", []item{ - tLeft, - mkItem(itemVariable, "$c"), - tSpace, - mkItem(itemDeclare, ":="), - tSpace, - mkItem(itemIdentifier, "printf"), - tSpace, - mkItem(itemVariable, "$"), - tSpace, - mkItem(itemVariable, "$hello"), - tSpace, - mkItem(itemVariable, "$23"), - tSpace, - mkItem(itemVariable, "$"), - tSpace, - mkItem(itemVariable, "$var"), - mkItem(itemField, ".Field"), - tSpace, - mkItem(itemField, ".Method"), - tRight, - tEOF, - }}, - {"variable invocation", "{{$x 23}}", []item{ - tLeft, - mkItem(itemVariable, "$x"), - tSpace, - mkItem(itemNumber, "23"), - tRight, - tEOF, - }}, - {"pipeline", `intro {{echo hi 1.2 |noargs|args 1 "hi"}} outro`, []item{ - mkItem(itemText, "intro "), - tLeft, - mkItem(itemIdentifier, "echo"), - tSpace, - mkItem(itemIdentifier, "hi"), - tSpace, - mkItem(itemNumber, "1.2"), - tSpace, - tPipe, - mkItem(itemIdentifier, "noargs"), - tPipe, - mkItem(itemIdentifier, "args"), - tSpace, - mkItem(itemNumber, "1"), - tSpace, - mkItem(itemString, `"hi"`), - tRight, - mkItem(itemText, " outro"), - tEOF, - }}, - {"declaration", "{{$v := 3}}", []item{ - tLeft, - mkItem(itemVariable, "$v"), - tSpace, - mkItem(itemDeclare, ":="), - tSpace, - mkItem(itemNumber, "3"), - tRight, - tEOF, - }}, - {"2 declarations", "{{$v , $w := 3}}", []item{ - tLeft, - mkItem(itemVariable, "$v"), - tSpace, - mkItem(itemChar, ","), - tSpace, - mkItem(itemVariable, "$w"), - tSpace, - mkItem(itemDeclare, ":="), - tSpace, - mkItem(itemNumber, "3"), - tRight, - tEOF, - }}, - {"field of parenthesized expression", "{{(.X).Y}}", []item{ - tLeft, - tLpar, - mkItem(itemField, ".X"), - tRpar, - mkItem(itemField, ".Y"), - tRight, - tEOF, - }}, - {"trimming spaces before and after", "hello- {{- 3 -}} -world", []item{ - mkItem(itemText, "hello-"), - tLeft, - mkItem(itemNumber, "3"), - tRight, - mkItem(itemText, "-world"), - tEOF, - }}, - {"trimming spaces before and after comment", "hello- {{- /* hello */ -}} -world", []item{ - mkItem(itemText, "hello-"), - mkItem(itemComment, "/* hello */"), - mkItem(itemText, "-world"), - tEOF, - }}, - // errors - {"badchar", "#{{\x01}}", []item{ - mkItem(itemText, "#"), - tLeft, - mkItem(itemError, "unrecognized character in action: U+0001"), - }}, - {"unclosed action", "{{", []item{ - tLeft, - mkItem(itemError, "unclosed action"), - }}, - {"EOF in action", "{{range", []item{ - tLeft, - tRange, - mkItem(itemError, "unclosed action"), - }}, - {"unclosed quote", "{{\"\n\"}}", []item{ - tLeft, - mkItem(itemError, "unterminated quoted string"), - }}, - {"unclosed raw quote", "{{`xx}}", []item{ - tLeft, - mkItem(itemError, "unterminated raw quoted string"), - }}, - {"unclosed char constant", "{{'\n}}", []item{ - tLeft, - mkItem(itemError, "unterminated character constant"), - }}, - {"bad number", "{{3k}}", []item{ - tLeft, - mkItem(itemError, `bad number syntax: "3k"`), - }}, - {"unclosed paren", "{{(3}}", []item{ - tLeft, - tLpar, - mkItem(itemNumber, "3"), - mkItem(itemError, `unclosed left paren`), - }}, - {"extra right paren", "{{3)}}", []item{ - tLeft, - mkItem(itemNumber, "3"), - tRpar, - mkItem(itemError, `unexpected right paren U+0029 ')'`), - }}, - - // Fixed bugs - // Many elements in an action blew the lookahead until - // we made lexInsideAction not loop. - {"long pipeline deadlock", "{{|||||}}", []item{ - tLeft, - tPipe, - tPipe, - tPipe, - tPipe, - tPipe, - tRight, - tEOF, - }}, - {"text with bad comment", "hello-{{/*/}}-world", []item{ - mkItem(itemText, "hello-"), - mkItem(itemError, `unclosed comment`), - }}, - {"text with comment close separated from delim", "hello-{{/* */ }}-world", []item{ - mkItem(itemText, "hello-"), - mkItem(itemError, `comment ends before closing delimiter`), - }}, - // This one is an error that we can't catch because it breaks templates with - // minimized JavaScript. Should have fixed it before Go 1.1. - {"unmatched right delimiter", "hello-{.}}-world", []item{ - mkItem(itemText, "hello-{.}}-world"), - tEOF, - }}, -} - -// collect gathers the emitted items into a slice. -func collect(t *lexTest, left, right string) (items []item) { - l := lex(t.name, t.input, left, right, true) - for { - item := l.nextItem() - items = append(items, item) - if item.typ == itemEOF || item.typ == itemError { - break - } - } - return -} - -func equal(i1, i2 []item, checkPos bool) bool { - if len(i1) != len(i2) { - return false - } - for k := range i1 { - if i1[k].typ != i2[k].typ { - return false - } - if i1[k].val != i2[k].val { - return false - } - if checkPos && i1[k].pos != i2[k].pos { - return false - } - if checkPos && i1[k].line != i2[k].line { - return false - } - } - return true -} - -func TestLex(t *testing.T) { - for _, test := range lexTests { - items := collect(&test, "", "") - if !equal(items, test.items, false) { - t.Errorf("%s: got\n\t%+v\nexpected\n\t%v", test.name, items, test.items) - } - } -} - -// Some easy cases from above, but with delimiters $$ and @@ -var lexDelimTests = []lexTest{ - {"punctuation", "$$,@%{{}}@@", []item{ - tLeftDelim, - mkItem(itemChar, ","), - mkItem(itemChar, "@"), - mkItem(itemChar, "%"), - mkItem(itemChar, "{"), - mkItem(itemChar, "{"), - mkItem(itemChar, "}"), - mkItem(itemChar, "}"), - tRightDelim, - tEOF, - }}, - {"empty action", `$$@@`, []item{tLeftDelim, tRightDelim, tEOF}}, - {"for", `$$for@@`, []item{tLeftDelim, tFor, tRightDelim, tEOF}}, - {"quote", `$$"abc \n\t\" "@@`, []item{tLeftDelim, tQuote, tRightDelim, tEOF}}, - {"raw quote", "$$" + raw + "@@", []item{tLeftDelim, tRawQuote, tRightDelim, tEOF}}, -} - -var ( - tLeftDelim = mkItem(itemLeftDelim, "$$") - tRightDelim = mkItem(itemRightDelim, "@@") -) - -func TestDelims(t *testing.T) { - for _, test := range lexDelimTests { - items := collect(&test, "$$", "@@") - if !equal(items, test.items, false) { - t.Errorf("%s: got\n\t%v\nexpected\n\t%v", test.name, items, test.items) - } - } -} - -var lexPosTests = []lexTest{ - {"empty", "", []item{{itemEOF, 0, "", 1}}}, - {"punctuation", "{{,@%#}}", []item{ - {itemLeftDelim, 0, "{{", 1}, - {itemChar, 2, ",", 1}, - {itemChar, 3, "@", 1}, - {itemChar, 4, "%", 1}, - {itemChar, 5, "#", 1}, - {itemRightDelim, 6, "}}", 1}, - {itemEOF, 8, "", 1}, - }}, - {"sample", "0123{{hello}}xyz", []item{ - {itemText, 0, "0123", 1}, - {itemLeftDelim, 4, "{{", 1}, - {itemIdentifier, 6, "hello", 1}, - {itemRightDelim, 11, "}}", 1}, - {itemText, 13, "xyz", 1}, - {itemEOF, 16, "", 1}, - }}, - {"trimafter", "{{x -}}\n{{y}}", []item{ - {itemLeftDelim, 0, "{{", 1}, - {itemIdentifier, 2, "x", 1}, - {itemRightDelim, 5, "}}", 1}, - {itemLeftDelim, 8, "{{", 2}, - {itemIdentifier, 10, "y", 2}, - {itemRightDelim, 11, "}}", 2}, - {itemEOF, 13, "", 2}, - }}, - {"trimbefore", "{{x}}\n{{- y}}", []item{ - {itemLeftDelim, 0, "{{", 1}, - {itemIdentifier, 2, "x", 1}, - {itemRightDelim, 3, "}}", 1}, - {itemLeftDelim, 6, "{{", 2}, - {itemIdentifier, 10, "y", 2}, - {itemRightDelim, 11, "}}", 2}, - {itemEOF, 13, "", 2}, - }}, -} - -// The other tests don't check position, to make the test cases easier to construct. -// This one does. -func TestPos(t *testing.T) { - for _, test := range lexPosTests { - items := collect(&test, "", "") - if !equal(items, test.items, true) { - t.Errorf("%s: got\n\t%v\nexpected\n\t%v", test.name, items, test.items) - if len(items) == len(test.items) { - // Detailed print; avoid item.String() to expose the position value. - for i := range items { - if !equal(items[i:i+1], test.items[i:i+1], true) { - i1 := items[i] - i2 := test.items[i] - t.Errorf("\t#%d: got {%v %d %q %d} expected {%v %d %q %d}", - i, i1.typ, i1.pos, i1.val, i1.line, i2.typ, i2.pos, i2.val, i2.line) - } - } - } - } - } -} - -// Test that an error shuts down the lexing goroutine. -func TestShutdown(t *testing.T) { - // We need to duplicate template.Parse here to hold on to the lexer. - const text = "erroneous{{define}}{{else}}1234" - lexer := lex("foo", text, "{{", "}}", false) - _, err := New("root").parseLexer(lexer) - if err == nil { - t.Fatalf("expected error") - } - // The error should have drained the input. Therefore, the lexer should be shut down. - token, ok := <-lexer.items - if ok { - t.Fatalf("input was not drained; got %v", token) - } -} - -// parseLexer is a local version of parse that lets us pass in the lexer instead of building it. -// We expect an error, so the tree set and funcs list are explicitly nil. -func (t *Tree) parseLexer(lex *lexer) (tree *Tree, err error) { - defer t.recover(&err) - t.ParseName = t.Name - t.startParse(nil, lex, map[string]*Tree{}) - t.parse() - t.add() - t.stopParse() - return t, nil -}
diff --git a/go.dev/cmd/internal/text/template/parse/node.go b/go.dev/cmd/internal/text/template/parse/node.go deleted file mode 100644 index a2da634..0000000 --- a/go.dev/cmd/internal/text/template/parse/node.go +++ /dev/null
@@ -1,1008 +0,0 @@ -// Copyright 2011 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -// Parse nodes. - -package parse - -import ( - "fmt" - "strconv" - "strings" -) - -var textFormat = "%s" // Changed to "%q" in tests for better error messages. - -// A Node is an element in the parse tree. The interface is trivial. -// The interface contains an unexported method so that only -// types local to this package can satisfy it. -type Node interface { - Type() NodeType - String() string - // Copy does a deep copy of the Node and all its components. - // To avoid type assertions, some XxxNodes also have specialized - // CopyXxx methods that return *XxxNode. - Copy() Node - Position() Pos // byte position of start of node in full original input string - // tree returns the containing *Tree. - // It is unexported so all implementations of Node are in this package. - tree() *Tree - // writeTo writes the String output to the builder. - writeTo(*strings.Builder) -} - -// NodeType identifies the type of a parse tree node. -type NodeType int - -// Pos represents a byte position in the original input text from which -// this template was parsed. -type Pos int - -func (p Pos) Position() Pos { - return p -} - -// Type returns itself and provides an easy default implementation -// for embedding in a Node. Embedded in all non-trivial Nodes. -func (t NodeType) Type() NodeType { - return t -} - -const ( - NodeText NodeType = iota // Plain text. - NodeAction // A non-control action such as a field evaluation. - NodeBool // A boolean constant. - NodeBreak // A break action. - NodeChain // A sequence of field accesses. - NodeCommand // An element of a pipeline. - NodeContinue // A continue action. - NodeDot // The cursor, dot. - nodeElse // An else action. Not added to tree. - nodeEnd // An end action. Not added to tree. - NodeField // A field or method name. - NodeIdentifier // An identifier; always a function name. - NodeIf // An if action. - NodeList // A list of Nodes. - NodeNil // An untyped nil constant. - NodeNumber // A numerical constant. - NodePipe // A pipeline of commands. - NodeRange // A range action. - NodeString // A string constant. - NodeTemplate // A template invocation action. - NodeVariable // A $ variable. - NodeWith // A with action. - NodeComment // A comment. -) - -// Nodes. - -// ListNode holds a sequence of nodes. -type ListNode struct { - NodeType - Pos - tr *Tree - Nodes []Node // The element nodes in lexical order. -} - -func (t *Tree) newList(pos Pos) *ListNode { - return &ListNode{tr: t, NodeType: NodeList, Pos: pos} -} - -func (l *ListNode) append(n Node) { - l.Nodes = append(l.Nodes, n) -} - -func (l *ListNode) tree() *Tree { - return l.tr -} - -func (l *ListNode) String() string { - var sb strings.Builder - l.writeTo(&sb) - return sb.String() -} - -func (l *ListNode) writeTo(sb *strings.Builder) { - for _, n := range l.Nodes { - n.writeTo(sb) - } -} - -func (l *ListNode) CopyList() *ListNode { - if l == nil { - return l - } - n := l.tr.newList(l.Pos) - for _, elem := range l.Nodes { - n.append(elem.Copy()) - } - return n -} - -func (l *ListNode) Copy() Node { - return l.CopyList() -} - -// TextNode holds plain text. -type TextNode struct { - NodeType - Pos - tr *Tree - Text []byte // The text; may span newlines. -} - -func (t *Tree) newText(pos Pos, text string) *TextNode { - return &TextNode{tr: t, NodeType: NodeText, Pos: pos, Text: []byte(text)} -} - -func (t *TextNode) String() string { - return fmt.Sprintf(textFormat, t.Text) -} - -func (t *TextNode) writeTo(sb *strings.Builder) { - sb.WriteString(t.String()) -} - -func (t *TextNode) tree() *Tree { - return t.tr -} - -func (t *TextNode) Copy() Node { - return &TextNode{tr: t.tr, NodeType: NodeText, Pos: t.Pos, Text: append([]byte{}, t.Text...)} -} - -// CommentNode holds a comment. -type CommentNode struct { - NodeType - Pos - tr *Tree - Text string // Comment text. -} - -func (t *Tree) newComment(pos Pos, text string) *CommentNode { - return &CommentNode{tr: t, NodeType: NodeComment, Pos: pos, Text: text} -} - -func (c *CommentNode) String() string { - var sb strings.Builder - c.writeTo(&sb) - return sb.String() -} - -func (c *CommentNode) writeTo(sb *strings.Builder) { - sb.WriteString("{{") - sb.WriteString(c.Text) - sb.WriteString("}}") -} - -func (c *CommentNode) tree() *Tree { - return c.tr -} - -func (c *CommentNode) Copy() Node { - return &CommentNode{tr: c.tr, NodeType: NodeComment, Pos: c.Pos, Text: c.Text} -} - -// PipeNode holds a pipeline with optional declaration -type PipeNode struct { - NodeType - Pos - tr *Tree - Line int // The line number in the input. Deprecated: Kept for compatibility. - IsAssign bool // The variables are being assigned, not declared. - Decl []*VariableNode // Variables in lexical order. - Cmds []*CommandNode // The commands in lexical order. -} - -func (t *Tree) newPipeline(pos Pos, line int, vars []*VariableNode) *PipeNode { - return &PipeNode{tr: t, NodeType: NodePipe, Pos: pos, Line: line, Decl: vars} -} - -func (p *PipeNode) append(command *CommandNode) { - p.Cmds = append(p.Cmds, command) -} - -func (p *PipeNode) String() string { - var sb strings.Builder - p.writeTo(&sb) - return sb.String() -} - -func (p *PipeNode) writeTo(sb *strings.Builder) { - if len(p.Decl) > 0 { - for i, v := range p.Decl { - if i > 0 { - sb.WriteString(", ") - } - v.writeTo(sb) - } - sb.WriteString(" := ") - } - for i, c := range p.Cmds { - if i > 0 { - sb.WriteString(" | ") - } - c.writeTo(sb) - } -} - -func (p *PipeNode) tree() *Tree { - return p.tr -} - -func (p *PipeNode) CopyPipe() *PipeNode { - if p == nil { - return p - } - vars := make([]*VariableNode, len(p.Decl)) - for i, d := range p.Decl { - vars[i] = d.Copy().(*VariableNode) - } - n := p.tr.newPipeline(p.Pos, p.Line, vars) - n.IsAssign = p.IsAssign - for _, c := range p.Cmds { - n.append(c.Copy().(*CommandNode)) - } - return n -} - -func (p *PipeNode) Copy() Node { - return p.CopyPipe() -} - -// ActionNode holds an action (something bounded by delimiters). -// Control actions have their own nodes; ActionNode represents simple -// ones such as field evaluations and parenthesized pipelines. -type ActionNode struct { - NodeType - Pos - tr *Tree - Line int // The line number in the input. Deprecated: Kept for compatibility. - Pipe *PipeNode // The pipeline in the action. -} - -func (t *Tree) newAction(pos Pos, line int, pipe *PipeNode) *ActionNode { - return &ActionNode{tr: t, NodeType: NodeAction, Pos: pos, Line: line, Pipe: pipe} -} - -func (a *ActionNode) String() string { - var sb strings.Builder - a.writeTo(&sb) - return sb.String() -} - -func (a *ActionNode) writeTo(sb *strings.Builder) { - sb.WriteString("{{") - a.Pipe.writeTo(sb) - sb.WriteString("}}") -} - -func (a *ActionNode) tree() *Tree { - return a.tr -} - -func (a *ActionNode) Copy() Node { - return a.tr.newAction(a.Pos, a.Line, a.Pipe.CopyPipe()) - -} - -// CommandNode holds a command (a pipeline inside an evaluating action). -type CommandNode struct { - NodeType - Pos - tr *Tree - Args []Node // Arguments in lexical order: Identifier, field, or constant. -} - -func (t *Tree) newCommand(pos Pos) *CommandNode { - return &CommandNode{tr: t, NodeType: NodeCommand, Pos: pos} -} - -func (c *CommandNode) append(arg Node) { - c.Args = append(c.Args, arg) -} - -func (c *CommandNode) String() string { - var sb strings.Builder - c.writeTo(&sb) - return sb.String() -} - -func (c *CommandNode) writeTo(sb *strings.Builder) { - for i, arg := range c.Args { - if i > 0 { - sb.WriteByte(' ') - } - if arg, ok := arg.(*PipeNode); ok { - sb.WriteByte('(') - arg.writeTo(sb) - sb.WriteByte(')') - continue - } - arg.writeTo(sb) - } -} - -func (c *CommandNode) tree() *Tree { - return c.tr -} - -func (c *CommandNode) Copy() Node { - if c == nil { - return c - } - n := c.tr.newCommand(c.Pos) - for _, c := range c.Args { - n.append(c.Copy()) - } - return n -} - -// IdentifierNode holds an identifier. -type IdentifierNode struct { - NodeType - Pos - tr *Tree - Ident string // The identifier's name. -} - -// NewIdentifier returns a new IdentifierNode with the given identifier name. -func NewIdentifier(ident string) *IdentifierNode { - return &IdentifierNode{NodeType: NodeIdentifier, Ident: ident} -} - -// SetPos sets the position. NewIdentifier is a public method so we can't modify its signature. -// Chained for convenience. -// TODO: fix one day? -func (i *IdentifierNode) SetPos(pos Pos) *IdentifierNode { - i.Pos = pos - return i -} - -// SetTree sets the parent tree for the node. NewIdentifier is a public method so we can't modify its signature. -// Chained for convenience. -// TODO: fix one day? -func (i *IdentifierNode) SetTree(t *Tree) *IdentifierNode { - i.tr = t - return i -} - -func (i *IdentifierNode) String() string { - return i.Ident -} - -func (i *IdentifierNode) writeTo(sb *strings.Builder) { - sb.WriteString(i.String()) -} - -func (i *IdentifierNode) tree() *Tree { - return i.tr -} - -func (i *IdentifierNode) Copy() Node { - return NewIdentifier(i.Ident).SetTree(i.tr).SetPos(i.Pos) -} - -// VariableNode holds a list of variable names, possibly with chained field -// accesses. The dollar sign is part of the (first) name. -type VariableNode struct { - NodeType - Pos - tr *Tree - Ident []string // Variable name and fields in lexical order. -} - -func (t *Tree) newVariable(pos Pos, ident string) *VariableNode { - return &VariableNode{tr: t, NodeType: NodeVariable, Pos: pos, Ident: strings.Split(ident, ".")} -} - -func (v *VariableNode) String() string { - var sb strings.Builder - v.writeTo(&sb) - return sb.String() -} - -func (v *VariableNode) writeTo(sb *strings.Builder) { - for i, id := range v.Ident { - if i > 0 { - sb.WriteByte('.') - } - sb.WriteString(id) - } -} - -func (v *VariableNode) tree() *Tree { - return v.tr -} - -func (v *VariableNode) Copy() Node { - return &VariableNode{tr: v.tr, NodeType: NodeVariable, Pos: v.Pos, Ident: append([]string{}, v.Ident...)} -} - -// DotNode holds the special identifier '.'. -type DotNode struct { - NodeType - Pos - tr *Tree -} - -func (t *Tree) newDot(pos Pos) *DotNode { - return &DotNode{tr: t, NodeType: NodeDot, Pos: pos} -} - -func (d *DotNode) Type() NodeType { - // Override method on embedded NodeType for API compatibility. - // TODO: Not really a problem; could change API without effect but - // api tool complains. - return NodeDot -} - -func (d *DotNode) String() string { - return "." -} - -func (d *DotNode) writeTo(sb *strings.Builder) { - sb.WriteString(d.String()) -} - -func (d *DotNode) tree() *Tree { - return d.tr -} - -func (d *DotNode) Copy() Node { - return d.tr.newDot(d.Pos) -} - -// NilNode holds the special identifier 'nil' representing an untyped nil constant. -type NilNode struct { - NodeType - Pos - tr *Tree -} - -func (t *Tree) newNil(pos Pos) *NilNode { - return &NilNode{tr: t, NodeType: NodeNil, Pos: pos} -} - -func (n *NilNode) Type() NodeType { - // Override method on embedded NodeType for API compatibility. - // TODO: Not really a problem; could change API without effect but - // api tool complains. - return NodeNil -} - -func (n *NilNode) String() string { - return "nil" -} - -func (n *NilNode) writeTo(sb *strings.Builder) { - sb.WriteString(n.String()) -} - -func (n *NilNode) tree() *Tree { - return n.tr -} - -func (n *NilNode) Copy() Node { - return n.tr.newNil(n.Pos) -} - -// FieldNode holds a field (identifier starting with '.'). -// The names may be chained ('.x.y'). -// The period is dropped from each ident. -type FieldNode struct { - NodeType - Pos - tr *Tree - Ident []string // The identifiers in lexical order. -} - -func (t *Tree) newField(pos Pos, ident string) *FieldNode { - return &FieldNode{tr: t, NodeType: NodeField, Pos: pos, Ident: strings.Split(ident[1:], ".")} // [1:] to drop leading period -} - -func (f *FieldNode) String() string { - var sb strings.Builder - f.writeTo(&sb) - return sb.String() -} - -func (f *FieldNode) writeTo(sb *strings.Builder) { - for _, id := range f.Ident { - sb.WriteByte('.') - sb.WriteString(id) - } -} - -func (f *FieldNode) tree() *Tree { - return f.tr -} - -func (f *FieldNode) Copy() Node { - return &FieldNode{tr: f.tr, NodeType: NodeField, Pos: f.Pos, Ident: append([]string{}, f.Ident...)} -} - -// ChainNode holds a term followed by a chain of field accesses (identifier starting with '.'). -// The names may be chained ('.x.y'). -// The periods are dropped from each ident. -type ChainNode struct { - NodeType - Pos - tr *Tree - Node Node - Field []string // The identifiers in lexical order. -} - -func (t *Tree) newChain(pos Pos, node Node) *ChainNode { - return &ChainNode{tr: t, NodeType: NodeChain, Pos: pos, Node: node} -} - -// Add adds the named field (which should start with a period) to the end of the chain. -func (c *ChainNode) Add(field string) { - if len(field) == 0 || field[0] != '.' { - panic("no dot in field") - } - field = field[1:] // Remove leading dot. - if field == "" { - panic("empty field") - } - c.Field = append(c.Field, field) -} - -func (c *ChainNode) String() string { - var sb strings.Builder - c.writeTo(&sb) - return sb.String() -} - -func (c *ChainNode) writeTo(sb *strings.Builder) { - if _, ok := c.Node.(*PipeNode); ok { - sb.WriteByte('(') - c.Node.writeTo(sb) - sb.WriteByte(')') - } else { - c.Node.writeTo(sb) - } - for _, field := range c.Field { - sb.WriteByte('.') - sb.WriteString(field) - } -} - -func (c *ChainNode) tree() *Tree { - return c.tr -} - -func (c *ChainNode) Copy() Node { - return &ChainNode{tr: c.tr, NodeType: NodeChain, Pos: c.Pos, Node: c.Node, Field: append([]string{}, c.Field...)} -} - -// BoolNode holds a boolean constant. -type BoolNode struct { - NodeType - Pos - tr *Tree - True bool // The value of the boolean constant. -} - -func (t *Tree) newBool(pos Pos, true bool) *BoolNode { - return &BoolNode{tr: t, NodeType: NodeBool, Pos: pos, True: true} -} - -func (b *BoolNode) String() string { - if b.True { - return "true" - } - return "false" -} - -func (b *BoolNode) writeTo(sb *strings.Builder) { - sb.WriteString(b.String()) -} - -func (b *BoolNode) tree() *Tree { - return b.tr -} - -func (b *BoolNode) Copy() Node { - return b.tr.newBool(b.Pos, b.True) -} - -// NumberNode holds a number: signed or unsigned integer, float, or complex. -// The value is parsed and stored under all the types that can represent the value. -// This simulates in a small amount of code the behavior of Go's ideal constants. -type NumberNode struct { - NodeType - Pos - tr *Tree - IsInt bool // Number has an integral value. - IsUint bool // Number has an unsigned integral value. - IsFloat bool // Number has a floating-point value. - IsComplex bool // Number is complex. - Int64 int64 // The signed integer value. - Uint64 uint64 // The unsigned integer value. - Float64 float64 // The floating-point value. - Complex128 complex128 // The complex value. - Text string // The original textual representation from the input. -} - -func (t *Tree) newNumber(pos Pos, text string, typ itemType) (*NumberNode, error) { - n := &NumberNode{tr: t, NodeType: NodeNumber, Pos: pos, Text: text} - switch typ { - case itemCharConstant: - rune, _, tail, err := strconv.UnquoteChar(text[1:], text[0]) - if err != nil { - return nil, err - } - if tail != "'" { - return nil, fmt.Errorf("malformed character constant: %s", text) - } - n.Int64 = int64(rune) - n.IsInt = true - n.Uint64 = uint64(rune) - n.IsUint = true - n.Float64 = float64(rune) // odd but those are the rules. - n.IsFloat = true - return n, nil - case itemComplex: - // fmt.Sscan can parse the pair, so let it do the work. - if _, err := fmt.Sscan(text, &n.Complex128); err != nil { - return nil, err - } - n.IsComplex = true - n.simplifyComplex() - return n, nil - } - // Imaginary constants can only be complex unless they are zero. - if len(text) > 0 && text[len(text)-1] == 'i' { - f, err := strconv.ParseFloat(text[:len(text)-1], 64) - if err == nil { - n.IsComplex = true - n.Complex128 = complex(0, f) - n.simplifyComplex() - return n, nil - } - } - // Do integer test first so we get 0x123 etc. - u, err := strconv.ParseUint(text, 0, 64) // will fail for -0; fixed below. - if err == nil { - n.IsUint = true - n.Uint64 = u - } - i, err := strconv.ParseInt(text, 0, 64) - if err == nil { - n.IsInt = true - n.Int64 = i - if i == 0 { - n.IsUint = true // in case of -0. - n.Uint64 = u - } - } - // If an integer extraction succeeded, promote the float. - if n.IsInt { - n.IsFloat = true - n.Float64 = float64(n.Int64) - } else if n.IsUint { - n.IsFloat = true - n.Float64 = float64(n.Uint64) - } else { - f, err := strconv.ParseFloat(text, 64) - if err == nil { - // If we parsed it as a float but it looks like an integer, - // it's a huge number too large to fit in an int. Reject it. - if !strings.ContainsAny(text, ".eEpP") { - return nil, fmt.Errorf("integer overflow: %q", text) - } - n.IsFloat = true - n.Float64 = f - // If a floating-point extraction succeeded, extract the int if needed. - if !n.IsInt && float64(int64(f)) == f { - n.IsInt = true - n.Int64 = int64(f) - } - if !n.IsUint && float64(uint64(f)) == f { - n.IsUint = true - n.Uint64 = uint64(f) - } - } - } - if !n.IsInt && !n.IsUint && !n.IsFloat { - return nil, fmt.Errorf("illegal number syntax: %q", text) - } - return n, nil -} - -// simplifyComplex pulls out any other types that are represented by the complex number. -// These all require that the imaginary part be zero. -func (n *NumberNode) simplifyComplex() { - n.IsFloat = imag(n.Complex128) == 0 - if n.IsFloat { - n.Float64 = real(n.Complex128) - n.IsInt = float64(int64(n.Float64)) == n.Float64 - if n.IsInt { - n.Int64 = int64(n.Float64) - } - n.IsUint = float64(uint64(n.Float64)) == n.Float64 - if n.IsUint { - n.Uint64 = uint64(n.Float64) - } - } -} - -func (n *NumberNode) String() string { - return n.Text -} - -func (n *NumberNode) writeTo(sb *strings.Builder) { - sb.WriteString(n.String()) -} - -func (n *NumberNode) tree() *Tree { - return n.tr -} - -func (n *NumberNode) Copy() Node { - nn := new(NumberNode) - *nn = *n // Easy, fast, correct. - return nn -} - -// StringNode holds a string constant. The value has been "unquoted". -type StringNode struct { - NodeType - Pos - tr *Tree - Quoted string // The original text of the string, with quotes. - Text string // The string, after quote processing. -} - -func (t *Tree) newString(pos Pos, orig, text string) *StringNode { - return &StringNode{tr: t, NodeType: NodeString, Pos: pos, Quoted: orig, Text: text} -} - -func (s *StringNode) String() string { - return s.Quoted -} - -func (s *StringNode) writeTo(sb *strings.Builder) { - sb.WriteString(s.String()) -} - -func (s *StringNode) tree() *Tree { - return s.tr -} - -func (s *StringNode) Copy() Node { - return s.tr.newString(s.Pos, s.Quoted, s.Text) -} - -// endNode represents an {{end}} action. -// It does not appear in the final parse tree. -type endNode struct { - NodeType - Pos - tr *Tree -} - -func (t *Tree) newEnd(pos Pos) *endNode { - return &endNode{tr: t, NodeType: nodeEnd, Pos: pos} -} - -func (e *endNode) String() string { - return "{{end}}" -} - -func (e *endNode) writeTo(sb *strings.Builder) { - sb.WriteString(e.String()) -} - -func (e *endNode) tree() *Tree { - return e.tr -} - -func (e *endNode) Copy() Node { - return e.tr.newEnd(e.Pos) -} - -// elseNode represents an {{else}} action. Does not appear in the final tree. -type elseNode struct { - NodeType - Pos - tr *Tree - Line int // The line number in the input. Deprecated: Kept for compatibility. -} - -func (t *Tree) newElse(pos Pos, line int) *elseNode { - return &elseNode{tr: t, NodeType: nodeElse, Pos: pos, Line: line} -} - -func (e *elseNode) Type() NodeType { - return nodeElse -} - -func (e *elseNode) String() string { - return "{{else}}" -} - -func (e *elseNode) writeTo(sb *strings.Builder) { - sb.WriteString(e.String()) -} - -func (e *elseNode) tree() *Tree { - return e.tr -} - -func (e *elseNode) Copy() Node { - return e.tr.newElse(e.Pos, e.Line) -} - -// BranchNode is the common representation of if, range, and with. -type BranchNode struct { - NodeType - Pos - tr *Tree - Line int // The line number in the input. Deprecated: Kept for compatibility. - Pipe *PipeNode // The pipeline to be evaluated. - List *ListNode // What to execute if the value is non-empty. - ElseList *ListNode // What to execute if the value is empty (nil if absent). -} - -func (b *BranchNode) String() string { - var sb strings.Builder - b.writeTo(&sb) - return sb.String() -} - -func (b *BranchNode) writeTo(sb *strings.Builder) { - name := "" - switch b.NodeType { - case NodeIf: - name = "if" - case NodeRange: - name = "range" - case NodeWith: - name = "with" - default: - panic("unknown branch type") - } - sb.WriteString("{{") - sb.WriteString(name) - sb.WriteByte(' ') - b.Pipe.writeTo(sb) - sb.WriteString("}}") - b.List.writeTo(sb) - if b.ElseList != nil { - sb.WriteString("{{else}}") - b.ElseList.writeTo(sb) - } - sb.WriteString("{{end}}") -} - -func (b *BranchNode) tree() *Tree { - return b.tr -} - -func (b *BranchNode) Copy() Node { - switch b.NodeType { - case NodeIf: - return b.tr.newIf(b.Pos, b.Line, b.Pipe, b.List, b.ElseList) - case NodeRange: - return b.tr.newRange(b.Pos, b.Line, b.Pipe, b.List, b.ElseList) - case NodeWith: - return b.tr.newWith(b.Pos, b.Line, b.Pipe, b.List, b.ElseList) - default: - panic("unknown branch type") - } -} - -// IfNode represents an {{if}} action and its commands. -type IfNode struct { - BranchNode -} - -func (t *Tree) newIf(pos Pos, line int, pipe *PipeNode, list, elseList *ListNode) *IfNode { - return &IfNode{BranchNode{tr: t, NodeType: NodeIf, Pos: pos, Line: line, Pipe: pipe, List: list, ElseList: elseList}} -} - -func (i *IfNode) Copy() Node { - return i.tr.newIf(i.Pos, i.Line, i.Pipe.CopyPipe(), i.List.CopyList(), i.ElseList.CopyList()) -} - -// BreakNode represents a {{break}} action. -type BreakNode struct { - tr *Tree - NodeType - Pos - Line int -} - -func (t *Tree) newBreak(pos Pos, line int) *BreakNode { - return &BreakNode{tr: t, NodeType: NodeBreak, Pos: pos, Line: line} -} - -func (b *BreakNode) Copy() Node { return b.tr.newBreak(b.Pos, b.Line) } -func (b *BreakNode) String() string { return "{{break}}" } -func (b *BreakNode) tree() *Tree { return b.tr } -func (b *BreakNode) writeTo(sb *strings.Builder) { sb.WriteString("{{break}}") } - -// ContinueNode represents a {{continue}} action. -type ContinueNode struct { - tr *Tree - NodeType - Pos - Line int -} - -func (t *Tree) newContinue(pos Pos, line int) *ContinueNode { - return &ContinueNode{tr: t, NodeType: NodeContinue, Pos: pos, Line: line} -} - -func (c *ContinueNode) Copy() Node { return c.tr.newContinue(c.Pos, c.Line) } -func (c *ContinueNode) String() string { return "{{continue}}" } -func (c *ContinueNode) tree() *Tree { return c.tr } -func (c *ContinueNode) writeTo(sb *strings.Builder) { sb.WriteString("{{continue}}") } - -// RangeNode represents a {{range}} action and its commands. -type RangeNode struct { - BranchNode -} - -func (t *Tree) newRange(pos Pos, line int, pipe *PipeNode, list, elseList *ListNode) *RangeNode { - return &RangeNode{BranchNode{tr: t, NodeType: NodeRange, Pos: pos, Line: line, Pipe: pipe, List: list, ElseList: elseList}} -} - -func (r *RangeNode) Copy() Node { - return r.tr.newRange(r.Pos, r.Line, r.Pipe.CopyPipe(), r.List.CopyList(), r.ElseList.CopyList()) -} - -// WithNode represents a {{with}} action and its commands. -type WithNode struct { - BranchNode -} - -func (t *Tree) newWith(pos Pos, line int, pipe *PipeNode, list, elseList *ListNode) *WithNode { - return &WithNode{BranchNode{tr: t, NodeType: NodeWith, Pos: pos, Line: line, Pipe: pipe, List: list, ElseList: elseList}} -} - -func (w *WithNode) Copy() Node { - return w.tr.newWith(w.Pos, w.Line, w.Pipe.CopyPipe(), w.List.CopyList(), w.ElseList.CopyList()) -} - -// TemplateNode represents a {{template}} action. -type TemplateNode struct { - NodeType - Pos - tr *Tree - Line int // The line number in the input. Deprecated: Kept for compatibility. - Name string // The name of the template (unquoted). - Pipe *PipeNode // The command to evaluate as dot for the template. -} - -func (t *Tree) newTemplate(pos Pos, line int, name string, pipe *PipeNode) *TemplateNode { - return &TemplateNode{tr: t, NodeType: NodeTemplate, Pos: pos, Line: line, Name: name, Pipe: pipe} -} - -func (t *TemplateNode) String() string { - var sb strings.Builder - t.writeTo(&sb) - return sb.String() -} - -func (t *TemplateNode) writeTo(sb *strings.Builder) { - sb.WriteString("{{template ") - sb.WriteString(strconv.Quote(t.Name)) - if t.Pipe != nil { - sb.WriteByte(' ') - t.Pipe.writeTo(sb) - } - sb.WriteString("}}") -} - -func (t *TemplateNode) tree() *Tree { - return t.tr -} - -func (t *TemplateNode) Copy() Node { - return t.tr.newTemplate(t.Pos, t.Line, t.Name, t.Pipe.CopyPipe()) -}
diff --git a/go.dev/cmd/internal/text/template/parse/parse.go b/go.dev/cmd/internal/text/template/parse/parse.go deleted file mode 100644 index d92bed5..0000000 --- a/go.dev/cmd/internal/text/template/parse/parse.go +++ /dev/null
@@ -1,796 +0,0 @@ -// Copyright 2011 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -// Package parse builds parse trees for templates as defined by text/template -// and html/template. Clients should use those packages to construct templates -// rather than this one, which provides shared internal data structures not -// intended for general use. -package parse - -import ( - "bytes" - "fmt" - "runtime" - "strconv" - "strings" -) - -// Tree is the representation of a single parsed template. -type Tree struct { - Name string // name of the template represented by the tree. - ParseName string // name of the top-level template during parsing, for error messages. - Root *ListNode // top-level root of the tree. - Mode Mode // parsing mode. - text string // text parsed to create the template (or its parent) - // Parsing only; cleared after parse. - funcs []map[string]interface{} - lex *lexer - token [3]item // three-token lookahead for parser. - peekCount int - vars []string // variables defined at the moment. - treeSet map[string]*Tree - actionLine int // line of left delim starting action - rangeDepth int - mode Mode -} - -// A mode value is a set of flags (or 0). Modes control parser behavior. -type Mode uint - -const ( - ParseComments Mode = 1 << iota // parse comments and add them to AST - SkipFuncCheck // do not check that functions are defined -) - -// Copy returns a copy of the Tree. Any parsing state is discarded. -func (t *Tree) Copy() *Tree { - if t == nil { - return nil - } - return &Tree{ - Name: t.Name, - ParseName: t.ParseName, - Root: t.Root.CopyList(), - text: t.text, - } -} - -// Parse returns a map from template name to parse.Tree, created by parsing the -// templates described in the argument string. The top-level template will be -// given the specified name. If an error is encountered, parsing stops and an -// empty map is returned with the error. -func Parse(name, text, leftDelim, rightDelim string, funcs ...map[string]interface{}) (map[string]*Tree, error) { - treeSet := make(map[string]*Tree) - t := New(name) - t.text = text - _, err := t.Parse(text, leftDelim, rightDelim, treeSet, funcs...) - return treeSet, err -} - -// next returns the next token. -func (t *Tree) next() item { - if t.peekCount > 0 { - t.peekCount-- - } else { - t.token[0] = t.lex.nextItem() - } - return t.token[t.peekCount] -} - -// backup backs the input stream up one token. -func (t *Tree) backup() { - t.peekCount++ -} - -// backup2 backs the input stream up two tokens. -// The zeroth token is already there. -func (t *Tree) backup2(t1 item) { - t.token[1] = t1 - t.peekCount = 2 -} - -// backup3 backs the input stream up three tokens -// The zeroth token is already there. -func (t *Tree) backup3(t2, t1 item) { // Reverse order: we're pushing back. - t.token[1] = t1 - t.token[2] = t2 - t.peekCount = 3 -} - -// peek returns but does not consume the next token. -func (t *Tree) peek() item { - if t.peekCount > 0 { - return t.token[t.peekCount-1] - } - t.peekCount = 1 - t.token[0] = t.lex.nextItem() - return t.token[0] -} - -// nextNonSpace returns the next non-space token. -func (t *Tree) nextNonSpace() (token item) { - for { - token = t.next() - if token.typ != itemSpace { - break - } - } - return token -} - -// peekNonSpace returns but does not consume the next non-space token. -func (t *Tree) peekNonSpace() item { - token := t.nextNonSpace() - t.backup() - return token -} - -// Parsing. - -// New allocates a new parse tree with the given name. -func New(name string, funcs ...map[string]interface{}) *Tree { - return &Tree{ - Name: name, - funcs: funcs, - } -} - -// ErrorContext returns a textual representation of the location of the node in the input text. -// The receiver is only used when the node does not have a pointer to the tree inside, -// which can occur in old code. -func (t *Tree) ErrorContext(n Node) (location, context string) { - pos := int(n.Position()) - tree := n.tree() - if tree == nil { - tree = t - } - text := tree.text[:pos] - byteNum := strings.LastIndex(text, "\n") - if byteNum == -1 { - byteNum = pos // On first line. - } else { - byteNum++ // After the newline. - byteNum = pos - byteNum - } - lineNum := 1 + strings.Count(text, "\n") - context = n.String() - return fmt.Sprintf("%s:%d:%d", tree.ParseName, lineNum, byteNum), context -} - -// errorf formats the error and terminates processing. -func (t *Tree) errorf(format string, args ...interface{}) { - t.Root = nil - format = fmt.Sprintf("template: %s:%d: %s", t.ParseName, t.token[0].line, format) - panic(fmt.Errorf(format, args...)) -} - -// error terminates processing. -func (t *Tree) error(err error) { - t.errorf("%s", err) -} - -// expect consumes the next token and guarantees it has the required type. -func (t *Tree) expect(expected itemType, context string) item { - token := t.nextNonSpace() - if token.typ != expected { - t.unexpected(token, context) - } - return token -} - -// expectOneOf consumes the next token and guarantees it has one of the required types. -func (t *Tree) expectOneOf(expected1, expected2 itemType, context string) item { - token := t.nextNonSpace() - if token.typ != expected1 && token.typ != expected2 { - t.unexpected(token, context) - } - return token -} - -// unexpected complains about the token and terminates processing. -func (t *Tree) unexpected(token item, context string) { - if token.typ == itemError { - extra := "" - if t.actionLine != 0 && t.actionLine != token.line { - extra = fmt.Sprintf(" in action started at %s:%d", t.ParseName, t.actionLine) - if strings.HasSuffix(token.val, " action") { - extra = extra[len(" in action"):] // avoid "action in action" - } - } - t.errorf("%s%s", token, extra) - } - t.errorf("unexpected %s in %s", token, context) -} - -// recover is the handler that turns panics into returns from the top level of Parse. -func (t *Tree) recover(errp *error) { - e := recover() - if e != nil { - if _, ok := e.(runtime.Error); ok { - panic(e) - } - if t != nil { - t.lex.drain() - t.stopParse() - } - *errp = e.(error) - } -} - -// startParse initializes the parser, using the lexer. -func (t *Tree) startParse(funcs []map[string]interface{}, lex *lexer, treeSet map[string]*Tree) { - t.Root = nil - t.lex = lex - t.vars = []string{"$"} - t.funcs = funcs - t.treeSet = treeSet - lex.breakOK = !t.hasFunction("break") - lex.continueOK = !t.hasFunction("continue") -} - -// stopParse terminates parsing. -func (t *Tree) stopParse() { - t.lex = nil - t.vars = nil - t.funcs = nil - t.treeSet = nil -} - -// Parse parses the template definition string to construct a representation of -// the template for execution. If either action delimiter string is empty, the -// default ("{{" or "}}") is used. Embedded template definitions are added to -// the treeSet map. -func (t *Tree) Parse(text, leftDelim, rightDelim string, treeSet map[string]*Tree, funcs ...map[string]interface{}) (tree *Tree, err error) { - defer t.recover(&err) - t.ParseName = t.Name - emitComment := t.Mode&ParseComments != 0 - t.startParse(funcs, lex(t.Name, text, leftDelim, rightDelim, emitComment), treeSet) - t.text = text - t.parse() - t.add() - t.stopParse() - return t, nil -} - -// add adds tree to t.treeSet. -func (t *Tree) add() { - tree := t.treeSet[t.Name] - if tree == nil || IsEmptyTree(tree.Root) { - t.treeSet[t.Name] = t - return - } - if !IsEmptyTree(t.Root) { - t.errorf("template: multiple definition of template %q", t.Name) - } -} - -// IsEmptyTree reports whether this tree (node) is empty of everything but space or comments. -func IsEmptyTree(n Node) bool { - switch n := n.(type) { - case nil: - return true - case *ActionNode: - case *CommentNode: - return true - case *IfNode: - case *ListNode: - for _, node := range n.Nodes { - if !IsEmptyTree(node) { - return false - } - } - return true - case *RangeNode: - case *TemplateNode: - case *TextNode: - return len(bytes.TrimSpace(n.Text)) == 0 - case *WithNode: - default: - panic("unknown node: " + n.String()) - } - return false -} - -// parse is the top-level parser for a template, essentially the same -// as itemList except it also parses {{define}} actions. -// It runs to EOF. -func (t *Tree) parse() { - t.Root = t.newList(t.peek().pos) - for t.peek().typ != itemEOF { - if t.peek().typ == itemLeftDelim { - delim := t.next() - if t.nextNonSpace().typ == itemDefine { - newT := New("definition") // name will be updated once we know it. - newT.text = t.text - newT.Mode = t.Mode - newT.ParseName = t.ParseName - newT.startParse(t.funcs, t.lex, t.treeSet) - newT.parseDefinition() - continue - } - t.backup2(delim) - } - switch n := t.textOrAction(); n.Type() { - case nodeEnd, nodeElse: - t.errorf("unexpected %s", n) - default: - t.Root.append(n) - } - } -} - -// parseDefinition parses a {{define}} ... {{end}} template definition and -// installs the definition in t.treeSet. The "define" keyword has already -// been scanned. -func (t *Tree) parseDefinition() { - const context = "define clause" - name := t.expectOneOf(itemString, itemRawString, context) - var err error - t.Name, err = strconv.Unquote(name.val) - if err != nil { - t.error(err) - } - t.expect(itemRightDelim, context) - var end Node - t.Root, end = t.itemList() - if end.Type() != nodeEnd { - t.errorf("unexpected %s in %s", end, context) - } - t.add() - t.stopParse() -} - -// itemList: -// textOrAction* -// Terminates at {{end}} or {{else}}, returned separately. -func (t *Tree) itemList() (list *ListNode, next Node) { - list = t.newList(t.peekNonSpace().pos) - for t.peekNonSpace().typ != itemEOF { - n := t.textOrAction() - switch n.Type() { - case nodeEnd, nodeElse: - return list, n - } - list.append(n) - } - t.errorf("unexpected EOF") - return -} - -// textOrAction: -// text | comment | action -func (t *Tree) textOrAction() Node { - switch token := t.nextNonSpace(); token.typ { - case itemText: - return t.newText(token.pos, token.val) - case itemLeftDelim: - t.actionLine = token.line - defer t.clearActionLine() - return t.action() - case itemComment: - return t.newComment(token.pos, token.val) - default: - t.unexpected(token, "input") - } - return nil -} - -func (t *Tree) clearActionLine() { - t.actionLine = 0 -} - -// Action: -// control -// command ("|" command)* -// Left delim is past. Now get actions. -// First word could be a keyword such as range. -func (t *Tree) action() (n Node) { - switch token := t.nextNonSpace(); token.typ { - case itemBlock: - return t.blockControl() - case itemBreak: - return t.breakControl(token.pos, token.line) - case itemContinue: - return t.continueControl(token.pos, token.line) - case itemElse: - return t.elseControl() - case itemEnd: - return t.endControl() - case itemIf: - return t.ifControl() - case itemRange: - return t.rangeControl() - case itemTemplate: - return t.templateControl() - case itemWith: - return t.withControl() - } - t.backup() - token := t.peek() - // Do not pop variables; they persist until "end". - return t.newAction(token.pos, token.line, t.pipeline("command", itemRightDelim)) -} - -// Break: -// {{break}} -// Break keyword is past. -func (t *Tree) breakControl(pos Pos, line int) Node { - if token := t.next(); token.typ != itemRightDelim { - t.unexpected(token, "in {{break}}") - } - if t.rangeDepth == 0 { - t.errorf("{{break}} outside {{range}}") - } - return t.newBreak(pos, line) -} - -// Continue: -// {{continue}} -// Continue keyword is past. -func (t *Tree) continueControl(pos Pos, line int) Node { - if token := t.next(); token.typ != itemRightDelim { - t.unexpected(token, "in {{continue}}") - } - if t.rangeDepth == 0 { - t.errorf("{{continue}} outside {{range}}") - } - return t.newContinue(pos, line) -} - -// Pipeline: -// declarations? command ('|' command)* -func (t *Tree) pipeline(context string, end itemType) (pipe *PipeNode) { - token := t.peekNonSpace() - pipe = t.newPipeline(token.pos, token.line, nil) - // Are there declarations or assignments? -decls: - if v := t.peekNonSpace(); v.typ == itemVariable { - t.next() - // Since space is a token, we need 3-token look-ahead here in the worst case: - // in "$x foo" we need to read "foo" (as opposed to ":=") to know that $x is an - // argument variable rather than a declaration. So remember the token - // adjacent to the variable so we can push it back if necessary. - tokenAfterVariable := t.peek() - next := t.peekNonSpace() - switch { - case next.typ == itemAssign, next.typ == itemDeclare: - pipe.IsAssign = next.typ == itemAssign - t.nextNonSpace() - pipe.Decl = append(pipe.Decl, t.newVariable(v.pos, v.val)) - t.vars = append(t.vars, v.val) - case next.typ == itemChar && next.val == ",": - t.nextNonSpace() - pipe.Decl = append(pipe.Decl, t.newVariable(v.pos, v.val)) - t.vars = append(t.vars, v.val) - if context == "range" && len(pipe.Decl) < 2 { - switch t.peekNonSpace().typ { - case itemVariable, itemRightDelim, itemRightParen: - // second initialized variable in a range pipeline - goto decls - default: - t.errorf("range can only initialize variables") - } - } - t.errorf("too many declarations in %s", context) - case tokenAfterVariable.typ == itemSpace: - t.backup3(v, tokenAfterVariable) - default: - t.backup2(v) - } - } - for { - switch token := t.nextNonSpace(); token.typ { - case end: - // At this point, the pipeline is complete - t.checkPipeline(pipe, context) - return - case itemBool, itemCharConstant, itemComplex, itemDot, itemField, itemIdentifier, - itemNumber, itemNil, itemRawString, itemString, itemVariable, itemLeftParen: - t.backup() - pipe.append(t.command()) - default: - t.unexpected(token, context) - } - } -} - -func (t *Tree) checkPipeline(pipe *PipeNode, context string) { - // Reject empty pipelines - if len(pipe.Cmds) == 0 { - t.errorf("missing value for %s", context) - } - // Only the first command of a pipeline can start with a non executable operand - for i, c := range pipe.Cmds[1:] { - switch c.Args[0].Type() { - case NodeBool, NodeDot, NodeNil, NodeNumber, NodeString: - // With A|B|C, pipeline stage 2 is B - t.errorf("non executable command in pipeline stage %d", i+2) - } - } -} - -func (t *Tree) parseControl(allowElseIf bool, context string) (pos Pos, line int, pipe *PipeNode, list, elseList *ListNode) { - defer t.popVars(len(t.vars)) - pipe = t.pipeline(context, itemRightDelim) - if context == "range" { - t.rangeDepth++ - } - var next Node - list, next = t.itemList() - if context == "range" { - t.rangeDepth-- - } - switch next.Type() { - case nodeEnd: //done - case nodeElse: - if allowElseIf { - // Special case for "else if". If the "else" is followed immediately by an "if", - // the elseControl will have left the "if" token pending. Treat - // {{if a}}_{{else if b}}_{{end}} - // as - // {{if a}}_{{else}}{{if b}}_{{end}}{{end}}. - // To do this, parse the if as usual and stop at it {{end}}; the subsequent{{end}} - // is assumed. This technique works even for long if-else-if chains. - // TODO: Should we allow else-if in with and range? - if t.peek().typ == itemIf { - t.next() // Consume the "if" token. - elseList = t.newList(next.Position()) - elseList.append(t.ifControl()) - // Do not consume the next item - only one {{end}} required. - break - } - } - elseList, next = t.itemList() - if next.Type() != nodeEnd { - t.errorf("expected end; found %s", next) - } - } - return pipe.Position(), pipe.Line, pipe, list, elseList -} - -// If: -// {{if pipeline}} itemList {{end}} -// {{if pipeline}} itemList {{else}} itemList {{end}} -// If keyword is past. -func (t *Tree) ifControl() Node { - return t.newIf(t.parseControl(true, "if")) -} - -// Range: -// {{range pipeline}} itemList {{end}} -// {{range pipeline}} itemList {{else}} itemList {{end}} -// Range keyword is past. -func (t *Tree) rangeControl() Node { - r := t.newRange(t.parseControl(false, "range")) - return r -} - -// With: -// {{with pipeline}} itemList {{end}} -// {{with pipeline}} itemList {{else}} itemList {{end}} -// If keyword is past. -func (t *Tree) withControl() Node { - return t.newWith(t.parseControl(false, "with")) -} - -// End: -// {{end}} -// End keyword is past. -func (t *Tree) endControl() Node { - return t.newEnd(t.expect(itemRightDelim, "end").pos) -} - -// Else: -// {{else}} -// Else keyword is past. -func (t *Tree) elseControl() Node { - // Special case for "else if". - peek := t.peekNonSpace() - if peek.typ == itemIf { - // We see "{{else if ... " but in effect rewrite it to {{else}}{{if ... ". - return t.newElse(peek.pos, peek.line) - } - token := t.expect(itemRightDelim, "else") - return t.newElse(token.pos, token.line) -} - -// Block: -// {{block stringValue pipeline}} -// Block keyword is past. -// The name must be something that can evaluate to a string. -// The pipeline is mandatory. -func (t *Tree) blockControl() Node { - const context = "block clause" - - token := t.nextNonSpace() - name := t.parseTemplateName(token, context) - pipe := t.pipeline(context, itemRightDelim) - - block := New(name) // name will be updated once we know it. - block.text = t.text - block.Mode = t.Mode - block.ParseName = t.ParseName - block.startParse(t.funcs, t.lex, t.treeSet) - var end Node - block.Root, end = block.itemList() - if end.Type() != nodeEnd { - t.errorf("unexpected %s in %s", end, context) - } - block.add() - block.stopParse() - - return t.newTemplate(token.pos, token.line, name, pipe) -} - -// Template: -// {{template stringValue pipeline}} -// Template keyword is past. The name must be something that can evaluate -// to a string. -func (t *Tree) templateControl() Node { - const context = "template clause" - token := t.nextNonSpace() - name := t.parseTemplateName(token, context) - var pipe *PipeNode - if t.nextNonSpace().typ != itemRightDelim { - t.backup() - // Do not pop variables; they persist until "end". - pipe = t.pipeline(context, itemRightDelim) - } - return t.newTemplate(token.pos, token.line, name, pipe) -} - -func (t *Tree) parseTemplateName(token item, context string) (name string) { - switch token.typ { - case itemString, itemRawString: - s, err := strconv.Unquote(token.val) - if err != nil { - t.error(err) - } - name = s - default: - t.unexpected(token, context) - } - return -} - -// command: -// operand (space operand)* -// space-separated arguments up to a pipeline character or right delimiter. -// we consume the pipe character but leave the right delim to terminate the action. -func (t *Tree) command() *CommandNode { - cmd := t.newCommand(t.peekNonSpace().pos) - for { - t.peekNonSpace() // skip leading spaces. - operand := t.operand() - if operand != nil { - cmd.append(operand) - } - switch token := t.next(); token.typ { - case itemSpace: - continue - case itemRightDelim, itemRightParen: - t.backup() - case itemPipe: - // nothing here; break loop below - default: - t.unexpected(token, "operand") - } - break - } - if len(cmd.Args) == 0 { - t.errorf("empty command") - } - return cmd -} - -// operand: -// term .Field* -// An operand is a space-separated component of a command, -// a term possibly followed by field accesses. -// A nil return means the next item is not an operand. -func (t *Tree) operand() Node { - node := t.term() - if node == nil { - return nil - } - if t.peek().typ == itemField { - chain := t.newChain(t.peek().pos, node) - for t.peek().typ == itemField { - chain.Add(t.next().val) - } - // Compatibility with original API: If the term is of type NodeField - // or NodeVariable, just put more fields on the original. - // Otherwise, keep the Chain node. - // Obvious parsing errors involving literal values are detected here. - // More complex error cases will have to be handled at execution time. - switch node.Type() { - case NodeField: - node = t.newField(chain.Position(), chain.String()) - case NodeVariable: - node = t.newVariable(chain.Position(), chain.String()) - case NodeBool, NodeString, NodeNumber, NodeNil, NodeDot: - t.errorf("unexpected . after term %q", node.String()) - default: - node = chain - } - } - return node -} - -// term: -// literal (number, string, nil, boolean) -// function (identifier) -// . -// .Field -// $ -// '(' pipeline ')' -// A term is a simple "expression". -// A nil return means the next item is not a term. -func (t *Tree) term() Node { - switch token := t.nextNonSpace(); token.typ { - case itemIdentifier: - checkFunc := t.Mode&SkipFuncCheck == 0 - if checkFunc && !t.hasFunction(token.val) { - t.errorf("function %q not defined", token.val) - } - return NewIdentifier(token.val).SetTree(t).SetPos(token.pos) - case itemDot: - return t.newDot(token.pos) - case itemNil: - return t.newNil(token.pos) - case itemVariable: - return t.useVar(token.pos, token.val) - case itemField: - return t.newField(token.pos, token.val) - case itemBool: - return t.newBool(token.pos, token.val == "true") - case itemCharConstant, itemComplex, itemNumber: - number, err := t.newNumber(token.pos, token.val, token.typ) - if err != nil { - t.error(err) - } - return number - case itemLeftParen: - return t.pipeline("parenthesized pipeline", itemRightParen) - case itemString, itemRawString: - s, err := strconv.Unquote(token.val) - if err != nil { - t.error(err) - } - return t.newString(token.pos, token.val, s) - } - t.backup() - return nil -} - -// hasFunction reports if a function name exists in the Tree's maps. -func (t *Tree) hasFunction(name string) bool { - for _, funcMap := range t.funcs { - if funcMap == nil { - continue - } - if funcMap[name] != nil { - return true - } - } - return false -} - -// popVars trims the variable list to the specified length -func (t *Tree) popVars(n int) { - t.vars = t.vars[:n] -} - -// useVar returns a node for a variable reference. It errors if the -// variable is not defined. -func (t *Tree) useVar(pos Pos, name string) Node { - v := t.newVariable(pos, name) - for _, varName := range t.vars { - if varName == v.Ident[0] { - return v - } - } - t.errorf("undefined variable %q", v.Ident[0]) - return nil -}
diff --git a/go.dev/cmd/internal/text/template/parse/parse_test.go b/go.dev/cmd/internal/text/template/parse/parse_test.go deleted file mode 100644 index c3679a0..0000000 --- a/go.dev/cmd/internal/text/template/parse/parse_test.go +++ /dev/null
@@ -1,676 +0,0 @@ -// Copyright 2011 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package parse - -import ( - "flag" - "fmt" - "strings" - "testing" -) - -var debug = flag.Bool("debug", false, "show the errors produced by the main tests") - -type numberTest struct { - text string - isInt bool - isUint bool - isFloat bool - isComplex bool - int64 - uint64 - float64 - complex128 -} - -var numberTests = []numberTest{ - // basics - {"0", true, true, true, false, 0, 0, 0, 0}, - {"-0", true, true, true, false, 0, 0, 0, 0}, // check that -0 is a uint. - {"73", true, true, true, false, 73, 73, 73, 0}, - {"7_3", true, true, true, false, 73, 73, 73, 0}, - {"0b10_010_01", true, true, true, false, 73, 73, 73, 0}, - {"0B10_010_01", true, true, true, false, 73, 73, 73, 0}, - {"073", true, true, true, false, 073, 073, 073, 0}, - {"0o73", true, true, true, false, 073, 073, 073, 0}, - {"0O73", true, true, true, false, 073, 073, 073, 0}, - {"0x73", true, true, true, false, 0x73, 0x73, 0x73, 0}, - {"0X73", true, true, true, false, 0x73, 0x73, 0x73, 0}, - {"0x7_3", true, true, true, false, 0x73, 0x73, 0x73, 0}, - {"-73", true, false, true, false, -73, 0, -73, 0}, - {"+73", true, false, true, false, 73, 0, 73, 0}, - {"100", true, true, true, false, 100, 100, 100, 0}, - {"1e9", true, true, true, false, 1e9, 1e9, 1e9, 0}, - {"-1e9", true, false, true, false, -1e9, 0, -1e9, 0}, - {"-1.2", false, false, true, false, 0, 0, -1.2, 0}, - {"1e19", false, true, true, false, 0, 1e19, 1e19, 0}, - {"1e1_9", false, true, true, false, 0, 1e19, 1e19, 0}, - {"1E19", false, true, true, false, 0, 1e19, 1e19, 0}, - {"-1e19", false, false, true, false, 0, 0, -1e19, 0}, - {"0x_1p4", true, true, true, false, 16, 16, 16, 0}, - {"0X_1P4", true, true, true, false, 16, 16, 16, 0}, - {"0x_1p-4", false, false, true, false, 0, 0, 1 / 16., 0}, - {"4i", false, false, false, true, 0, 0, 0, 4i}, - {"-1.2+4.2i", false, false, false, true, 0, 0, 0, -1.2 + 4.2i}, - {"073i", false, false, false, true, 0, 0, 0, 73i}, // not octal! - // complex with 0 imaginary are float (and maybe integer) - {"0i", true, true, true, true, 0, 0, 0, 0}, - {"-1.2+0i", false, false, true, true, 0, 0, -1.2, -1.2}, - {"-12+0i", true, false, true, true, -12, 0, -12, -12}, - {"13+0i", true, true, true, true, 13, 13, 13, 13}, - // funny bases - {"0123", true, true, true, false, 0123, 0123, 0123, 0}, - {"-0x0", true, true, true, false, 0, 0, 0, 0}, - {"0xdeadbeef", true, true, true, false, 0xdeadbeef, 0xdeadbeef, 0xdeadbeef, 0}, - // character constants - {`'a'`, true, true, true, false, 'a', 'a', 'a', 0}, - {`'\n'`, true, true, true, false, '\n', '\n', '\n', 0}, - {`'\\'`, true, true, true, false, '\\', '\\', '\\', 0}, - {`'\''`, true, true, true, false, '\'', '\'', '\'', 0}, - {`'\xFF'`, true, true, true, false, 0xFF, 0xFF, 0xFF, 0}, - {`'パ'`, true, true, true, false, 0x30d1, 0x30d1, 0x30d1, 0}, - {`'\u30d1'`, true, true, true, false, 0x30d1, 0x30d1, 0x30d1, 0}, - {`'\U000030d1'`, true, true, true, false, 0x30d1, 0x30d1, 0x30d1, 0}, - // some broken syntax - {text: "+-2"}, - {text: "0x123."}, - {text: "1e."}, - {text: "0xi."}, - {text: "1+2."}, - {text: "'x"}, - {text: "'xx'"}, - {text: "'433937734937734969526500969526500'"}, // Integer too large - issue 10634. - // Issue 8622 - 0xe parsed as floating point. Very embarrassing. - {"0xef", true, true, true, false, 0xef, 0xef, 0xef, 0}, -} - -func TestNumberParse(t *testing.T) { - for _, test := range numberTests { - // If fmt.Sscan thinks it's complex, it's complex. We can't trust the output - // because imaginary comes out as a number. - var c complex128 - typ := itemNumber - var tree *Tree - if test.text[0] == '\'' { - typ = itemCharConstant - } else { - _, err := fmt.Sscan(test.text, &c) - if err == nil { - typ = itemComplex - } - } - n, err := tree.newNumber(0, test.text, typ) - ok := test.isInt || test.isUint || test.isFloat || test.isComplex - if ok && err != nil { - t.Errorf("unexpected error for %q: %s", test.text, err) - continue - } - if !ok && err == nil { - t.Errorf("expected error for %q", test.text) - continue - } - if !ok { - if *debug { - fmt.Printf("%s\n\t%s\n", test.text, err) - } - continue - } - if n.IsComplex != test.isComplex { - t.Errorf("complex incorrect for %q; should be %t", test.text, test.isComplex) - } - if test.isInt { - if !n.IsInt { - t.Errorf("expected integer for %q", test.text) - } - if n.Int64 != test.int64 { - t.Errorf("int64 for %q should be %d Is %d", test.text, test.int64, n.Int64) - } - } else if n.IsInt { - t.Errorf("did not expect integer for %q", test.text) - } - if test.isUint { - if !n.IsUint { - t.Errorf("expected unsigned integer for %q", test.text) - } - if n.Uint64 != test.uint64 { - t.Errorf("uint64 for %q should be %d Is %d", test.text, test.uint64, n.Uint64) - } - } else if n.IsUint { - t.Errorf("did not expect unsigned integer for %q", test.text) - } - if test.isFloat { - if !n.IsFloat { - t.Errorf("expected float for %q", test.text) - } - if n.Float64 != test.float64 { - t.Errorf("float64 for %q should be %g Is %g", test.text, test.float64, n.Float64) - } - } else if n.IsFloat { - t.Errorf("did not expect float for %q", test.text) - } - if test.isComplex { - if !n.IsComplex { - t.Errorf("expected complex for %q", test.text) - } - if n.Complex128 != test.complex128 { - t.Errorf("complex128 for %q should be %g Is %g", test.text, test.complex128, n.Complex128) - } - } else if n.IsComplex { - t.Errorf("did not expect complex for %q", test.text) - } - } -} - -type parseTest struct { - name string - input string - ok bool - result string // what the user would see in an error message. -} - -const ( - noError = true - hasError = false -) - -var parseTests = []parseTest{ - {"empty", "", noError, - ``}, - {"comment", "{{/*\n\n\n*/}}", noError, - ``}, - {"spaces", " \t\n", noError, - `" \t\n"`}, - {"text", "some text", noError, - `"some text"`}, - {"emptyAction", "{{}}", hasError, - `{{}}`}, - {"field", "{{.X}}", noError, - `{{.X}}`}, - {"simple command", "{{printf}}", noError, - `{{printf}}`}, - {"$ invocation", "{{$}}", noError, - "{{$}}"}, - {"variable invocation", "{{with $x := 3}}{{$x 23}}{{end}}", noError, - "{{with $x := 3}}{{$x 23}}{{end}}"}, - {"variable with fields", "{{$.I}}", noError, - "{{$.I}}"}, - {"multi-word command", "{{printf `%d` 23}}", noError, - "{{printf `%d` 23}}"}, - {"pipeline", "{{.X|.Y}}", noError, - `{{.X | .Y}}`}, - {"pipeline with decl", "{{$x := .X|.Y}}", noError, - `{{$x := .X | .Y}}`}, - {"nested pipeline", "{{.X (.Y .Z) (.A | .B .C) (.E)}}", noError, - `{{.X (.Y .Z) (.A | .B .C) (.E)}}`}, - {"field applied to parentheses", "{{(.Y .Z).Field}}", noError, - `{{(.Y .Z).Field}}`}, - {"simple if", "{{if .X}}hello{{end}}", noError, - `{{if .X}}"hello"{{end}}`}, - {"if with else", "{{if .X}}true{{else}}false{{end}}", noError, - `{{if .X}}"true"{{else}}"false"{{end}}`}, - {"if with else if", "{{if .X}}true{{else if .Y}}false{{end}}", noError, - `{{if .X}}"true"{{else}}{{if .Y}}"false"{{end}}{{end}}`}, - {"if else chain", "+{{if .X}}X{{else if .Y}}Y{{else if .Z}}Z{{end}}+", noError, - `"+"{{if .X}}"X"{{else}}{{if .Y}}"Y"{{else}}{{if .Z}}"Z"{{end}}{{end}}{{end}}"+"`}, - {"simple range", "{{range .X}}hello{{end}}", noError, - `{{range .X}}"hello"{{end}}`}, - {"chained field range", "{{range .X.Y.Z}}hello{{end}}", noError, - `{{range .X.Y.Z}}"hello"{{end}}`}, - {"nested range", "{{range .X}}hello{{range .Y}}goodbye{{end}}{{end}}", noError, - `{{range .X}}"hello"{{range .Y}}"goodbye"{{end}}{{end}}`}, - {"range with else", "{{range .X}}true{{else}}false{{end}}", noError, - `{{range .X}}"true"{{else}}"false"{{end}}`}, - {"range over pipeline", "{{range .X|.M}}true{{else}}false{{end}}", noError, - `{{range .X | .M}}"true"{{else}}"false"{{end}}`}, - {"range []int", "{{range .SI}}{{.}}{{end}}", noError, - `{{range .SI}}{{.}}{{end}}`}, - {"range 1 var", "{{range $x := .SI}}{{.}}{{end}}", noError, - `{{range $x := .SI}}{{.}}{{end}}`}, - {"range 2 vars", "{{range $x, $y := .SI}}{{.}}{{end}}", noError, - `{{range $x, $y := .SI}}{{.}}{{end}}`}, - {"range with break", "{{range .SI}}{{.}}{{break}}{{end}}", noError, - `{{range .SI}}{{.}}{{break}}{{end}}`}, - {"range with continue", "{{range .SI}}{{.}}{{continue}}{{end}}", noError, - `{{range .SI}}{{.}}{{continue}}{{end}}`}, - {"constants", "{{range .SI 1 -3.2i true false 'a' nil}}{{end}}", noError, - `{{range .SI 1 -3.2i true false 'a' nil}}{{end}}`}, - {"template", "{{template `x`}}", noError, - `{{template "x"}}`}, - {"template with arg", "{{template `x` .Y}}", noError, - `{{template "x" .Y}}`}, - {"with", "{{with .X}}hello{{end}}", noError, - `{{with .X}}"hello"{{end}}`}, - {"with with else", "{{with .X}}hello{{else}}goodbye{{end}}", noError, - `{{with .X}}"hello"{{else}}"goodbye"{{end}}`}, - // Trimming spaces. - {"trim left", "x \r\n\t{{- 3}}", noError, `"x"{{3}}`}, - {"trim right", "{{3 -}}\n\n\ty", noError, `{{3}}"y"`}, - {"trim left and right", "x \r\n\t{{- 3 -}}\n\n\ty", noError, `"x"{{3}}"y"`}, - {"trim with extra spaces", "x\n{{- 3 -}}\ny", noError, `"x"{{3}}"y"`}, - {"comment trim left", "x \r\n\t{{- /* hi */}}", noError, `"x"`}, - {"comment trim right", "{{/* hi */ -}}\n\n\ty", noError, `"y"`}, - {"comment trim left and right", "x \r\n\t{{- /* */ -}}\n\n\ty", noError, `"x""y"`}, - {"block definition", `{{block "foo" .}}hello{{end}}`, noError, - `{{template "foo" .}}`}, - - {"newline in assignment", "{{ $x \n := \n 1 \n }}", noError, "{{$x := 1}}"}, - {"newline in empty action", "{{\n}}", hasError, "{{\n}}"}, - {"newline in pipeline", "{{\n\"x\"\n|\nprintf\n}}", noError, `{{"x" | printf}}`}, - {"newline in comment", "{{/*\nhello\n*/}}", noError, ""}, - {"newline in comment", "{{-\n/*\nhello\n*/\n-}}", noError, ""}, - - // Errors. - {"unclosed action", "hello{{range", hasError, ""}, - {"unmatched end", "{{end}}", hasError, ""}, - {"unmatched else", "{{else}}", hasError, ""}, - {"unmatched else after if", "{{if .X}}hello{{end}}{{else}}", hasError, ""}, - {"multiple else", "{{if .X}}1{{else}}2{{else}}3{{end}}", hasError, ""}, - {"missing end", "hello{{range .x}}", hasError, ""}, - {"missing end after else", "hello{{range .x}}{{else}}", hasError, ""}, - {"undefined function", "hello{{undefined}}", hasError, ""}, - {"undefined variable", "{{$x}}", hasError, ""}, - {"variable undefined after end", "{{with $x := 4}}{{end}}{{$x}}", hasError, ""}, - {"variable undefined in template", "{{template $v}}", hasError, ""}, - {"declare with field", "{{with $x.Y := 4}}{{end}}", hasError, ""}, - {"template with field ref", "{{template .X}}", hasError, ""}, - {"template with var", "{{template $v}}", hasError, ""}, - {"invalid punctuation", "{{printf 3, 4}}", hasError, ""}, - {"multidecl outside range", "{{with $v, $u := 3}}{{end}}", hasError, ""}, - {"too many decls in range", "{{range $u, $v, $w := 3}}{{end}}", hasError, ""}, - {"dot applied to parentheses", "{{printf (printf .).}}", hasError, ""}, - {"adjacent args", "{{printf 3`x`}}", hasError, ""}, - {"adjacent args with .", "{{printf `x`.}}", hasError, ""}, - {"extra end after if", "{{if .X}}a{{else if .Y}}b{{end}}{{end}}", hasError, ""}, - {"break outside range", "{{range .}}{{end}} {{break}}", hasError, ""}, - {"continue outside range", "{{range .}}{{end}} {{continue}}", hasError, ""}, - {"break in range else", "{{range .}}{{else}}{{break}}{{end}}", hasError, ""}, - {"continue in range else", "{{range .}}{{else}}{{continue}}{{end}}", hasError, ""}, - // Other kinds of assignments and operators aren't available yet. - {"bug0a", "{{$x := 0}}{{$x}}", noError, "{{$x := 0}}{{$x}}"}, - {"bug0b", "{{$x += 1}}{{$x}}", hasError, ""}, - {"bug0c", "{{$x ! 2}}{{$x}}", hasError, ""}, - {"bug0d", "{{$x % 3}}{{$x}}", hasError, ""}, - // Check the parse fails for := rather than comma. - {"bug0e", "{{range $x := $y := 3}}{{end}}", hasError, ""}, - // Another bug: variable read must ignore following punctuation. - {"bug1a", "{{$x:=.}}{{$x!2}}", hasError, ""}, // ! is just illegal here. - {"bug1b", "{{$x:=.}}{{$x+2}}", hasError, ""}, // $x+2 should not parse as ($x) (+2). - {"bug1c", "{{$x:=.}}{{$x +2}}", noError, "{{$x := .}}{{$x +2}}"}, // It's OK with a space. - // dot following a literal value - {"dot after integer", "{{1.E}}", hasError, ""}, - {"dot after float", "{{0.1.E}}", hasError, ""}, - {"dot after boolean", "{{true.E}}", hasError, ""}, - {"dot after char", "{{'a'.any}}", hasError, ""}, - {"dot after string", `{{"hello".guys}}`, hasError, ""}, - {"dot after dot", "{{..E}}", hasError, ""}, - {"dot after nil", "{{nil.E}}", hasError, ""}, - // Wrong pipeline - {"wrong pipeline dot", "{{12|.}}", hasError, ""}, - {"wrong pipeline number", "{{.|12|printf}}", hasError, ""}, - {"wrong pipeline string", "{{.|printf|\"error\"}}", hasError, ""}, - {"wrong pipeline char", "{{12|printf|'e'}}", hasError, ""}, - {"wrong pipeline boolean", "{{.|true}}", hasError, ""}, - {"wrong pipeline nil", "{{'c'|nil}}", hasError, ""}, - {"empty pipeline", `{{printf "%d" ( ) }}`, hasError, ""}, - // Missing pipeline in block - {"block definition", `{{block "foo"}}hello{{end}}`, hasError, ""}, -} - -var builtins = map[string]interface{}{ - "printf": fmt.Sprintf, - "contains": strings.Contains, -} - -func testParse(doCopy bool, t *testing.T) { - textFormat = "%q" - defer func() { textFormat = "%s" }() - for _, test := range parseTests { - tmpl, err := New(test.name).Parse(test.input, "", "", make(map[string]*Tree), builtins) - switch { - case err == nil && !test.ok: - t.Errorf("%q: expected error; got none", test.name) - continue - case err != nil && test.ok: - t.Errorf("%q: unexpected error: %v", test.name, err) - continue - case err != nil && !test.ok: - // expected error, got one - if *debug { - fmt.Printf("%s: %s\n\t%s\n", test.name, test.input, err) - } - continue - } - var result string - if doCopy { - result = tmpl.Root.Copy().String() - } else { - result = tmpl.Root.String() - } - if result != test.result { - t.Errorf("%s=(%q): got\n\t%v\nexpected\n\t%v", test.name, test.input, result, test.result) - } - } -} - -func TestParse(t *testing.T) { - testParse(false, t) -} - -// Same as TestParse, but we copy the node first -func TestParseCopy(t *testing.T) { - testParse(true, t) -} - -func TestParseWithComments(t *testing.T) { - textFormat = "%q" - defer func() { textFormat = "%s" }() - tests := [...]parseTest{ - {"comment", "{{/*\n\n\n*/}}", noError, "{{/*\n\n\n*/}}"}, - {"comment trim left", "x \r\n\t{{- /* hi */}}", noError, `"x"{{/* hi */}}`}, - {"comment trim right", "{{/* hi */ -}}\n\n\ty", noError, `{{/* hi */}}"y"`}, - {"comment trim left and right", "x \r\n\t{{- /* */ -}}\n\n\ty", noError, `"x"{{/* */}}"y"`}, - } - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - tr := New(test.name) - tr.Mode = ParseComments - tmpl, err := tr.Parse(test.input, "", "", make(map[string]*Tree)) - if err != nil { - t.Errorf("%q: expected error; got none", test.name) - } - if result := tmpl.Root.String(); result != test.result { - t.Errorf("%s=(%q): got\n\t%v\nexpected\n\t%v", test.name, test.input, result, test.result) - } - }) - } -} - -func TestSkipFuncCheck(t *testing.T) { - oldTextFormat := textFormat - textFormat = "%q" - defer func() { textFormat = oldTextFormat }() - tr := New("skip func check") - tr.Mode = SkipFuncCheck - tmpl, err := tr.Parse("{{fn 1 2}}", "", "", make(map[string]*Tree)) - if err != nil { - t.Fatalf("unexpected error: %v", err) - } - expected := "{{fn 1 2}}" - if result := tmpl.Root.String(); result != expected { - t.Errorf("got\n\t%v\nexpected\n\t%v", result, expected) - } -} - -type isEmptyTest struct { - name string - input string - empty bool -} - -var isEmptyTests = []isEmptyTest{ - {"empty", ``, true}, - {"nonempty", `hello`, false}, - {"spaces only", " \t\n \t\n", true}, - {"comment only", "{{/* comment */}}", true}, - {"definition", `{{define "x"}}something{{end}}`, true}, - {"definitions and space", "{{define `x`}}something{{end}}\n\n{{define `y`}}something{{end}}\n\n", true}, - {"definitions and text", "{{define `x`}}something{{end}}\nx\n{{define `y`}}something{{end}}\ny\n", false}, - {"definition and action", "{{define `x`}}something{{end}}{{if 3}}foo{{end}}", false}, -} - -func TestIsEmpty(t *testing.T) { - if !IsEmptyTree(nil) { - t.Errorf("nil tree is not empty") - } - for _, test := range isEmptyTests { - tree, err := New("root").Parse(test.input, "", "", make(map[string]*Tree), nil) - if err != nil { - t.Errorf("%q: unexpected error: %v", test.name, err) - continue - } - if empty := IsEmptyTree(tree.Root); empty != test.empty { - t.Errorf("%q: expected %t got %t", test.name, test.empty, empty) - } - } -} - -func TestErrorContextWithTreeCopy(t *testing.T) { - tree, err := New("root").Parse("{{if true}}{{end}}", "", "", make(map[string]*Tree), nil) - if err != nil { - t.Fatalf("unexpected tree parse failure: %v", err) - } - treeCopy := tree.Copy() - wantLocation, wantContext := tree.ErrorContext(tree.Root.Nodes[0]) - gotLocation, gotContext := treeCopy.ErrorContext(treeCopy.Root.Nodes[0]) - if wantLocation != gotLocation { - t.Errorf("wrong error location want %q got %q", wantLocation, gotLocation) - } - if wantContext != gotContext { - t.Errorf("wrong error location want %q got %q", wantContext, gotContext) - } -} - -// All failures, and the result is a string that must appear in the error message. -var errorTests = []parseTest{ - // Check line numbers are accurate. - {"unclosed1", - "line1\n{{", - hasError, `unclosed1:2: unclosed action`}, - {"unclosed2", - "line1\n{{define `x`}}line2\n{{", - hasError, `unclosed2:3: unclosed action`}, - {"unclosed3", - "line1\n{{\"x\"\n\"y\"\n", - hasError, `unclosed3:4: unclosed action started at unclosed3:2`}, - {"unclosed4", - "{{\n\n\n\n\n", - hasError, `unclosed4:6: unclosed action started at unclosed4:1`}, - {"var1", - "line1\n{{\nx\n}}", - hasError, `var1:3: function "x" not defined`}, - // Specific errors. - {"function", - "{{foo}}", - hasError, `function "foo" not defined`}, - {"comment1", - "{{/*}}", - hasError, `comment1:1: unclosed comment`}, - {"comment2", - "{{/*\nhello\n}}", - hasError, `comment2:1: unclosed comment`}, - {"lparen", - "{{.X (1 2 3}}", - hasError, `unclosed left paren`}, - {"rparen", - "{{.X 1 2 3 ) }}", - hasError, `unexpected ")" in command`}, - {"rparen2", - "{{(.X 1 2 3", - hasError, `unclosed action`}, - {"space", - "{{`x`3}}", - hasError, `in operand`}, - {"idchar", - "{{a#}}", - hasError, `'#'`}, - {"charconst", - "{{'a}}", - hasError, `unterminated character constant`}, - {"stringconst", - `{{"a}}`, - hasError, `unterminated quoted string`}, - {"rawstringconst", - "{{`a}}", - hasError, `unterminated raw quoted string`}, - {"number", - "{{0xi}}", - hasError, `number syntax`}, - {"multidefine", - "{{define `a`}}a{{end}}{{define `a`}}b{{end}}", - hasError, `multiple definition of template`}, - {"eof", - "{{range .X}}", - hasError, `unexpected EOF`}, - {"variable", - // Declare $x so it's defined, to avoid that error, and then check we don't parse a declaration. - "{{$x := 23}}{{with $x.y := 3}}{{$x 23}}{{end}}", - hasError, `unexpected ":="`}, - {"multidecl", - "{{$a,$b,$c := 23}}", - hasError, `too many declarations`}, - {"undefvar", - "{{$a}}", - hasError, `undefined variable`}, - {"wrongdot", - "{{true.any}}", - hasError, `unexpected . after term`}, - {"wrongpipeline", - "{{12|false}}", - hasError, `non executable command in pipeline`}, - {"emptypipeline", - `{{ ( ) }}`, - hasError, `missing value for parenthesized pipeline`}, - {"multilinerawstring", - "{{ $v := `\n` }} {{", - hasError, `multilinerawstring:2: unclosed action`}, - {"rangeundefvar", - "{{range $k}}{{end}}", - hasError, `undefined variable`}, - {"rangeundefvars", - "{{range $k, $v}}{{end}}", - hasError, `undefined variable`}, - {"rangemissingvalue1", - "{{range $k,}}{{end}}", - hasError, `missing value for range`}, - {"rangemissingvalue2", - "{{range $k, $v := }}{{end}}", - hasError, `missing value for range`}, - {"rangenotvariable1", - "{{range $k, .}}{{end}}", - hasError, `range can only initialize variables`}, - {"rangenotvariable2", - "{{range $k, 123 := .}}{{end}}", - hasError, `range can only initialize variables`}, -} - -func TestErrors(t *testing.T) { - for _, test := range errorTests { - t.Run(test.name, func(t *testing.T) { - _, err := New(test.name).Parse(test.input, "", "", make(map[string]*Tree)) - if err == nil { - t.Fatalf("expected error %q, got nil", test.result) - } - if !strings.Contains(err.Error(), test.result) { - t.Fatalf("error %q does not contain %q", err, test.result) - } - }) - } -} - -func TestBlock(t *testing.T) { - const ( - input = `a{{block "inner" .}}bar{{.}}baz{{end}}b` - outer = `a{{template "inner" .}}b` - inner = `bar{{.}}baz` - ) - treeSet := make(map[string]*Tree) - tmpl, err := New("outer").Parse(input, "", "", treeSet, nil) - if err != nil { - t.Fatal(err) - } - if g, w := tmpl.Root.String(), outer; g != w { - t.Errorf("outer template = %q, want %q", g, w) - } - inTmpl := treeSet["inner"] - if inTmpl == nil { - t.Fatal("block did not define template") - } - if g, w := inTmpl.Root.String(), inner; g != w { - t.Errorf("inner template = %q, want %q", g, w) - } -} - -func TestLineNum(t *testing.T) { - const count = 100 - text := strings.Repeat("{{printf 1234}}\n", count) - tree, err := New("bench").Parse(text, "", "", make(map[string]*Tree), builtins) - if err != nil { - t.Fatal(err) - } - // Check the line numbers. Each line is an action containing a template, followed by text. - // That's two nodes per line. - nodes := tree.Root.Nodes - for i := 0; i < len(nodes); i += 2 { - line := 1 + i/2 - // Action first. - action := nodes[i].(*ActionNode) - if action.Line != line { - t.Fatalf("line %d: action is line %d", line, action.Line) - } - pipe := action.Pipe - if pipe.Line != line { - t.Fatalf("line %d: pipe is line %d", line, pipe.Line) - } - } -} - -func BenchmarkParseLarge(b *testing.B) { - text := strings.Repeat("{{1234}}\n", 10000) - for i := 0; i < b.N; i++ { - _, err := New("bench").Parse(text, "", "", make(map[string]*Tree), builtins) - if err != nil { - b.Fatal(err) - } - } -} - -var sinkv, sinkl string - -func BenchmarkVariableString(b *testing.B) { - v := &VariableNode{ - Ident: []string{"$", "A", "BB", "CCC", "THIS_IS_THE_VARIABLE_BEING_PROCESSED"}, - } - b.ResetTimer() - b.ReportAllocs() - for i := 0; i < b.N; i++ { - sinkv = v.String() - } - if sinkv == "" { - b.Fatal("Benchmark was not run") - } -} - -func BenchmarkListString(b *testing.B) { - text := ` -{{(printf .Field1.Field2.Field3).Value}} -{{$x := (printf .Field1.Field2.Field3).Value}} -{{$y := (printf $x.Field1.Field2.Field3).Value}} -{{$z := $y.Field1.Field2.Field3}} -{{if contains $y $z}} - {{printf "%q" $y}} -{{else}} - {{printf "%q" $x}} -{{end}} -{{with $z.Field1 | contains "boring"}} - {{printf "%q" . | printf "%s"}} -{{else}} - {{printf "%d %d %d" 11 11 11}} - {{printf "%d %d %s" 22 22 $x.Field1.Field2.Field3 | printf "%s"}} - {{printf "%v" (contains $z.Field1.Field2 $y)}} -{{end}} -` - tree, err := New("bench").Parse(text, "", "", make(map[string]*Tree), builtins) - if err != nil { - b.Fatal(err) - } - b.ResetTimer() - b.ReportAllocs() - for i := 0; i < b.N; i++ { - sinkl = tree.Root.String() - } - if sinkl == "" { - b.Fatal("Benchmark was not run") - } -}
diff --git a/go.dev/cmd/internal/text/template/template.go b/go.dev/cmd/internal/text/template/template.go deleted file mode 100644 index 5ba31da..0000000 --- a/go.dev/cmd/internal/text/template/template.go +++ /dev/null
@@ -1,239 +0,0 @@ -// Copyright 2011 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package template - -import ( - "reflect" - "sync" - - "golang.org/x/website/go.dev/cmd/internal/text/template/parse" -) - -// common holds the information shared by related templates. -type common struct { - tmpl map[string]*Template // Map from name to defined templates. - muTmpl sync.RWMutex // protects tmpl - option option - // We use two maps, one for parsing and one for execution. - // This separation makes the API cleaner since it doesn't - // expose reflection to the client. - muFuncs sync.RWMutex // protects parseFuncs and execFuncs - parseFuncs FuncMap - execFuncs map[string]reflect.Value -} - -// Template is the representation of a parsed template. The *parse.Tree -// field is exported only for use by html/template and should be treated -// as unexported by all other clients. -type Template struct { - name string - *parse.Tree - *common - leftDelim string - rightDelim string -} - -// New allocates a new, undefined template with the given name. -func New(name string) *Template { - t := &Template{ - name: name, - } - t.init() - return t -} - -// Name returns the name of the template. -func (t *Template) Name() string { - return t.name -} - -// New allocates a new, undefined template associated with the given one and with the same -// delimiters. The association, which is transitive, allows one template to -// invoke another with a {{template}} action. -// -// Because associated templates share underlying data, template construction -// cannot be done safely in parallel. Once the templates are constructed, they -// can be executed in parallel. -func (t *Template) New(name string) *Template { - t.init() - nt := &Template{ - name: name, - common: t.common, - leftDelim: t.leftDelim, - rightDelim: t.rightDelim, - } - return nt -} - -// init guarantees that t has a valid common structure. -func (t *Template) init() { - if t.common == nil { - c := new(common) - c.tmpl = make(map[string]*Template) - c.parseFuncs = make(FuncMap) - c.execFuncs = make(map[string]reflect.Value) - t.common = c - } -} - -// Clone returns a duplicate of the template, including all associated -// templates. The actual representation is not copied, but the name space of -// associated templates is, so further calls to Parse in the copy will add -// templates to the copy but not to the original. Clone can be used to prepare -// common templates and use them with variant definitions for other templates -// by adding the variants after the clone is made. -func (t *Template) Clone() (*Template, error) { - nt := t.copy(nil) - nt.init() - if t.common == nil { - return nt, nil - } - t.muTmpl.RLock() - defer t.muTmpl.RUnlock() - for k, v := range t.tmpl { - if k == t.name { - nt.tmpl[t.name] = nt - continue - } - // The associated templates share nt's common structure. - tmpl := v.copy(nt.common) - nt.tmpl[k] = tmpl - } - t.muFuncs.RLock() - defer t.muFuncs.RUnlock() - for k, v := range t.parseFuncs { - nt.parseFuncs[k] = v - } - for k, v := range t.execFuncs { - nt.execFuncs[k] = v - } - return nt, nil -} - -// copy returns a shallow copy of t, with common set to the argument. -func (t *Template) copy(c *common) *Template { - return &Template{ - name: t.name, - Tree: t.Tree, - common: c, - leftDelim: t.leftDelim, - rightDelim: t.rightDelim, - } -} - -// AddParseTree associates the argument parse tree with the template t, giving -// it the specified name. If the template has not been defined, this tree becomes -// its definition. If it has been defined and already has that name, the existing -// definition is replaced; otherwise a new template is created, defined, and returned. -func (t *Template) AddParseTree(name string, tree *parse.Tree) (*Template, error) { - t.muTmpl.Lock() - defer t.muTmpl.Unlock() - t.init() - nt := t - if name != t.name { - nt = t.New(name) - } - // Even if nt == t, we need to install it in the common.tmpl map. - if t.associate(nt, tree) || nt.Tree == nil { - nt.Tree = tree - } - return nt, nil -} - -// Templates returns a slice of defined templates associated with t. -func (t *Template) Templates() []*Template { - if t.common == nil { - return nil - } - // Return a slice so we don't expose the map. - t.muTmpl.RLock() - defer t.muTmpl.RUnlock() - m := make([]*Template, 0, len(t.tmpl)) - for _, v := range t.tmpl { - m = append(m, v) - } - return m -} - -// Delims sets the action delimiters to the specified strings, to be used in -// subsequent calls to Parse, ParseFiles, or ParseGlob. Nested template -// definitions will inherit the settings. An empty delimiter stands for the -// corresponding default: {{ or }}. -// The return value is the template, so calls can be chained. -func (t *Template) Delims(left, right string) *Template { - t.init() - t.leftDelim = left - t.rightDelim = right - return t -} - -// Funcs adds the elements of the argument map to the template's function map. -// It must be called before the template is parsed. -// It panics if a value in the map is not a function with appropriate return -// type or if the name cannot be used syntactically as a function in a template. -// It is legal to overwrite elements of the map. The return value is the template, -// so calls can be chained. -func (t *Template) Funcs(funcMap FuncMap) *Template { - t.init() - t.muFuncs.Lock() - defer t.muFuncs.Unlock() - addValueFuncs(t.execFuncs, funcMap) - addFuncs(t.parseFuncs, funcMap) - return t -} - -// Lookup returns the template with the given name that is associated with t. -// It returns nil if there is no such template or the template has no definition. -func (t *Template) Lookup(name string) *Template { - if t.common == nil { - return nil - } - t.muTmpl.RLock() - defer t.muTmpl.RUnlock() - return t.tmpl[name] -} - -// Parse parses text as a template body for t. -// Named template definitions ({{define ...}} or {{block ...}} statements) in text -// define additional templates associated with t and are removed from the -// definition of t itself. -// -// Templates can be redefined in successive calls to Parse. -// A template definition with a body containing only white space and comments -// is considered empty and will not replace an existing template's body. -// This allows using Parse to add new named template definitions without -// overwriting the main template body. -func (t *Template) Parse(text string) (*Template, error) { - t.init() - t.muFuncs.RLock() - trees, err := parse.Parse(t.name, text, t.leftDelim, t.rightDelim, t.parseFuncs, builtins()) - t.muFuncs.RUnlock() - if err != nil { - return nil, err - } - // Add the newly parsed trees, including the one for t, into our common structure. - for name, tree := range trees { - if _, err := t.AddParseTree(name, tree); err != nil { - return nil, err - } - } - return t, nil -} - -// associate installs the new template into the group of templates associated -// with t. The two are already known to share the common structure. -// The boolean return value reports whether to store this tree as t.Tree. -func (t *Template) associate(new *Template, tree *parse.Tree) bool { - if new.common != t.common { - panic("internal error: associate not common") - } - if old := t.tmpl[new.name]; old != nil && parse.IsEmptyTree(tree.Root) && old.Tree != nil { - // If a template by that name exists, - // don't replace it with an empty template. - return false - } - t.tmpl[new.name] = new - return true -}
diff --git a/go.dev/cmd/internal/text/template/testdata/file1.tmpl b/go.dev/cmd/internal/text/template/testdata/file1.tmpl deleted file mode 100644 index febf9d9..0000000 --- a/go.dev/cmd/internal/text/template/testdata/file1.tmpl +++ /dev/null
@@ -1,2 +0,0 @@ -{{define "x"}}TEXT{{end}} -{{define "dotV"}}{{.V}}{{end}}
diff --git a/go.dev/cmd/internal/text/template/testdata/file2.tmpl b/go.dev/cmd/internal/text/template/testdata/file2.tmpl deleted file mode 100644 index 39bf6fb..0000000 --- a/go.dev/cmd/internal/text/template/testdata/file2.tmpl +++ /dev/null
@@ -1,2 +0,0 @@ -{{define "dot"}}{{.}}{{end}} -{{define "nested"}}{{template "dot" .}}{{end}}
diff --git a/go.dev/cmd/internal/text/template/testdata/tmpl1.tmpl b/go.dev/cmd/internal/text/template/testdata/tmpl1.tmpl deleted file mode 100644 index b72b3a3..0000000 --- a/go.dev/cmd/internal/text/template/testdata/tmpl1.tmpl +++ /dev/null
@@ -1,3 +0,0 @@ -template1 -{{define "x"}}x{{end}} -{{template "y"}}
diff --git a/go.dev/cmd/internal/text/template/testdata/tmpl2.tmpl b/go.dev/cmd/internal/text/template/testdata/tmpl2.tmpl deleted file mode 100644 index 16beba6..0000000 --- a/go.dev/cmd/internal/text/template/testdata/tmpl2.tmpl +++ /dev/null
@@ -1,3 +0,0 @@ -template2 -{{define "y"}}y{{end}} -{{template "x"}}
diff --git a/go.dev/cmd/internal/tmplfunc/func.go b/go.dev/cmd/internal/tmplfunc/func.go index 9ea117b..7d20d9b 100644 --- a/go.dev/cmd/internal/tmplfunc/func.go +++ b/go.dev/cmd/internal/tmplfunc/func.go
@@ -11,10 +11,10 @@ "regexp" "strings" - "golang.org/x/website/go.dev/cmd/internal/text/template/parse" + "golang.org/x/website/internal/backport/text/template/parse" - htmltemplate "golang.org/x/website/go.dev/cmd/internal/html/template" - texttemplate "golang.org/x/website/go.dev/cmd/internal/text/template" + htmltemplate "golang.org/x/website/internal/backport/html/template" + texttemplate "golang.org/x/website/internal/backport/text/template" ) var validNameRE = regexp.MustCompile(`\A[_\pL][_\pL\p{Nd}]*\z`)
diff --git a/go.dev/cmd/internal/tmplfunc/tmpl.go b/go.dev/cmd/internal/tmplfunc/tmpl.go index 8932685..1f115f5 100644 --- a/go.dev/cmd/internal/tmplfunc/tmpl.go +++ b/go.dev/cmd/internal/tmplfunc/tmpl.go
@@ -84,8 +84,8 @@ "io/ioutil" "path/filepath" - htmltemplate "golang.org/x/website/go.dev/cmd/internal/html/template" - texttemplate "golang.org/x/website/go.dev/cmd/internal/text/template" + htmltemplate "golang.org/x/website/internal/backport/html/template" + texttemplate "golang.org/x/website/internal/backport/text/template" ) // A Template is a *template.Template, where template refers to either
diff --git a/go.dev/cmd/internal/tmplfunc/tmplfunc_test.go b/go.dev/cmd/internal/tmplfunc/tmplfunc_test.go index ee555ad..baa6fd9 100644 --- a/go.dev/cmd/internal/tmplfunc/tmplfunc_test.go +++ b/go.dev/cmd/internal/tmplfunc/tmplfunc_test.go
@@ -10,8 +10,8 @@ "strings" "testing" - htmltemplate "golang.org/x/website/go.dev/cmd/internal/html/template" - texttemplate "golang.org/x/website/go.dev/cmd/internal/text/template" + htmltemplate "golang.org/x/website/internal/backport/html/template" + texttemplate "golang.org/x/website/internal/backport/text/template" ) var tests = []struct {
diff --git a/go.dev/go.mod b/go.dev/go.mod deleted file mode 100644 index 37706b9..0000000 --- a/go.dev/go.mod +++ /dev/null
@@ -1,13 +0,0 @@ -module golang.org/x/website/go.dev - -go 1.13 - -require ( - github.com/google/go-cmp v0.3.1 - github.com/microcosm-cc/bluemonday v1.0.2 - github.com/yuin/goldmark v1.2.1 - google.golang.org/api v0.13.0 - gopkg.in/yaml.v2 v2.2.2 - gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b - rsc.io/rf v0.0.0-20210401221041-ba8df2a1fd6c -)
diff --git a/go.dev/go.sum b/go.dev/go.sum deleted file mode 100644 index e2f0231..0000000 --- a/go.dev/go.sum +++ /dev/null
@@ -1,113 +0,0 @@ -cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -cloud.google.com/go v0.38.0 h1:ROfEUZz+Gh5pa62DJWXSaonyu3StP6EA6lPEXPI6mCo= -cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= -github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= -github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58= -github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= -github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= -github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= -github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.1 h1:YF8+flBXS5eO826T4nzqPrxfhQThhXl0YzfuUPu4SBg= -github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= -github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= -github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= -github.com/google/go-cmp v0.3.1 h1:Xye71clBPdm5HgqGwUkwhbynsUJZhDbS20FvLhQ2izg= -github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= -github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= -github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= -github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= -github.com/googleapis/gax-go/v2 v2.0.5 h1:sjZBwGj9Jlw33ImPtvFviGYvseOtDM7hkSKB7+Tv3SM= -github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= -github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= -github.com/hashicorp/golang-lru v0.5.1 h1:0hERBMJE1eitiLkihrMvRVBYAkpHzc/J3QdDN+dAcgU= -github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= -github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= -github.com/microcosm-cc/bluemonday v1.0.2 h1:5lPfLTTAvAbtS0VqT+94yOtFnGfUWYyx0+iToC3Os3s= -github.com/microcosm-cc/bluemonday v1.0.2/go.mod h1:iVP4YcDBq+n/5fb23BhYFvIMq/leAFZyRl6bYmGDlGc= -github.com/yuin/goldmark v1.2.1 h1:ruQGxdhGHe7FWOJPT0mKs5+pD2Xs1Bm/kdGlHO04FmM= -github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -go.opencensus.io v0.21.0 h1:mU6zScU4U1YAFPHEHYk+3JC4SY7JxgkqS10ZOSyksNg= -go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= -golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= -golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= -golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= -golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20201021035429-f5854403a974 h1:IX6qOQeG5uLjB/hjjwjedwfjND0hgjPMMyO1RoIXQNI= -golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= -golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45 h1:SVwTIAaPC2U/AvvLNZ2a7OVsmBpC8L5BlwK1whH3hm0= -golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f h1:+Nyd8tzPX9R7BWHguqsrbFdRx3WQ/1ib8I44HXV5yTA= -golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= -golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k= -golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= -golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20201111224557-41a3a589386c/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= -google.golang.org/api v0.13.0 h1:Q3Ui3V3/CVinFWFiW39Iw0kMuVrRzYX0wN6OPFp0lTA= -google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= -google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= -google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= -google.golang.org/appengine v1.5.0 h1:KxkO13IPW4Lslp2bz+KHP2E3gtFlrIGNThxkZQ3g+4c= -google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= -google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= -google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873 h1:nfPFGzJkUDX6uBmpN/pSw7MbOAWegH5QDQuoXFHedLg= -google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= -google.golang.org/grpc v1.20.1 h1:Hz2g2wirWK7H0qIIhGIqRGTuMwTE8HEKFnDZZ7lm9NU= -google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= -gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo= -gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -rsc.io/rf v0.0.0-20210401221041-ba8df2a1fd6c h1:lZl/oVdEEh5FuRJIt6ms8oLEkycLohVgloElXrnx+uw= -rsc.io/rf v0.0.0-20210401221041-ba8df2a1fd6c/go.mod h1:4rdFt/SlKutY8W9onF7XZvD3H0hD+Xpz/uodEcQLuM4=
diff --git a/go.mod b/go.mod index b22ee88..183e016 100644 --- a/go.mod +++ b/go.mod
@@ -6,10 +6,15 @@ cloud.google.com/go v0.58.0 // indirect cloud.google.com/go/datastore v1.2.0 github.com/gomodule/redigo v2.0.0+incompatible + github.com/google/go-cmp v0.5.5 + github.com/microcosm-cc/bluemonday v1.0.2 github.com/yuin/goldmark v1.2.1 golang.org/x/build v0.0.0-20210422214718-6469a76194d9 golang.org/x/net v0.0.0-20201110031124-69a78807bb2b golang.org/x/tools v0.1.1-0.20210215123931-123adc86bcb6 - google.golang.org/api v0.27.0 // indirect + google.golang.org/api v0.27.0 google.golang.org/genproto v0.0.0-20200617032506-f1bdc9086088 // indirect + gopkg.in/yaml.v2 v2.3.0 + gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b + rsc.io/rf v0.0.0-20210401221041-ba8df2a1fd6c )
diff --git a/go.sum b/go.sum index 6e68972..3fc15ca 100644 --- a/go.sum +++ b/go.sum
@@ -237,9 +237,11 @@ github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= +github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/pty v1.1.3/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20190605223551-bc2310a04743/go.mod h1:qklhhLq1aX+mtWk9cPHPzaBjWImj5ULL6C7HFJtXQMM= github.com/lightstep/lightstep-tracer-go v0.18.1/go.mod h1:jlF1pusYV4pidLvZ+XD0UBX0ZE6WURAspgAczcDHrL4= @@ -249,6 +251,8 @@ github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= +github.com/microcosm-cc/bluemonday v1.0.2 h1:5lPfLTTAvAbtS0VqT+94yOtFnGfUWYyx0+iToC3Os3s= +github.com/microcosm-cc/bluemonday v1.0.2/go.mod h1:iVP4YcDBq+n/5fb23BhYFvIMq/leAFZyRl6bYmGDlGc= github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= @@ -570,6 +574,7 @@ golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200606014950-c42cb6316fb6/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200612220849-54c614fe050c/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20201111224557-41a3a589386c/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.1-0.20210215123931-123adc86bcb6 h1:GZ5npTh1qUGmauzey32dt41k2308lqvKwwsxdvSQD9g= golang.org/x/tools v0.1.1-0.20210215123931-123adc86bcb6/go.mod h1:9bzcO0MWcOuT0tm1iBGzDVPshzfwoVvREIui8C+MHqU= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -665,6 +670,7 @@ gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/cheggaaa/pb.v1 v1.0.25/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= @@ -679,8 +685,11 @@ gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo= +gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= grpc.go4.org v0.0.0-20170609214715-11d0a25b4919/go.mod h1:77eQGdRu53HpSqPFJFmuJdjuHRquDANNeA4x7B8WQ9o= honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= @@ -693,6 +702,8 @@ honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= +rsc.io/rf v0.0.0-20210401221041-ba8df2a1fd6c h1:lZl/oVdEEh5FuRJIt6ms8oLEkycLohVgloElXrnx+uw= +rsc.io/rf v0.0.0-20210401221041-ba8df2a1fd6c/go.mod h1:4rdFt/SlKutY8W9onF7XZvD3H0hD+Xpz/uodEcQLuM4= rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o= sourcegraph.com/sourcegraph/appdash v0.0.0-20190731080439-ebfcffb1b5c0/go.mod h1:hI742Nqp5OhwiqlzhgfbWU4mW4yO10fP+LoT9WOswdU=