]> git.maquefel.me Git - brevno-suite/hugo/commitdiff
Upgrade to Go 1.23
authorBjørn Erik Pedersen <bjorn.erik.pedersen@gmail.com>
Wed, 14 Aug 2024 09:34:21 +0000 (11:34 +0200)
committerBjørn Erik Pedersen <bjorn.erik.pedersen@gmail.com>
Thu, 15 Aug 2024 08:18:19 +0000 (10:18 +0200)
Fixes #12763

34 files changed:
.circleci/config.yml
.github/workflows/test.yml
go.mod
hugofs/fs.go
modules/client.go
scripts/fork_go_templates/main.go
testscripts/commands/mod.txt
testscripts/commands/mod_npm.txt
testscripts/commands/mod_npm_withexisting.txt
tpl/internal/go_templates/cfg/cfg.go
tpl/internal/go_templates/fmtsort/sort.go
tpl/internal/go_templates/fmtsort/sort_test.go
tpl/internal/go_templates/htmltemplate/content.go
tpl/internal/go_templates/htmltemplate/doc.go
tpl/internal/go_templates/htmltemplate/examplefiles_test.go
tpl/internal/go_templates/htmltemplate/exec_test.go
tpl/internal/go_templates/htmltemplate/js.go
tpl/internal/go_templates/htmltemplate/template.go
tpl/internal/go_templates/htmltemplate/transition.go
tpl/internal/go_templates/testenv/testenv.go
tpl/internal/go_templates/texttemplate/doc.go
tpl/internal/go_templates/texttemplate/example_test.go
tpl/internal/go_templates/texttemplate/examplefiles_test.go
tpl/internal/go_templates/texttemplate/exec.go
tpl/internal/go_templates/texttemplate/exec_test.go
tpl/internal/go_templates/texttemplate/funcs.go
tpl/internal/go_templates/texttemplate/helper.go
tpl/internal/go_templates/texttemplate/hugo_template.go
tpl/internal/go_templates/texttemplate/link_test.go
tpl/internal/go_templates/texttemplate/parse/node.go
tpl/internal/go_templates/texttemplate/parse/parse.go
tpl/internal/go_templates/texttemplate/parse/parse_test.go
tpl/internal/go_templates/texttemplate/template.go
tpl/tplimpl/tplimpl_integration_test.go

index beee3b5366d79aeff78743c3e175a32bb66ce728..0ff9559365e44e8c2f76e103db866f588b147320 100644 (file)
@@ -4,7 +4,7 @@ parameters:
 defaults: &defaults
   resource_class: large
   docker:
-      - image: bepsays/ci-hugoreleaser:1.22200.20501
+      - image: bepsays/ci-hugoreleaser:1.22300.20000
 environment: &buildenv
       GOMODCACHE: /root/project/gomodcache
 version: 2
@@ -60,7 +60,7 @@ jobs:
     environment:
         <<: [*buildenv]
     docker:
-    - image: bepsays/ci-hugoreleaser-linux-arm64:1.22200.20501
+    - image: bepsays/ci-hugoreleaser-linux-arm64:1.22300.20000
     steps:
       - *restore-cache
       - &attach-workspace
index b5491090e6618efd8506adc2d3cfd2cde3628289..1202620986c0772f90d7ef284dc3935cb1fd4f5d 100644 (file)
@@ -16,7 +16,7 @@ jobs:
   test:
     strategy:
       matrix:
-        go-version: [1.21.x, 1.22.x]
+        go-version: [1.22.x, 1.23.x]
         os: [ubuntu-latest, macos-latest, windows-latest]
     runs-on: ${{ matrix.os }}
     steps:
diff --git a/go.mod b/go.mod
index f9776279e42a9543d965a70cb41378c336e15e0a..40f90c45f0e0b9c095ea69c2480feeec4914d495 100644 (file)
--- a/go.mod
+++ b/go.mod
@@ -170,4 +170,4 @@ require (
        software.sslmate.com/src/go-pkcs12 v0.2.0 // indirect
 )
 
-go 1.21.8
+go 1.22.6
index fc0ea71c6668b365715007a1019f4b07c54ef709..fab0d388656265faf2a27d52b53242aec5ff98b9 100644 (file)
@@ -147,7 +147,8 @@ func isWrite(flag int) bool {
 // TODO(bep) move this to a more suitable place.
 func MakeReadableAndRemoveAllModulePkgDir(fs afero.Fs, dir string) (int, error) {
        // Safe guard
-       if !strings.Contains(dir, "pkg") {
+       // Note that the base directory changed from pkg to gomod_cache in Go 1.23.
+       if !strings.Contains(dir, "pkg") && !strings.Contains(dir, "gomod") {
                panic(fmt.Sprint("invalid dir:", dir))
        }
 
index a6caec23c90ae758bdbe012346e362170290b352..dce40d2db9b35c0b7f457caf1250ea00ae394ef1 100644 (file)
@@ -365,18 +365,6 @@ func (c *Client) Get(args ...string) error {
 }
 
 func (c *Client) get(args ...string) error {
-       var hasD bool
-       for _, arg := range args {
-               if arg == "-d" {
-                       hasD = true
-                       break
-               }
-       }
-       if !hasD {
-               // go get without the -d flag does not make sense to us, as
-               // it will try to build and install go packages.
-               args = append([]string{"-d"}, args...)
-       }
        if err := c.runGo(context.Background(), c.logger.Out(), append([]string{"get"}, args...)...); err != nil {
                return fmt.Errorf("failed to get %q: %w", args, err)
        }
index 476fe480be69d06f3356c23303b8713a85ba5651..c4dad3224051018b50ed2f38d43f4aecca7a336e 100644 (file)
@@ -16,7 +16,7 @@ import (
 )
 
 func main() {
-       // The current is built with 8e1fdea8316d840fd07e9d6e026048e53290948b go1.22.5
+       // The current is built with  6885bad7dd86880be6929c02085e5c7a67ff2887 go1.23.0
        // TODO(bep) preserve the staticcheck.conf file.
        fmt.Println("Forking ...")
        defer fmt.Println("Done ...")
index 56cea2c001a81ec504cca54b7f78d7ab201bd0df..2fa17dbbe5e15d1bf52dbfc4fc086ab3c7863bc5 100644 (file)
@@ -18,7 +18,8 @@ hugo mod clean
 ! stderr .
 stdout 'hugo: removed 1 dirs in module cache for \"github.com/bep/empty-hugo-module\"'
 hugo mod clean --all
-stdout 'Deleted 2\d{2} files from module cache\.'
+# Currently this is 299 on MacOS and 301 on Linux.
+stdout 'Deleted (2|3)\d{2} files from module cache\.'
 cd submod
 hugo mod init testsubmod
 cmpenv go.mod $WORK/golden/go.mod.testsubmod
index 32cc37f06ad72bdafa5bc5310b7d27160e0c7e7a..3d8903e6a351d479951ed20874cd097bb7d3b711 100644 (file)
@@ -2,6 +2,7 @@
 
 dostounix golden/package.json
 
+
 hugo mod npm pack
 cmp package.json golden/package.json
 
@@ -41,3 +42,4 @@ path="github.com/gohugoio/hugoTestModule2"
 }
 -- go.mod --
 module github.com/gohugoio/hugoTestModule
+go 1.20
index e92eba3fd9d53c45c669a9ddf95e355f3c07abc7..073af0f077f63367406bf207e574958d8f4d7e57 100644 (file)
@@ -55,3 +55,4 @@ path="github.com/gohugoio/hugoTestModule2"
 }
 -- go.mod --
 module github.com/gohugoio/hugoTestModule
+go 1.20
index 2af0ec707868947bda6c966a3156625241db12c7..08d210b797385b26ef4a652ec8b17bdf99768b3e 100644 (file)
@@ -36,6 +36,7 @@ const KnownEnv = `
        GOAMD64
        GOARCH
        GOARM
+       GOARM64
        GOBIN
        GOCACHE
        GOCACHEPROG
@@ -57,6 +58,7 @@ const KnownEnv = `
        GOPPC64
        GOPRIVATE
        GOPROXY
+       GORISCV64
        GOROOT
        GOSUMDB
        GOTMPDIR
index 278a89bd75d4230e14e935aa9949df0a91bd6d3d..f51cdc7083a4b106bf6b9a16741c0a482e45b02e 100644 (file)
@@ -9,25 +9,23 @@
 package fmtsort
 
 import (
+       "cmp"
        "reflect"
-       "sort"
+       "slices"
 )
 
 // 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
-}
+// SortedMap is a slice of KeyValue pairs that simplifies sorting
+// and iterating over map entries.
+//
+// Each KeyValue pair contains a map key and its corresponding value.
+type SortedMap []KeyValue
 
-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]
+// KeyValue holds a single key and value pair found in a map.
+type KeyValue struct {
+       Key, Value reflect.Value
 }
 
 // Sort accepts a map and returns a SortedMap that has the same keys and
@@ -48,7 +46,7 @@ func (o *SortedMap) Swap(i, j int) {
 //     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 {
+func Sort(mapValue reflect.Value) SortedMap {
        if mapValue.Type().Kind() != reflect.Map {
                return nil
        }
@@ -56,18 +54,14 @@ func Sort(mapValue reflect.Value) *SortedMap {
        // 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)
+       sorted := make(SortedMap, 0, n)
        iter := mapValue.MapRange()
        for iter.Next() {
-               key = append(key, iter.Key())
-               value = append(value, iter.Value())
-       }
-       sorted := &SortedMap{
-               Key:   key,
-               Value: value,
+               sorted = append(sorted, KeyValue{iter.Key(), iter.Value()})
        }
-       sort.Stable(sorted)
+       slices.SortStableFunc(sorted, func(a, b KeyValue) int {
+               return compare(a.Key, b.Key)
+       })
        return sorted
 }
 
@@ -82,43 +76,19 @@ func compare(aVal, bVal reflect.Value) int {
        }
        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
-               }
+               return cmp.Compare(aVal.Int(), bVal.Int())
        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
-               }
+               return cmp.Compare(aVal.Uint(), bVal.Uint())
        case reflect.String:
-               a, b := aVal.String(), bVal.String()
-               switch {
-               case a < b:
-                       return -1
-               case a > b:
-                       return 1
-               default:
-                       return 0
-               }
+               return cmp.Compare(aVal.String(), bVal.String())
        case reflect.Float32, reflect.Float64:
-               return floatCompare(aVal.Float(), bVal.Float())
+               return cmp.Compare(aVal.Float(), bVal.Float())
        case reflect.Complex64, reflect.Complex128:
                a, b := aVal.Complex(), bVal.Complex()
-               if c := floatCompare(real(a), real(b)); c != 0 {
+               if c := cmp.Compare(real(a), real(b)); c != 0 {
                        return c
                }
-               return floatCompare(imag(a), imag(b))
+               return cmp.Compare(imag(a), imag(b))
        case reflect.Bool:
                a, b := aVal.Bool(), bVal.Bool()
                switch {
@@ -130,28 +100,12 @@ func compare(aVal, bVal reflect.Value) int {
                        return -1
                }
        case reflect.Pointer, reflect.UnsafePointer:
-               a, b := aVal.Pointer(), bVal.Pointer()
-               switch {
-               case a < b:
-                       return -1
-               case a > b:
-                       return 1
-               default:
-                       return 0
-               }
+               return cmp.Compare(aVal.Pointer(), bVal.Pointer())
        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
-               }
+               return cmp.Compare(aVal.Pointer(), bVal.Pointer())
        case reflect.Struct:
                for i := 0; i < aVal.NumField(); i++ {
                        if c := compare(aVal.Field(i), bVal.Field(i)); c != 0 {
@@ -198,22 +152,3 @@ func nilCompare(aVal, bVal reflect.Value) (int, bool) {
        }
        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
-}
index e86b4c673d47ed7b70ab2b9233909d4f88e11eb6..0986dbb6df9d240fa2df211fbe52add19fa7b19d 100644 (file)
@@ -5,12 +5,13 @@
 package fmtsort_test
 
 import (
+       "cmp"
        "fmt"
        "github.com/gohugoio/hugo/tpl/internal/go_templates/fmtsort"
        "math"
        "reflect"
        "runtime"
-       "sort"
+       "slices"
        "strings"
        "testing"
        "unsafe"
@@ -67,10 +68,6 @@ func TestCompare(t *testing.T) {
                                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:
@@ -142,13 +139,13 @@ func sprint(data any) string {
                return "nil"
        }
        b := new(strings.Builder)
-       for i, key := range om.Key {
+       for i, m := range om {
                if i > 0 {
                        b.WriteRune(' ')
                }
-               b.WriteString(sprintKey(key))
+               b.WriteString(sprintKey(m.Key))
                b.WriteRune(':')
-               fmt.Fprint(b, om.Value[i])
+               fmt.Fprint(b, m.Value)
        }
        return b.String()
 }
@@ -200,8 +197,8 @@ func makeChans() []chan int {
        for i := range cs {
                pin.Pin(reflect.ValueOf(cs[i]).UnsafePointer())
        }
-       sort.Slice(cs, func(i, j int) bool {
-               return uintptr(reflect.ValueOf(cs[i]).UnsafePointer()) < uintptr(reflect.ValueOf(cs[j]).UnsafePointer())
+       slices.SortFunc(cs, func(a, b chan int) int {
+               return cmp.Compare(reflect.ValueOf(a).Pointer(), reflect.ValueOf(b).Pointer())
        })
        return cs
 }
index 9c61cfac0a3727399ae8d317eb1f03641d140064..d19b1ec1274eb8df897272628ec2d5230164b203 100644 (file)
@@ -29,7 +29,6 @@ const (
 
 // indirect returns the value, after dereferencing as many times
 // as necessary to reach the base type (or nil).
-// Signature modified by Hugo. TODO(bep) script this.
 func doIndirect(a any) any {
        if a == nil {
                return nil
@@ -46,8 +45,8 @@ func doIndirect(a any) any {
 }
 
 var (
-       errorType       = reflect.TypeOf((*error)(nil)).Elem()
-       fmtStringerType = reflect.TypeOf((*fmt.Stringer)(nil)).Elem()
+       errorType       = reflect.TypeFor[error]()
+       fmtStringerType = reflect.TypeFor[fmt.Stringer]()
 )
 
 // indirectToStringerOrError returns the value, after dereferencing as many times
index 6be5e0f84e5bc844f7283b74087692397c4f8442..7442194f79ced058d57be0d9e6ae9f100c58229a 100644 (file)
@@ -232,11 +232,9 @@ Least Surprise Property:
 knows that contextual autoescaping happens should be able to look at a {{.}}
 and correctly infer what sanitization happens."
 
-As a consequence of the Least Surprise Property, template actions within an
-ECMAScript 6 template literal are disabled by default.
-Handling string interpolation within these literals is rather complex resulting
-in no clear safe way to support it.
-To re-enable template actions within ECMAScript 6 template literals, use the
-GODEBUG=jstmpllitinterp=1 environment variable.
+Previously, ECMAScript 6 template literal were disabled by default, and could be
+enabled with the GODEBUG=jstmpllitinterp=1 environment variable. Template
+literals are now supported by default, and setting jstmpllitinterp has no
+effect.
 */
 package template
index 43cc3bf011ff5be4f5cc59d53c304b29eb686cca..24b22d984a2a82d15e40c406078ac2a02ff272eb 100644 (file)
@@ -2,9 +2,6 @@
 // Use of this source code is governed by a BSD-style
 // license that can be found in the LICENSE file.
 
-//go:build go1.13
-// +build go1.13
-
 package template_test
 
 import (
index 428cddc0d2837452466c09cacf07da58784dff29..e01813e68523a44e7071ee593735bf22796155d0 100644 (file)
@@ -273,8 +273,8 @@ type execTest struct {
 // 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)))
+       bigInt  = fmt.Sprintf("0x%x", int(1<<uint(reflect.TypeFor[int]().Bits()-1)-1))
+       bigUint = fmt.Sprintf("0x%x", uint(1<<uint(reflect.TypeFor[int]().Bits()-1)))
 )
 
 var execTests = []execTest{
@@ -580,6 +580,8 @@ var execTests = []execTest{
        {"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},
+       {"with else with", "{{with 0}}{{.}}{{else with true}}{{.}}{{end}}", "true", tVal, true},
+       {"with else with chain", "{{with 0}}{{.}}{{else with false}}{{.}}{{else with `notempty`}}{{.}}{{end}}", "notempty", tVal, true},
 
        // Range.
        {"range []int", "{{range .SI}}-{{.}}-{{end}}", "-3--4--5-", tVal, true},
index cc80d2b64e15186a838e462740999e6a9bdd52ed..1b56abeb108608db8c7bba8e3492a9463a6764e2 100644 (file)
@@ -125,7 +125,7 @@ var regexpPrecederKeywords = map[string]bool{
        "void":       true,
 }
 
-var jsonMarshalType = reflect.TypeOf((*json.Marshaler)(nil)).Elem()
+var jsonMarshalType = reflect.TypeFor[json.Marshaler]()
 
 // indirectToJSONMarshaler returns the value, after dereferencing as many times
 // as necessary to reach the base type (or nil) or an implementation of json.Marshal.
@@ -172,7 +172,7 @@ func jsValEscaper(args ...any) string {
        // cyclic data. This may be an unacceptable DoS risk.
        b, err := json.Marshal(a)
        if err != nil {
-               // While the standard JSON marshaller does not include user controlled
+               // While the standard JSON marshaler does not include user controlled
                // information in the error message, if a type has a MarshalJSON method,
                // the content of the error message is not guaranteed. Since we insert
                // the error into the template, as part of a comment, we attempt to
@@ -393,7 +393,6 @@ var jsStrNormReplacementTable = []string{
        '<':  `\u003c`,
        '>':  `\u003e`,
 }
-
 var jsRegexpReplacementTable = []string{
        0:    `\u0000`,
        '\t': `\t`,
index d7454f101d197f64201650c44771fef61e218d08..4582ddd5fd16943d25880030a07fb6bc0771272a 100644 (file)
@@ -179,7 +179,7 @@ func (t *Template) DefinedTemplates() string {
 // 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.
+// before the first use of [Template.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
@@ -238,8 +238,8 @@ func (t *Template) AddParseTree(name string, tree *parse.Tree) (*Template, error
 
 // 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
+// associated templates is, so further calls to [Template.Parse] in the copy will add
+// templates to the copy but not to the original. [Template.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.
 //
@@ -342,7 +342,7 @@ func (t *Template) Funcs(funcMap FuncMap) *Template {
 }
 
 // Delims sets the action delimiters to the specified strings, to be used in
-// subsequent calls to Parse, ParseFiles, or ParseGlob. Nested template
+// subsequent calls to [Template.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.
@@ -359,7 +359,7 @@ func (t *Template) Lookup(name string) *Template {
        return t.set[name]
 }
 
-// Must is a helper that wraps a call to a function returning (*Template, error)
+// 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
 //
@@ -371,10 +371,10 @@ func Must(t *Template, err error) *Template {
        return t
 }
 
-// ParseFiles creates a new Template and parses the template definitions from
+// 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.
+// 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.
@@ -436,12 +436,12 @@ func parseFiles(t *Template, readFile func(string) (string, []byte, error), file
        return t, nil
 }
 
-// ParseGlob creates a new Template and parses the template definitions from
+// 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.
+// [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.
@@ -485,7 +485,7 @@ func IsTrue(val any) (truth, ok bool) {
        return template.IsTrue(val)
 }
 
-// ParseFS is like ParseFiles or ParseGlob but reads from the file system fs
+// ParseFS is like [ParseFiles] or [ParseGlob] but reads from the file system fs
 // instead of the host operating system's file system.
 // It accepts a list of glob patterns.
 // (Note that most file names serve as glob patterns matching only themselves.)
@@ -493,7 +493,7 @@ func ParseFS(fs fs.FS, patterns ...string) (*Template, error) {
        return parseFS(nil, fs, patterns)
 }
 
-// ParseFS is like ParseFiles or ParseGlob but reads from the file system fs
+// ParseFS is like [Template.ParseFiles] or [Template.ParseGlob] but reads from the file system fs
 // instead of the host operating system's file system.
 // It accepts a list of glob patterns.
 // (Note that most file names serve as glob patterns matching only themselves.)
index d5a05f66da406da9f4eb3ebfc36499115c7e42a1..c430389a345f69b256ede5488a4e2b2940ffe9a2 100644 (file)
@@ -414,7 +414,7 @@ func tJSDelimited(c context, s []byte) (context, int) {
                        // If "</script" appears in a regex literal, the '/' should not
                        // close the regex literal, and it will later be escaped to
                        // "\x3C/script" in escapeText.
-                       if i > 0 && i+7 <= len(s) && bytes.Compare(bytes.ToLower(s[i-1:i+7]), []byte("</script")) == 0 {
+                       if i > 0 && i+7 <= len(s) && bytes.Equal(bytes.ToLower(s[i-1:i+7]), []byte("</script")) {
                                i++
                        } else if !inCharset {
                                c.state, c.jsCtx = stateJS, jsCtxDivOp
index 277df2255f8ef0406f0373696e840b4b4ffebe6c..e8577d2eae800a495a69c072a910364c70698ff9 100644 (file)
@@ -132,15 +132,13 @@ func findGOROOT() (string, error) {
                        // If runtime.GOROOT() is non-empty, assume that it is valid.
                        //
                        // (It might not be: for example, the user may have explicitly set GOROOT
-                       // to the wrong directory, or explicitly set GOROOT_FINAL but not GOROOT
-                       // and hasn't moved the tree to GOROOT_FINAL yet. But those cases are
+                       // to the wrong directory. But this case is
                        // rare, and if that happens the user can fix what they broke.)
                        return
                }
 
                // runtime.GOROOT doesn't know where GOROOT is (perhaps because the test
-               // binary was built with -trimpath, or perhaps because GOROOT_FINAL was set
-               // without GOROOT and the tree hasn't been moved there yet).
+               // binary was built with -trimpath).
                //
                // Since this is internal/testenv, we can cheat and assume that the caller
                // is a test of some package in a subdirectory of GOROOT/src. ('go test'
@@ -315,12 +313,18 @@ func MustInternalLink(t testing.TB, withCgo bool) {
        }
 }
 
+// MustInternalLinkPIE checks whether the current system can link PIE binary using
+// internal linking.
+// If not, MustInternalLinkPIE calls t.Skip with an explanation.
+// Modified by Hugo (not needed)
+func MustInternalLinkPIE(t testing.TB) {
+}
+
 // MustHaveBuildMode reports whether the current system can build programs in
 // the given build mode.
 // If not, MustHaveBuildMode calls t.Skip with an explanation.
 // Modified by Hugo (not needed)
 func MustHaveBuildMode(t testing.TB, buildmode string) {
-       return
 }
 
 // HasSymlink reports whether the current system can use os.Symlink.
@@ -447,3 +451,10 @@ func WriteImportcfg(t testing.TB, dstPath string, packageFiles map[string]string
 func SyscallIsNotSupported(err error) bool {
        return syscallIsNotSupported(err)
 }
+
+// ParallelOn64Bit calls t.Parallel() unless there is a case that cannot be parallel.
+// This function should be used when it is necessary to avoid t.Parallel on
+// 32-bit machines, typically because the test uses lots of memory.
+// Disabled by Hugo.
+func ParallelOn64Bit(t *testing.T) {
+}
index 032784bc3f0c08fc8c69363198cbf71381556edc..b3ffaabb153c9deac544aa00405321b2f340600e 100644 (file)
@@ -144,6 +144,13 @@ data, defined in detail in the corresponding sections that follow.
                is executed; otherwise, dot is set to the value of the pipeline
                and T1 is executed.
 
+       {{with pipeline}} T1 {{else with pipeline}} T0 {{end}}
+               To simplify the appearance of with-else chains, the else action
+               of a with may include another with directly; the effect is exactly
+               the same as writing
+                       {{with pipeline}} T1 {{else}}{{with pipeline}} T0 {{end}}{{end}}
+
+
 Arguments
 
 An argument is a simple value, denoted by one of the following.
index 295a810b853e5f12071a2f5567a230199e0708f4..975ceea930629202cb028e5c3d97ea5d996de7bb 100644 (file)
@@ -35,7 +35,7 @@ Josie
                Name, Gift string
                Attended   bool
        }
-       var recipients = []Recipient{
+       recipients := []Recipient{
                {"Aunt Mildred", "bone china tea set", true},
                {"Uncle John", "moleskin pants", false},
                {"Cousin Rodney", "", false},
index bc91e87f99ba263b59a4b7c1e137b2c7387134d5..6534ee33158890d4b1a0cd1565db1bfc2e32fb6a 100644 (file)
@@ -2,9 +2,6 @@
 // Use of this source code is governed by a BSD-style
 // license that can be found in the LICENSE file.
 
-//go:build go1.13
-// +build go1.13
-
 package template_test
 
 import (
index 73153c7648d060c91e4ba2c1e98c0277e57ba7ea..bd8c82bd705934924ed0b760a936ab13c0102c08 100644 (file)
@@ -7,13 +7,12 @@ package template
 import (
        "errors"
        "fmt"
+       "github.com/gohugoio/hugo/tpl/internal/go_templates/fmtsort"
+       "github.com/gohugoio/hugo/tpl/internal/go_templates/texttemplate/parse"
        "io"
        "reflect"
        "runtime"
        "strings"
-
-       "github.com/gohugoio/hugo/tpl/internal/go_templates/fmtsort"
-       "github.com/gohugoio/hugo/tpl/internal/go_templates/texttemplate/parse"
 )
 
 // maxExecDepth specifies the maximum stack depth of templates within
@@ -95,7 +94,7 @@ type missingValType struct{}
 
 var missingVal = reflect.ValueOf(missingValType{})
 
-var missingValReflectType = reflect.TypeOf(missingValType{})
+var missingValReflectType = reflect.TypeFor[missingValType]()
 
 func isMissing(v reflect.Value) bool {
        return v.IsValid() && v.Type() == missingValReflectType
@@ -202,8 +201,8 @@ func (t *Template) ExecuteTemplate(wr io.Writer, name string, data any) error {
 // 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.
+// 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 any) error {
        return t.execute(wr, data)
 }
@@ -229,7 +228,7 @@ func (t *Template) execute(wr io.Writer, data any) (err error) {
 // 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.
+// and in [html/template].
 func (t *Template) DefinedTemplates() string {
        if t.common == nil {
                return ""
@@ -409,8 +408,8 @@ func (s *state) walkRange(dot reflect.Value, r *parse.RangeNode) {
                        break
                }
                om := fmtsort.Sort(val)
-               for i, key := range om.Key {
-                       oneIteration(key, om.Value[i])
+               for _, m := range om {
+                       oneIteration(m.Key, m.Value)
                }
                return
        case reflect.Chan:
@@ -480,7 +479,7 @@ func (s *state) evalPipeline(dot reflect.Value, pipe *parse.PipeNode) (value ref
                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!
+                       value = value.Elem()
                }
        }
        for _, variable := range pipe.Decl {
@@ -709,9 +708,9 @@ func (s *state) evalFieldOld(dot reflect.Value, fieldName string, node parse.Nod
 }
 
 var (
-       errorType        = reflect.TypeOf((*error)(nil)).Elem()
-       fmtStringerType  = reflect.TypeOf((*fmt.Stringer)(nil)).Elem()
-       reflectValueType = reflect.TypeOf((*reflect.Value)(nil)).Elem()
+       errorType        = reflect.TypeFor[error]()
+       fmtStringerType  = reflect.TypeFor[fmt.Stringer]()
+       reflectValueType = reflect.TypeFor[reflect.Value]()
 )
 
 // evalCall executes a function or method call. If it's a method, fun already has the receiver bound, so
@@ -735,9 +734,8 @@ func (s *state) evalCallOld(dot, fun reflect.Value, isBuiltin bool, node parse.N
        } 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())
+       if err := goodFunc(name, typ); err != nil {
+               s.errorf("%v", err)
        }
 
        unwrap := func(v reflect.Value) reflect.Value {
@@ -801,6 +799,15 @@ func (s *state) evalCallOld(dot, fun reflect.Value, isBuiltin bool, node parse.N
                }
                argv[i] = s.validateType(final, t)
        }
+
+       // Special case for the "call" builtin.
+       // Insert the name of the callee function as the first argument.
+       if isBuiltin && name == "call" {
+               calleeName := args[0].String()
+               argv = append([]reflect.Value{reflect.ValueOf(calleeName)}, argv...)
+               fun = reflect.ValueOf(call)
+       }
+
        v, err := safeCall(fun, argv)
        // If we have an error that is not nil, stop execution and return that
        // error to the caller.
index ed75adacb6ac43c6f80606dc8eb959097cfb5890..efbaa9eec47a8f714e67fc5395181ef4ed01fbe5 100644 (file)
@@ -2,6 +2,9 @@
 // Use of this source code is governed by a BSD-style
 // license that can be found in the LICENSE file.
 
+//go:build !windows
+// +build !windows
+
 package template
 
 import (
@@ -75,12 +78,15 @@ type T struct {
        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
+       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
+       TooFewReturnCountFunc  func()
+       TooManyReturnCountFunc func() (string, error, int)
+       InvalidReturnTypeFunc  func() (string, bool)
        // Template to test evaluation of templates.
        Tmpl *Template
        // Unexported field; cannot be accessed by template.
@@ -168,6 +174,9 @@ var tVal = &T{
        NilOKFunc:                 func(s *int) bool { return s == nil },
        ErrFunc:                   func() (string, error) { return "bla", nil },
        PanicFunc:                 func() string { panic("test panic") },
+       TooFewReturnCountFunc:     func() {},
+       TooManyReturnCountFunc:    func() (string, error, int) { return "", nil, 0 },
+       InvalidReturnTypeFunc:     func() (string, bool) { return "", false },
        Tmpl:                      Must(New("x").Parse("test template")), // "x" is the value of .X
 }
 
@@ -265,8 +274,8 @@ type execTest struct {
 // 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)))
+       bigInt  = fmt.Sprintf("0x%x", int(1<<uint(reflect.TypeFor[int]().Bits()-1)-1))
+       bigUint = fmt.Sprintf("0x%x", uint(1<<uint(reflect.TypeFor[int]().Bits()-1)))
 )
 
 var execTests = []execTest{
@@ -583,6 +592,8 @@ var execTests = []execTest{
        {"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},
+       {"with else with", "{{with 0}}{{.}}{{else with true}}{{.}}{{end}}", "true", tVal, true},
+       {"with else with chain", "{{with 0}}{{.}}{{else with false}}{{.}}{{else with `notempty`}}{{.}}{{end}}", "notempty", tVal, true},
 
        // Range.
        {"range []int", "{{range .SI}}-{{.}}-{{end}}", "-3--4--5-", tVal, true},
@@ -1723,6 +1734,81 @@ func TestExecutePanicDuringCall(t *testing.T) {
        }
 }
 
+func TestFunctionCheckDuringCall(t *testing.T) {
+       tests := []struct {
+               name    string
+               input   string
+               data    any
+               wantErr string
+       }{
+               {
+                       name:    "call nothing",
+                       input:   `{{call}}`,
+                       data:    tVal,
+                       wantErr: "wrong number of args for call: want at least 1 got 0",
+               },
+               {
+                       name:    "call non-function",
+                       input:   "{{call .True}}",
+                       data:    tVal,
+                       wantErr: "error calling call: non-function .True of type bool",
+               },
+               {
+                       name:    "call func with wrong argument",
+                       input:   "{{call .BinaryFunc 1}}",
+                       data:    tVal,
+                       wantErr: "error calling call: wrong number of args for .BinaryFunc: got 1 want 2",
+               },
+               {
+                       name:    "call variadic func with wrong argument",
+                       input:   `{{call .VariadicFuncInt}}`,
+                       data:    tVal,
+                       wantErr: "error calling call: wrong number of args for .VariadicFuncInt: got 0 want at least 1",
+               },
+               {
+                       name:    "call too few return number func",
+                       input:   `{{call .TooFewReturnCountFunc}}`,
+                       data:    tVal,
+                       wantErr: "error calling call: function .TooFewReturnCountFunc has 0 return values; should be 1 or 2",
+               },
+               {
+                       name:    "call too many return number func",
+                       input:   `{{call .TooManyReturnCountFunc}}`,
+                       data:    tVal,
+                       wantErr: "error calling call: function .TooManyReturnCountFunc has 3 return values; should be 1 or 2",
+               },
+               {
+                       name:    "call invalid return type func",
+                       input:   `{{call .InvalidReturnTypeFunc}}`,
+                       data:    tVal,
+                       wantErr: "error calling call: invalid function signature for .InvalidReturnTypeFunc: second return value should be error; is bool",
+               },
+               {
+                       name:    "call pipeline",
+                       input:   `{{call (len "test")}}`,
+                       data:    nil,
+                       wantErr: "error calling call: non-function len \"test\" of type int",
+               },
+       }
+
+       for _, tc := range tests {
+               b := new(bytes.Buffer)
+               tmpl, err := New("t").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 tc.wantErr == "" || !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.
index a949f896fa3d5a5396f8a9750e7eee597ca76831..7d63cf8b7bb6db3864672e162394d21d6090b83b 100644 (file)
@@ -22,14 +22,14 @@ import (
 // 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
+// Errors returned by Execute wrap the underlying error; call [errors.As] to
 // unwrap 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.
+// of type [reflect.Value]. Similarly, functions meant to return a result of arbitrary
+// type can return interface{} or [reflect.Value].
 type FuncMap map[string]any
 
 // builtins returns the FuncMap.
@@ -39,7 +39,7 @@ type FuncMap map[string]any
 func builtins() FuncMap {
        return FuncMap{
                "and":      and,
-               "call":     call,
+               "call":     emptyCall,
                "html":     HTMLEscaper,
                "index":    index,
                "slice":    slice,
@@ -93,8 +93,8 @@ func addValueFuncs(out map[string]reflect.Value, in FuncMap) {
                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()))
+               if err := goodFunc(name, v.Type()); err != nil {
+                       panic(err)
                }
                out[name] = v
        }
@@ -109,15 +109,18 @@ func addFuncs(out, in FuncMap) {
 }
 
 // goodFunc reports whether the function or method has the right result signature.
-func goodFunc(typ reflect.Type) bool {
+func goodFunc(name string, typ reflect.Type) error {
        // 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
+       switch numOut := typ.NumOut(); {
+       case numOut == 1:
+               return nil
+       case numOut == 2 && typ.Out(1) == errorType:
+               return nil
+       case numOut == 2:
+               return fmt.Errorf("invalid function signature for %s: second return value should be error; is %s", name, typ.Out(1))
+       default:
+               return fmt.Errorf("function %s has %d return values; should be 1 or 2", name, typ.NumOut())
        }
-       return false
 }
 
 // goodName reports whether the function name is a valid identifier.
@@ -309,30 +312,35 @@ func length(item reflect.Value) (int, error) {
 
 // Function invocation
 
+func emptyCall(fn reflect.Value, args ...reflect.Value) reflect.Value {
+       panic("unreachable") // implemented as a special case in evalCall
+}
+
 // 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) {
+func call(name string, 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)
+               return reflect.Value{}, fmt.Errorf("non-function %s of type %s", name, typ)
        }
-       if !goodFunc(typ) {
-               return reflect.Value{}, fmt.Errorf("function called with %d args; should be 1 or 2", typ.NumOut())
+
+       if err := goodFunc(name, typ); err != nil {
+               return reflect.Value{}, err
        }
        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)
+                       return reflect.Value{}, fmt.Errorf("wrong number of args for %s: got %d want at least %d", name, 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)
+                       return reflect.Value{}, fmt.Errorf("wrong number of args for %s: got %d want %d", name, len(args), numIn)
                }
        }
        argv := make([]reflect.Value, len(args))
index 48af3928b39dbb7c4965fe971f8381d27242068b..81b55538e57c955ddeacfcf8eab05411699b99c9 100644 (file)
@@ -16,7 +16,7 @@ import (
 
 // Functions and methods to parse templates.
 
-// Must is a helper that wraps a call to a function returning (*Template, error)
+// 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
 //
@@ -28,7 +28,7 @@ func Must(t *Template, err error) *Template {
        return t
 }
 
-// ParseFiles creates a new Template and parses the template definitions from
+// 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.
@@ -45,9 +45,9 @@ func ParseFiles(filenames ...string) (*Template, error) {
 // 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
+// (see [filepath.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,
@@ -93,12 +93,12 @@ func parseFiles(t *Template, readFile func(string) (string, []byte, error), file
        return t, nil
 }
 
-// ParseGlob creates a new Template and parses the template definitions from
+// 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.
+// semantics of [filepath.Match], and the pattern must match at least one file.
+// The returned template will have the [filepath.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.
@@ -108,9 +108,9 @@ func ParseGlob(pattern string) (*Template, error) {
 
 // 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.
+// according to the semantics of [filepath.Match], and the pattern must match at
+// least one file. ParseGlob is equivalent to calling [Template.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.
@@ -131,17 +131,17 @@ func parseGlob(t *Template, pattern string) (*Template, error) {
        return parseFiles(t, readFileOS, filenames...)
 }
 
-// ParseFS is like ParseFiles or ParseGlob but reads from the file system fsys
+// ParseFS is like [Template.ParseFiles] or [Template.ParseGlob] but reads from the file system fsys
 // instead of the host operating system's file system.
-// It accepts a list of glob patterns.
+// It accepts a list of glob patterns (see [path.Match]).
 // (Note that most file names serve as glob patterns matching only themselves.)
 func ParseFS(fsys fs.FS, patterns ...string) (*Template, error) {
        return parseFS(nil, fsys, patterns)
 }
 
-// ParseFS is like ParseFiles or ParseGlob but reads from the file system fsys
+// ParseFS is like [Template.ParseFiles] or [Template.ParseGlob] but reads from the file system fsys
 // instead of the host operating system's file system.
-// It accepts a list of glob patterns.
+// It accepts a list of glob patterns (see [path.Match]).
 // (Note that most file names serve as glob patterns matching only themselves.)
 func (t *Template) ParseFS(fsys fs.FS, patterns ...string) (*Template, error) {
        t.init()
index 276367a7c386d689a6bab75826318078b38df821..12dbe041217f2e4bf6dcfd8849ee26fd69a4c87f 100644 (file)
@@ -278,9 +278,8 @@ func (s *state) evalCall(dot, fun reflect.Value, isBuiltin bool, node parse.Node
        } 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())
+       if err := goodFunc(name, typ); err != nil {
+               s.errorf("%v", err)
        }
 
        unwrap := func(v reflect.Value) reflect.Value {
@@ -345,6 +344,14 @@ func (s *state) evalCall(dot, fun reflect.Value, isBuiltin bool, node parse.Node
                argv[i] = s.validateType(final, t)
        }
 
+       // Special case for the "call" builtin.
+       // Insert the name of the callee function as the first argument.
+       if isBuiltin && name == "call" {
+               calleeName := args[0].String()
+               argv = append([]reflect.Value{reflect.ValueOf(calleeName)}, argv...)
+               fun = reflect.ValueOf(call)
+       }
+
        // Added for Hugo
        for i := 0; i < len(first); i++ {
                argv[i] = s.validateType(first[i], typ.In(i))
index 23f6a31fade53afb7188aed40776a3147b497d6a..63418cd90ce3ba2836744c6d733df430ef1b599e 100644 (file)
@@ -2,18 +2,16 @@
 // Use of this source code is governed by a BSD-style
 // license that can be found in the LICENSE file.
 
-//go:build go1.13
-// +build go1.13
-
 package template_test
 
 import (
        "bytes"
-       "github.com/gohugoio/hugo/tpl/internal/go_templates/testenv"
        "os"
        "os/exec"
        "path/filepath"
        "testing"
+
+       "github.com/gohugoio/hugo/tpl/internal/go_templates/testenv"
 )
 
 // Issue 36021: verify that text/template doesn't prevent the linker from removing
@@ -44,7 +42,7 @@ func main() {
 `
        td := t.TempDir()
 
-       if err := os.WriteFile(filepath.Join(td, "x.go"), []byte(prog), 0644); err != nil {
+       if err := os.WriteFile(filepath.Join(td, "x.go"), []byte(prog), 0o644); err != nil {
                t.Fatal(err)
        }
        cmd := exec.Command(testenv.GoToolPath(t), "build", "-o", "x.exe", "x.go")
index c36688825c200c2c415996b6a04b5944ce6a469d..a31309874d9da319961c80ded9edd6e21809ea59 100644 (file)
@@ -217,7 +217,11 @@ func (p *PipeNode) writeTo(sb *strings.Builder) {
                        }
                        v.writeTo(sb)
                }
-               sb.WriteString(" := ")
+               if p.IsAssign {
+                       sb.WriteString(" = ")
+               } else {
+                       sb.WriteString(" := ")
+               }
        }
        for i, c := range p.Cmds {
                if i > 0 {
@@ -346,12 +350,12 @@ type IdentifierNode struct {
        Ident string // The identifier's name.
 }
 
-// NewIdentifier returns a new IdentifierNode with the given identifier 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.
+// 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 {
@@ -359,7 +363,7 @@ func (i *IdentifierNode) SetPos(pos Pos) *IdentifierNode {
        return i
 }
 
-// SetTree sets the parent tree for the node. NewIdentifier is a public method so we can't modify its signature.
+// 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 {
index d43d5334ba04f3c3f7bdbee23378f1155d53a716..27c84f31eb3a9b423a4a7c26dac6aed59b6271b2 100644 (file)
@@ -42,7 +42,7 @@ const (
        SkipFuncCheck                  // do not check that functions are defined
 )
 
-// Copy returns a copy of the Tree. Any parsing state is discarded.
+// Copy returns a copy of the [Tree]. Any parsing state is discarded.
 func (t *Tree) Copy() *Tree {
        if t == nil {
                return nil
@@ -55,7 +55,7 @@ func (t *Tree) Copy() *Tree {
        }
 }
 
-// Parse returns a map from template name to parse.Tree, created by parsing the
+// Parse returns a map from template name to [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.
@@ -521,7 +521,7 @@ func (t *Tree) checkPipeline(pipe *PipeNode, context string) {
        }
 }
 
-func (t *Tree) parseControl(allowElseIf bool, context string) (pos Pos, line int, pipe *PipeNode, list, elseList *ListNode) {
+func (t *Tree) parseControl(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" {
@@ -535,27 +535,30 @@ func (t *Tree) parseControl(allowElseIf bool, context string) (pos Pos, line int
        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
+               // Special case for "else if" and "else with".
+               // If the "else" is followed immediately by an "if" or "with",
+               // the elseControl will have left the "if" or "with" token pending. Treat
+               //      {{if a}}_{{else if b}}_{{end}}
+               //  {{with a}}_{{else with b}}_{{end}}
+               // as
+               //      {{if a}}_{{else}}{{if b}}_{{end}}{{end}}
+               //  {{with a}}_{{else}}{{with b}}_{{end}}{{end}}.
+               // To do this, parse the "if" or "with" as usual and stop at it {{end}};
+               // the subsequent{{end}} is assumed. This technique works even for long if-else-if chains.
+               if context == "if" && t.peek().typ == itemIf {
+                       t.next() // Consume the "if" token.
+                       elseList = t.newList(next.Position())
+                       elseList.append(t.ifControl())
+               } else if context == "with" && t.peek().typ == itemWith {
+                       t.next()
+                       elseList = t.newList(next.Position())
+                       elseList.append(t.withControl())
+               } else {
+                       elseList, next = t.itemList()
+                       if next.Type() != nodeEnd {
+                               t.errorf("expected end; found %s", next)
                        }
                }
-               elseList, next = t.itemList()
-               if next.Type() != nodeEnd {
-                       t.errorf("expected end; found %s", next)
-               }
        }
        return pipe.Position(), pipe.Line, pipe, list, elseList
 }
@@ -567,7 +570,7 @@ func (t *Tree) parseControl(allowElseIf bool, context string) (pos Pos, line int
 //
 // If keyword is past.
 func (t *Tree) ifControl() Node {
-       return t.newIf(t.parseControl(true, "if"))
+       return t.newIf(t.parseControl("if"))
 }
 
 // Range:
@@ -577,7 +580,7 @@ func (t *Tree) ifControl() Node {
 //
 // Range keyword is past.
 func (t *Tree) rangeControl() Node {
-       r := t.newRange(t.parseControl(false, "range"))
+       r := t.newRange(t.parseControl("range"))
        return r
 }
 
@@ -588,7 +591,7 @@ func (t *Tree) rangeControl() Node {
 //
 // If keyword is past.
 func (t *Tree) withControl() Node {
-       return t.newWith(t.parseControl(false, "with"))
+       return t.newWith(t.parseControl("with"))
 }
 
 // End:
@@ -606,10 +609,11 @@ func (t *Tree) endControl() Node {
 //
 // 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 ... ".
+       // The "{{else if ... " and "{{else with ..." will be
+       // treated as "{{else}}{{if ..." and "{{else}}{{with ...".
+       // So return the else node here.
+       if peek.typ == itemIf || peek.typ == itemWith {
                return t.newElse(peek.pos, peek.line)
        }
        token := t.expect(itemRightDelim, "else")
index 59e0a17412235b1c2645543e49e02c0b5ff9827b..80e7f53faddcd623e8b121af07e619b8a0533cc8 100644 (file)
@@ -33,9 +33,9 @@ var numberTests = []numberTest{
        {"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},
+       {"073", true, true, true, false, 0o73, 0o73, 0o73, 0},
+       {"0o73", true, true, true, false, 0o73, 0o73, 0o73, 0},
+       {"0O73", true, true, true, false, 0o73, 0o73, 0o73, 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},
@@ -61,7 +61,7 @@ var numberTests = []numberTest{
        {"-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},
+       {"0123", true, true, true, false, 0o123, 0o123, 0o123, 0},
        {"-0x0", true, true, true, false, 0, 0, 0, 0},
        {"0xdeadbeef", true, true, true, false, 0xdeadbeef, 0xdeadbeef, 0xdeadbeef, 0},
        // character constants
@@ -176,74 +176,150 @@ const (
 )
 
 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}}`},
+       {
+               "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}}`,
+       },
+       {
+               "with with else with", "{{with .X}}hello{{else with .Y}}goodbye{{end}}", noError,
+               `{{with .X}}"hello"{{else}}{{with .Y}}"goodbye"{{end}}{{end}}`,
+       },
+       {
+               "with else chain", "{{with .X}}X{{else with .Y}}Y{{else with .Z}}Z{{end}}", noError,
+               `{{with .X}}"X"{{else}}{{with .Y}}"Y"{{else}}{{with .Z}}"Z"{{end}}{{end}}{{end}}`,
+       },
        // Trimming spaces.
        {"trim left", "x \r\n\t{{- 3}}", noError, `"x"{{3}}`},
        {"trim right", "{{3 -}}\n\n\ty", noError, `{{3}}"y"`},
@@ -252,18 +328,24 @@ var parseTests = []parseTest{
        {"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" .}}`},
+       {
+               "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, ""},
-       {"spaces around continue", "{{range .SI}}{{.}}{{ continue }}{{end}}", noError,
-               `{{range .SI}}{{.}}{{continue}}{{end}}`},
-       {"spaces around break", "{{range .SI}}{{.}}{{ break }}{{end}}", noError,
-               `{{range .SI}}{{.}}{{break}}{{end}}`},
+       {
+               "spaces around continue", "{{range .SI}}{{.}}{{ continue }}{{end}}", noError,
+               `{{range .SI}}{{.}}{{continue}}{{end}}`,
+       },
+       {
+               "spaces around break", "{{range .SI}}{{.}}{{ break }}{{end}}", noError,
+               `{{range .SI}}{{.}}{{break}}{{end}}`,
+       },
 
        // Errors.
        {"unclosed action", "hello{{range", hasError, ""},
@@ -302,6 +384,9 @@ var parseTests = []parseTest{
        {"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.
+       // Check the range handles assignment vs. declaration properly.
+       {"bug2a", "{{range $x := 0}}{{$x}}{{end}}", noError, "{{range $x := 0}}{{$x}}{{end}}"},
+       {"bug2b", "{{range $x = 0}}{{$x}}{{end}}", noError, "{{range $x = 0}}{{$x}}{{end}}"},
        // dot following a literal value
        {"dot after integer", "{{1.E}}", hasError, ""},
        {"dot after float", "{{0.1.E}}", hasError, ""},
@@ -402,7 +487,7 @@ func TestKeywordsAndFuncs(t *testing.T) {
        {
                // 'break' is a defined function, don't treat it as a keyword: it should
                // accept an argument successfully.
-               var funcsWithKeywordFunc = map[string]any{
+               funcsWithKeywordFunc := map[string]any{
                        "break": func(in any) any { return in },
                }
                tmpl, err := New("").Parse(inp, "", "", make(map[string]*Tree), funcsWithKeywordFunc)
@@ -489,104 +574,168 @@ func TestErrorContextWithTreeCopy(t *testing.T) {
 // All failures, and the result is a string that must appear in the error message.
 var errorTests = []parseTest{
        // Check line numbers are accurate.
-       {"unclosed1",
+       {
+               "unclosed1",
                "line1\n{{",
-               hasError, `unclosed1:2: unclosed action`},
-       {"unclosed2",
+               hasError, `unclosed1:2: unclosed action`,
+       },
+       {
+               "unclosed2",
                "line1\n{{define `x`}}line2\n{{",
-               hasError, `unclosed2:3: unclosed action`},
-       {"unclosed3",
+               hasError, `unclosed2:3: unclosed action`,
+       },
+       {
+               "unclosed3",
                "line1\n{{\"x\"\n\"y\"\n",
-               hasError, `unclosed3:4: unclosed action started at unclosed3:2`},
-       {"unclosed4",
+               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",
+               hasError, `unclosed4:6: unclosed action started at unclosed4:1`,
+       },
+       {
+               "var1",
                "line1\n{{\nx\n}}",
-               hasError, `var1:3: function "x" not defined`},
+               hasError, `var1:3: function "x" not defined`,
+       },
        // Specific errors.
-       {"function",
+       {
+               "function",
                "{{foo}}",
-               hasError, `function "foo" not defined`},
-       {"comment1",
+               hasError, `function "foo" not defined`,
+       },
+       {
+               "comment1",
                "{{/*}}",
-               hasError, `comment1:1: unclosed comment`},
-       {"comment2",
+               hasError, `comment1:1: unclosed comment`,
+       },
+       {
+               "comment2",
                "{{/*\nhello\n}}",
-               hasError, `comment2:1: unclosed comment`},
-       {"lparen",
+               hasError, `comment2:1: unclosed comment`,
+       },
+       {
+               "lparen",
                "{{.X (1 2 3}}",
-               hasError, `unclosed left paren`},
-       {"rparen",
+               hasError, `unclosed left paren`,
+       },
+       {
+               "rparen",
                "{{.X 1 2 3 ) }}",
-               hasError, "unexpected right paren"},
-       {"rparen2",
+               hasError, "unexpected right paren",
+       },
+       {
+               "rparen2",
                "{{(.X 1 2 3",
-               hasError, `unclosed action`},
-       {"space",
+               hasError, `unclosed action`,
+       },
+       {
+               "space",
                "{{`x`3}}",
-               hasError, `in operand`},
-       {"idchar",
+               hasError, `in operand`,
+       },
+       {
+               "idchar",
                "{{a#}}",
-               hasError, `'#'`},
-       {"charconst",
+               hasError, `'#'`,
+       },
+       {
+               "charconst",
                "{{'a}}",
-               hasError, `unterminated character constant`},
-       {"stringconst",
+               hasError, `unterminated character constant`,
+       },
+       {
+               "stringconst",
                `{{"a}}`,
-               hasError, `unterminated quoted string`},
-       {"rawstringconst",
+               hasError, `unterminated quoted string`,
+       },
+       {
+               "rawstringconst",
                "{{`a}}",
-               hasError, `unterminated raw quoted string`},
-       {"number",
+               hasError, `unterminated raw quoted string`,
+       },
+       {
+               "number",
                "{{0xi}}",
-               hasError, `number syntax`},
-       {"multidefine",
+               hasError, `number syntax`,
+       },
+       {
+               "multidefine",
                "{{define `a`}}a{{end}}{{define `a`}}b{{end}}",
-               hasError, `multiple definition of template`},
-       {"eof",
+               hasError, `multiple definition of template`,
+       },
+       {
+               "eof",
                "{{range .X}}",
-               hasError, `unexpected EOF`},
-       {"variable",
+               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",
+               hasError, `unexpected ":="`,
+       },
+       {
+               "multidecl",
                "{{$a,$b,$c := 23}}",
-               hasError, `too many declarations`},
-       {"undefvar",
+               hasError, `too many declarations`,
+       },
+       {
+               "undefvar",
                "{{$a}}",
-               hasError, `undefined variable`},
-       {"wrongdot",
+               hasError, `undefined variable`,
+       },
+       {
+               "wrongdot",
                "{{true.any}}",
-               hasError, `unexpected . after term`},
-       {"wrongpipeline",
+               hasError, `unexpected . after term`,
+       },
+       {
+               "wrongpipeline",
                "{{12|false}}",
-               hasError, `non executable command in pipeline`},
-       {"emptypipeline",
+               hasError, `non executable command in pipeline`,
+       },
+       {
+               "emptypipeline",
                `{{ ( ) }}`,
-               hasError, `missing value for parenthesized pipeline`},
-       {"multilinerawstring",
+               hasError, `missing value for parenthesized pipeline`,
+       },
+       {
+               "multilinerawstring",
                "{{ $v := `\n` }} {{",
-               hasError, `multilinerawstring:2: unclosed action`},
-       {"rangeundefvar",
+               hasError, `multilinerawstring:2: unclosed action`,
+       },
+       {
+               "rangeundefvar",
                "{{range $k}}{{end}}",
-               hasError, `undefined variable`},
-       {"rangeundefvars",
+               hasError, `undefined variable`,
+       },
+       {
+               "rangeundefvars",
                "{{range $k, $v}}{{end}}",
-               hasError, `undefined variable`},
-       {"rangemissingvalue1",
+               hasError, `undefined variable`,
+       },
+       {
+               "rangemissingvalue1",
                "{{range $k,}}{{end}}",
-               hasError, `missing value for range`},
-       {"rangemissingvalue2",
+               hasError, `missing value for range`,
+       },
+       {
+               "rangemissingvalue2",
                "{{range $k, $v := }}{{end}}",
-               hasError, `missing value for range`},
-       {"rangenotvariable1",
+               hasError, `missing value for range`,
+       },
+       {
+               "rangenotvariable1",
                "{{range $k, .}}{{end}}",
-               hasError, `range can only initialize variables`},
-       {"rangenotvariable2",
+               hasError, `range can only initialize variables`,
+       },
+       {
+               "rangenotvariable2",
                "{{range $k, 123 := .}}{{end}}",
-               hasError, `range can only initialize variables`},
+               hasError, `range can only initialize variables`,
+       },
 }
 
 func TestErrors(t *testing.T) {
index 1ba72c194d0e07eddb6948e73eb4a43e8584e8d9..536932a60bdc057defc6b0803d6d4501594c4221 100644 (file)
@@ -24,7 +24,7 @@ type common struct {
 }
 
 // Template is the representation of a parsed template. The *parse.Tree
-// field is exported only for use by html/template and should be treated
+// field is exported only for use by [html/template] and should be treated
 // as unexported by all other clients.
 type Template struct {
        name string
@@ -79,7 +79,7 @@ func (t *Template) init() {
 
 // 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
+// associated templates is, so further calls to [Template.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.
@@ -157,7 +157,7 @@ func (t *Template) Templates() []*Template {
 }
 
 // Delims sets the action delimiters to the specified strings, to be used in
-// subsequent calls to Parse, ParseFiles, or ParseGlob. Nested template
+// subsequent calls to [Template.Parse], [Template.ParseFiles], or [Template.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.
index 41257912d20ad20110619e342cc0c0b283367b2e..a8599bbade6270ce1abf222e7306d5be11857a84 100644 (file)
@@ -116,6 +116,20 @@ counter2: 3
 `)
 }
 
+func TestGo23ElseWith(t *testing.T) {
+       t.Parallel()
+
+       files := `
+-- hugo.toml --
+title = "Hugo"
+-- layouts/index.html --
+{{ with false }}{{ else with .Site }}{{ .Title }}{{ end }}|
+`
+       b := hugolib.Test(t, files)
+
+       b.AssertFileContent("public/index.html", "Hugo|")
+}
+
 // Issue 10495
 func TestCommentsBeforeBlockDefinition(t *testing.T) {
        t.Parallel()