diff options
48 files changed, 697 insertions, 223 deletions
diff --git a/scripts/fork_go_templates/main.go b/scripts/fork_go_templates/main.go index f550efbbb..4ffba018f 100644 --- a/scripts/fork_go_templates/main.go +++ b/scripts/fork_go_templates/main.go @@ -17,8 +17,7 @@ import ( ) func main() { - // TODO(bep) git checkout tag - // The current is built with Go version 2f0da6d9e29d9b9d5a4d10427ca9f71d12bbacc8 / go1.16 + // The current is built with Go tag go1.18 4aa1efed4853ea067d665a952eee77c52faac774 fmt.Println("Forking ...") defer fmt.Println("Done ...") @@ -40,7 +39,7 @@ func main() { const ( // TODO(bep) - goSource = "/Users/bep/dev/go/dump/go/src" + goSource = "/Users/bep/dev/go/misc/go/src" forkRoot = "../../tpl/internal/go_templates" ) diff --git a/tpl/internal/go_templates/cfg/cfg.go b/tpl/internal/go_templates/cfg/cfg.go index 553021374..78664d7a9 100644 --- a/tpl/internal/go_templates/cfg/cfg.go +++ b/tpl/internal/go_templates/cfg/cfg.go @@ -33,12 +33,14 @@ const KnownEnv = ` GCCGO GO111MODULE GO386 + GOAMD64 GOARCH GOARM GOBIN GOCACHE GOENV GOEXE + GOEXPERIMENT GOFLAGS GOGCCFLAGS GOHOSTARCH @@ -60,6 +62,7 @@ const KnownEnv = ` GOTOOLDIR GOVCS GOWASM + GOWORK GO_EXTLINK_ENABLED PKG_CONFIG ` diff --git a/tpl/internal/go_templates/fmtsort/sort.go b/tpl/internal/go_templates/fmtsort/sort.go index 7127ba6ac..34c1f477f 100644 --- a/tpl/internal/go_templates/fmtsort/sort.go +++ b/tpl/internal/go_templates/fmtsort/sort.go @@ -130,7 +130,7 @@ func compare(aVal, bVal reflect.Value) int { default: return -1 } - case reflect.Ptr, reflect.UnsafePointer: + case reflect.Pointer, reflect.UnsafePointer: a, b := aVal.Pointer(), bVal.Pointer() switch { case a < b: diff --git a/tpl/internal/go_templates/fmtsort/sort_test.go b/tpl/internal/go_templates/fmtsort/sort_test.go index 5205a6413..a05e8a3c3 100644 --- a/tpl/internal/go_templates/fmtsort/sort_test.go +++ b/tpl/internal/go_templates/fmtsort/sort_test.go @@ -9,6 +9,7 @@ import ( "github.com/gohugoio/hugo/tpl/internal/go_templates/fmtsort" "math" "reflect" + "sort" "strings" "testing" "unsafe" @@ -37,12 +38,12 @@ var compareTests = [][]reflect.Value{ 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), + ct(reflect.TypeOf(any(any(0))), iFace, 1, 2, 3), } -var iFace interface{} +var iFace any -func ct(typ reflect.Type, args ...interface{}) []reflect.Value { +func ct(typ reflect.Type, args ...any) []reflect.Value { value := make([]reflect.Value, len(args)) for i, v := range args { x := reflect.ValueOf(v) @@ -83,8 +84,8 @@ func TestCompare(t *testing.T) { } type sortTest struct { - data interface{} // Always a map. - print string // Printed result using our custom printer. + data any // Always a map. + print string // Printed result using our custom printer. } var sortTests = []sortTest{ @@ -134,7 +135,7 @@ var sortTests = []sortTest{ }, } -func sprint(data interface{}) string { +func sprint(data any) string { om := fmtsort.Sort(reflect.ValueOf(data)) if om == nil { return "nil" @@ -188,9 +189,19 @@ func sprintKey(key reflect.Value) string { var ( ints [3]int - chans = [3]chan int{make(chan int), make(chan int), make(chan int)} + chans = makeChans() ) +func makeChans() []chan int { + cs := []chan int{make(chan int), make(chan int), make(chan int)} + // Order channels by address. See issue #49431. + // TODO: pin these pointers once pinning is available (#46787). + sort.Slice(cs, func(i, j int) bool { + return uintptr(reflect.ValueOf(cs[i]).UnsafePointer()) < uintptr(reflect.ValueOf(cs[j]).UnsafePointer()) + }) + return cs +} + func pointerMap() map[*int]string { m := make(map[*int]string) for i := 2; i >= 0; i-- { @@ -233,7 +244,7 @@ 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{ + m := map[any]string{ [2]int{1, 0}: "", [2]int{0, 1}: "", true: "", diff --git a/tpl/internal/go_templates/htmltemplate/attr.go b/tpl/internal/go_templates/htmltemplate/attr.go index 22922e603..6c52211fe 100644 --- a/tpl/internal/go_templates/htmltemplate/attr.go +++ b/tpl/internal/go_templates/htmltemplate/attr.go @@ -143,12 +143,12 @@ func attrType(name string) contentType { // widely applied. // Treat data-action as URL below. name = name[5:] - } else if colon := strings.IndexRune(name, ':'); colon != -1 { - if name[:colon] == "xmlns" { + } else if prefix, short, ok := strings.Cut(name, ":"); ok { + if prefix == "xmlns" { return contentTypeURL } // Treat svg:href and xlink:href as href below. - name = name[colon+1:] + name = short } if t, ok := attrTypeMap[name]; ok { return t diff --git a/tpl/internal/go_templates/htmltemplate/clone_test.go b/tpl/internal/go_templates/htmltemplate/clone_test.go index 7c774e923..553f656b5 100644 --- a/tpl/internal/go_templates/htmltemplate/clone_test.go +++ b/tpl/internal/go_templates/htmltemplate/clone_test.go @@ -2,6 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. +//go:build go1.13 && !windows // +build go1.13,!windows package template diff --git a/tpl/internal/go_templates/htmltemplate/content.go b/tpl/internal/go_templates/htmltemplate/content.go index bc32dc813..65cc3086c 100644 --- a/tpl/internal/go_templates/htmltemplate/content.go +++ b/tpl/internal/go_templates/htmltemplate/content.go @@ -29,16 +29,16 @@ const ( // indirect returns the value, after dereferencing as many times // as necessary to reach the base type (or nil). -func indirect(a interface{}) interface{} { +func indirect(a any) any { if a == nil { return nil } - if t := reflect.TypeOf(a); t.Kind() != reflect.Ptr { + if t := reflect.TypeOf(a); t.Kind() != reflect.Pointer { // Avoid creating a reflect.Value if it's not a pointer. return a } v := reflect.ValueOf(a) - for v.Kind() == reflect.Ptr && !v.IsNil() { + for v.Kind() == reflect.Pointer && !v.IsNil() { v = v.Elem() } return v.Interface() @@ -52,12 +52,12 @@ var ( // 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{} { +func indirectToStringerOrError(a any) any { if a == nil { return nil } v := reflect.ValueOf(a) - for !v.Type().Implements(fmtStringerType) && !v.Type().Implements(errorType) && v.Kind() == reflect.Ptr && !v.IsNil() { + for !v.Type().Implements(fmtStringerType) && !v.Type().Implements(errorType) && v.Kind() == reflect.Pointer && !v.IsNil() { v = v.Elem() } return v.Interface() @@ -65,7 +65,7 @@ func indirectToStringerOrError(a interface{}) 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) { +func stringify(args ...any) (string, contentType) { if len(args) == 1 { switch s := indirect(args[0]).(type) { case string: diff --git a/tpl/internal/go_templates/htmltemplate/content_test.go b/tpl/internal/go_templates/htmltemplate/content_test.go index 909a24bc0..29221a4ad 100644 --- a/tpl/internal/go_templates/htmltemplate/content_test.go +++ b/tpl/internal/go_templates/htmltemplate/content_test.go @@ -2,6 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. +//go:build go1.13 && !windows // +build go1.13,!windows package template @@ -15,7 +16,7 @@ import ( ) func TestTypedContent(t *testing.T) { - data := []interface{}{ + data := []any{ `<b> "foo%" O'Reilly &bar;`, htmltemplate.CSS(`a[href =~ "//example.com"]#foo`), htmltemplate.HTML(`Hello, <b>World</b> &tc!`), @@ -452,7 +453,7 @@ func TestEscapingNilNonemptyInterfaces(t *testing.T) { // A non-empty interface should print like an empty interface. want := new(bytes.Buffer) - data := struct{ E interface{} }{} + data := struct{ E any }{} tmpl.Execute(want, data) if !bytes.Equal(want.Bytes(), got.Bytes()) { diff --git a/tpl/internal/go_templates/htmltemplate/context.go b/tpl/internal/go_templates/htmltemplate/context.go index 006b870ec..c28e08dce 100644 --- a/tpl/internal/go_templates/htmltemplate/context.go +++ b/tpl/internal/go_templates/htmltemplate/context.go @@ -4,7 +4,11 @@ package template -import "fmt" +import ( + "fmt" + + "github.com/gohugoio/hugo/tpl/internal/go_templates/texttemplate/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. @@ -20,6 +24,7 @@ type context struct { jsCtx jsCtx attr attr element element + n parse.Node // for range break/continue err *Error } @@ -139,6 +144,8 @@ const ( // 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 diff --git a/tpl/internal/go_templates/htmltemplate/css.go b/tpl/internal/go_templates/htmltemplate/css.go index eb92fc92b..890a0c6b2 100644 --- a/tpl/internal/go_templates/htmltemplate/css.go +++ b/tpl/internal/go_templates/htmltemplate/css.go @@ -155,7 +155,7 @@ func isCSSSpace(b byte) bool { } // cssEscaper escapes HTML and CSS special characters using \<hex>+ escapes. -func cssEscaper(args ...interface{}) string { +func cssEscaper(args ...any) string { s, _ := stringify(args...) var b strings.Builder r, w, written := rune(0), 0, 0 @@ -218,7 +218,7 @@ var mozBindingBytes = []byte("mozbinding") // (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 { +func cssValueFilter(args ...any) string { s, t := stringify(args...) if t == contentTypeCSS { return s diff --git a/tpl/internal/go_templates/htmltemplate/css_test.go b/tpl/internal/go_templates/htmltemplate/css_test.go index afed58c29..7d8ad8b59 100644 --- a/tpl/internal/go_templates/htmltemplate/css_test.go +++ b/tpl/internal/go_templates/htmltemplate/css_test.go @@ -2,6 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. +//go:build go1.13 && !windows // +build go1.13,!windows package template diff --git a/tpl/internal/go_templates/htmltemplate/error.go b/tpl/internal/go_templates/htmltemplate/error.go index f8b3f1cea..21c86a9ef 100644 --- a/tpl/internal/go_templates/htmltemplate/error.go +++ b/tpl/internal/go_templates/htmltemplate/error.go @@ -229,6 +229,6 @@ func (e *Error) Error() string { // 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 { +func errorf(k ErrorCode, node parse.Node, line int, f string, args ...any) *Error { return &Error{k, node, "", line, fmt.Sprintf(f, args...)} } diff --git a/tpl/internal/go_templates/htmltemplate/escape.go b/tpl/internal/go_templates/htmltemplate/escape.go index 5f4c92a17..5382c4288 100644 --- a/tpl/internal/go_templates/htmltemplate/escape.go +++ b/tpl/internal/go_templates/htmltemplate/escape.go @@ -46,7 +46,7 @@ func escapeTemplate(tmpl *Template, node parse.Node, name string) error { // 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 { +func evalArgs(args ...any) string { // Optimization for simple common case of a single string argument. if len(args) == 1 { if s, ok := args[0].(string); ok { @@ -98,6 +98,15 @@ type escaper struct { 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. @@ -110,6 +119,7 @@ func makeEscaper(n *nameSpace) escaper { map[*parse.ActionNode][]string{}, map[*parse.TemplateNode]string{}, map[*parse.TextNode][]byte{}, + nil, } } @@ -125,8 +135,16 @@ 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: @@ -428,6 +446,12 @@ func join(a, b context, node parse.Node, nodeName string) context { if b.state == stateError { return b } + if a.state == stateDead { + return b + } + if b.state == stateDead { + return a + } if a.eq(b) { return a } @@ -467,14 +491,27 @@ func join(a, b context, node parse.Node, nodeName string) context { // 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" && c0.state != stateError { + 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} |