parent
9b5f786df8
commit
fd8b0fbf8a
37 changed files with 652 additions and 566 deletions
|
@ -4,7 +4,7 @@ parameters:
|
||||||
defaults: &defaults
|
defaults: &defaults
|
||||||
resource_class: large
|
resource_class: large
|
||||||
docker:
|
docker:
|
||||||
- image: bepsays/ci-hugoreleaser:1.22301.20401
|
- image: bepsays/ci-hugoreleaser:1.22400.20000
|
||||||
environment: &buildenv
|
environment: &buildenv
|
||||||
GOMODCACHE: /root/project/gomodcache
|
GOMODCACHE: /root/project/gomodcache
|
||||||
version: 2
|
version: 2
|
||||||
|
@ -58,7 +58,7 @@ jobs:
|
||||||
environment:
|
environment:
|
||||||
<<: [*buildenv]
|
<<: [*buildenv]
|
||||||
docker:
|
docker:
|
||||||
- image: bepsays/ci-hugoreleaser-linux-arm64:1.22301.20401
|
- image: bepsays/ci-hugoreleaser-linux-arm64:1.22400.20000
|
||||||
steps:
|
steps:
|
||||||
- *restore-cache
|
- *restore-cache
|
||||||
- &attach-workspace
|
- &attach-workspace
|
||||||
|
|
2
.github/workflows/test.yml
vendored
2
.github/workflows/test.yml
vendored
|
@ -16,7 +16,7 @@ jobs:
|
||||||
test:
|
test:
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
go-version: [1.22.x, 1.23.x]
|
go-version: [1.23.x, 1.24.x]
|
||||||
os: [ubuntu-latest, windows-latest] # macos disabled for now because of disk space issues.
|
os: [ubuntu-latest, windows-latest] # macos disabled for now because of disk space issues.
|
||||||
runs-on: ${{ matrix.os }}
|
runs-on: ${{ matrix.os }}
|
||||||
steps:
|
steps:
|
||||||
|
|
2
go.mod
2
go.mod
|
@ -170,4 +170,4 @@ require (
|
||||||
software.sslmate.com/src/go-pkcs12 v0.2.0 // indirect
|
software.sslmate.com/src/go-pkcs12 v0.2.0 // indirect
|
||||||
)
|
)
|
||||||
|
|
||||||
go 1.22.6
|
go 1.23
|
||||||
|
|
|
@ -16,7 +16,7 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
// The current is built with 6885bad7dd86880be6929c02085e5c7a67ff2887 go1.23.0
|
// The current is built with 3901409b5d [release-branch.go1.24] go1.24.0
|
||||||
// TODO(bep) preserve the staticcheck.conf file.
|
// TODO(bep) preserve the staticcheck.conf file.
|
||||||
fmt.Println("Forking ...")
|
fmt.Println("Forking ...")
|
||||||
defer fmt.Println("Done ...")
|
defer fmt.Println("Done ...")
|
||||||
|
@ -216,6 +216,7 @@ func rewrite(filename, rule string) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func goimports(dir string) {
|
func goimports(dir string) {
|
||||||
|
// Needs go install golang.org/x/tools/cmd/goimports@latest
|
||||||
cmf, _ := hexec.SafeCommand("goimports", "-w", dir)
|
cmf, _ := hexec.SafeCommand("goimports", "-w", dir)
|
||||||
out, err := cmf.CombinedOutput()
|
out, err := cmf.CombinedOutput()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -37,12 +37,14 @@ const KnownEnv = `
|
||||||
GOARCH
|
GOARCH
|
||||||
GOARM
|
GOARM
|
||||||
GOARM64
|
GOARM64
|
||||||
|
GOAUTH
|
||||||
GOBIN
|
GOBIN
|
||||||
GOCACHE
|
GOCACHE
|
||||||
GOCACHEPROG
|
GOCACHEPROG
|
||||||
GOENV
|
GOENV
|
||||||
GOEXE
|
GOEXE
|
||||||
GOEXPERIMENT
|
GOEXPERIMENT
|
||||||
|
GOFIPS140
|
||||||
GOFLAGS
|
GOFLAGS
|
||||||
GOGCCFLAGS
|
GOGCCFLAGS
|
||||||
GOHOSTARCH
|
GOHOSTARCH
|
||||||
|
|
|
@ -2,8 +2,8 @@
|
||||||
// Use of this source code is governed by a BSD-style
|
// Use of this source code is governed by a BSD-style
|
||||||
// license that can be found in the LICENSE file.
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
//go:build !windows
|
//go:build go1.13 && !windows
|
||||||
// +build !windows
|
// +build go1.13,!windows
|
||||||
|
|
||||||
package template
|
package template
|
||||||
|
|
||||||
|
|
|
@ -2,8 +2,8 @@
|
||||||
// Use of this source code is governed by a BSD-style
|
// Use of this source code is governed by a BSD-style
|
||||||
// license that can be found in the LICENSE file.
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
//go:build !windows
|
//go:build go1.13 && !windows
|
||||||
// +build !windows
|
// +build go1.13,!windows
|
||||||
|
|
||||||
package template
|
package template
|
||||||
|
|
||||||
|
@ -428,7 +428,7 @@ func TestStringer(t *testing.T) {
|
||||||
if err := tmpl.Execute(b, s); err != nil {
|
if err := tmpl.Execute(b, s); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
expect := "string=3"
|
var expect = "string=3"
|
||||||
if b.String() != expect {
|
if b.String() != expect {
|
||||||
t.Errorf("expected %q got %q", expect, b.String())
|
t.Errorf("expected %q got %q", expect, b.String())
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,8 +2,8 @@
|
||||||
// Use of this source code is governed by a BSD-style
|
// Use of this source code is governed by a BSD-style
|
||||||
// license that can be found in the LICENSE file.
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
//go:build !windows
|
//go:build go1.13 && !windows
|
||||||
// +build !windows
|
// +build go1.13,!windows
|
||||||
|
|
||||||
package template
|
package template
|
||||||
|
|
||||||
|
|
|
@ -9,6 +9,7 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"html"
|
"html"
|
||||||
"io"
|
"io"
|
||||||
|
"maps"
|
||||||
"regexp"
|
"regexp"
|
||||||
|
|
||||||
template "github.com/gohugoio/hugo/tpl/internal/go_templates/texttemplate"
|
template "github.com/gohugoio/hugo/tpl/internal/go_templates/texttemplate"
|
||||||
|
@ -145,7 +146,7 @@ func (e *escaper) escape(c context, n parse.Node) context {
|
||||||
return c
|
return c
|
||||||
case *parse.ContinueNode:
|
case *parse.ContinueNode:
|
||||||
c.n = n
|
c.n = n
|
||||||
e.rangeContext.continues = append(e.rangeContext.breaks, c)
|
e.rangeContext.continues = append(e.rangeContext.continues, c)
|
||||||
return context{state: stateDead}
|
return context{state: stateDead}
|
||||||
case *parse.IfNode:
|
case *parse.IfNode:
|
||||||
return e.escapeBranch(c, &n.BranchNode, "if")
|
return e.escapeBranch(c, &n.BranchNode, "if")
|
||||||
|
@ -588,22 +589,14 @@ func (e *escaper) escapeListConditionally(c context, n *parse.ListNode, filter f
|
||||||
e1 := makeEscaper(e.ns)
|
e1 := makeEscaper(e.ns)
|
||||||
e1.rangeContext = e.rangeContext
|
e1.rangeContext = e.rangeContext
|
||||||
// Make type inferences available to f.
|
// Make type inferences available to f.
|
||||||
for k, v := range e.output {
|
maps.Copy(e1.output, e.output)
|
||||||
e1.output[k] = v
|
|
||||||
}
|
|
||||||
c = e1.escapeList(c, n)
|
c = e1.escapeList(c, n)
|
||||||
ok := filter != nil && filter(&e1, c)
|
ok := filter != nil && filter(&e1, c)
|
||||||
if ok {
|
if ok {
|
||||||
// Copy inferences and edits from e1 back into e.
|
// Copy inferences and edits from e1 back into e.
|
||||||
for k, v := range e1.output {
|
maps.Copy(e.output, e1.output)
|
||||||
e.output[k] = v
|
maps.Copy(e.derived, e1.derived)
|
||||||
}
|
maps.Copy(e.called, e1.called)
|
||||||
for k, v := range e1.derived {
|
|
||||||
e.derived[k] = v
|
|
||||||
}
|
|
||||||
for k, v := range e1.called {
|
|
||||||
e.called[k] = v
|
|
||||||
}
|
|
||||||
for k, v := range e1.actionNodeEdits {
|
for k, v := range e1.actionNodeEdits {
|
||||||
e.editActionNode(k, v)
|
e.editActionNode(k, v)
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,8 +2,8 @@
|
||||||
// Use of this source code is governed by a BSD-style
|
// Use of this source code is governed by a BSD-style
|
||||||
// license that can be found in the LICENSE file.
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
//go:build !windows
|
//go:build go1.13 && !windows
|
||||||
// +build !windows
|
// +build go1.13,!windows
|
||||||
|
|
||||||
package template
|
package template
|
||||||
|
|
||||||
|
@ -944,6 +944,7 @@ func TestEscapeSet(t *testing.T) {
|
||||||
t.Errorf("want\n\t%q\ngot\n\t%q", test.want, got)
|
t.Errorf("want\n\t%q\ngot\n\t%q", test.want, got)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestErrors(t *testing.T) {
|
func TestErrors(t *testing.T) {
|
||||||
|
@ -1064,6 +1065,10 @@ func TestErrors(t *testing.T) {
|
||||||
"{{range .Items}}<a{{if .X}}{{continue}}{{end}}>{{end}}",
|
"{{range .Items}}<a{{if .X}}{{continue}}{{end}}>{{end}}",
|
||||||
"z:1:29: at range loop continue: {{range}} branches end in different contexts",
|
"z:1:29: at range loop continue: {{range}} branches end in different contexts",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"{{range .Items}}{{if .X}}{{break}}{{end}}<a{{if .Y}}{{continue}}{{end}}>{{if .Z}}{{continue}}{{end}}{{end}}",
|
||||||
|
"z:1:54: at range loop continue: {{range}} branches end in different contexts",
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"<a b=1 c={{.H}}",
|
"<a b=1 c={{.H}}",
|
||||||
"z: ends in a non-text context: {stateAttr delimSpaceOrTagEnd",
|
"z: ends in a non-text context: {stateAttr delimSpaceOrTagEnd",
|
||||||
|
@ -1193,6 +1198,7 @@ func TestErrors(t *testing.T) {
|
||||||
// Check that we get the same error if we call Execute again.
|
// Check that we get the same error if we call Execute again.
|
||||||
if err := tmpl.Execute(buf, nil); err == nil || err.Error() != got {
|
if err := tmpl.Execute(buf, nil); err == nil || err.Error() != got {
|
||||||
t.Errorf("input=%q: unexpected error on second call %q", test.input, err)
|
t.Errorf("input=%q: unexpected error on second call %q", test.input, err)
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,6 +2,9 @@
|
||||||
// Use of this source code is governed by a BSD-style
|
// Use of this source code is governed by a BSD-style
|
||||||
// license that can be found in the LICENSE file.
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
//go:build go1.13
|
||||||
|
// +build go1.13
|
||||||
|
|
||||||
package template_test
|
package template_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
@ -80,6 +83,7 @@ func Example() {
|
||||||
// <div><strong>no rows</strong></div>
|
// <div><strong>no rows</strong></div>
|
||||||
// </body>
|
// </body>
|
||||||
// </html>
|
// </html>
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func Example_autoescaping() {
|
func Example_autoescaping() {
|
||||||
|
@ -120,6 +124,7 @@ func Example_escape() {
|
||||||
// \"Fran \u0026 Freddie\'s Diner\" \u003Ctasty@example.com\u003E
|
// \"Fran \u0026 Freddie\'s Diner\" \u003Ctasty@example.com\u003E
|
||||||
// \"Fran \u0026 Freddie\'s Diner\"32\u003Ctasty@example.com\u003E
|
// \"Fran \u0026 Freddie\'s Diner\"32\u003Ctasty@example.com\u003E
|
||||||
// %22Fran+%26+Freddie%27s+Diner%2232%3Ctasty%40example.com%3E
|
// %22Fran+%26+Freddie%27s+Diner%2232%3Ctasty%40example.com%3E
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func ExampleTemplate_Delims() {
|
func ExampleTemplate_Delims() {
|
||||||
|
|
|
@ -2,6 +2,9 @@
|
||||||
// Use of this source code is governed by a BSD-style
|
// Use of this source code is governed by a BSD-style
|
||||||
// license that can be found in the LICENSE file.
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
//go:build go1.13
|
||||||
|
// +build go1.13
|
||||||
|
|
||||||
package template_test
|
package template_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
|
|
@ -325,16 +325,12 @@ var execTests = []execTest{
|
||||||
{"$.U.V", "{{$.U.V}}", "v", tVal, true},
|
{"$.U.V", "{{$.U.V}}", "v", tVal, true},
|
||||||
{"declare in action", "{{$x := $.U.V}}{{$x}}", "v", tVal, true},
|
{"declare in action", "{{$x := $.U.V}}{{$x}}", "v", tVal, true},
|
||||||
{"simple assignment", "{{$x := 2}}{{$x = 3}}{{$x}}", "3", tVal, true},
|
{"simple assignment", "{{$x := 2}}{{$x = 3}}{{$x}}", "3", tVal, true},
|
||||||
{
|
{"nested assignment",
|
||||||
"nested assignment",
|
|
||||||
"{{$x := 2}}{{if true}}{{$x = 3}}{{end}}{{$x}}",
|
"{{$x := 2}}{{if true}}{{$x = 3}}{{end}}{{$x}}",
|
||||||
"3", tVal, true,
|
"3", tVal, true},
|
||||||
},
|
{"nested assignment changes the last declaration",
|
||||||
{
|
|
||||||
"nested assignment changes the last declaration",
|
|
||||||
"{{$x := 1}}{{if true}}{{$x := 2}}{{if true}}{{$x = 3}}{{end}}{{end}}{{$x}}",
|
"{{$x := 1}}{{if true}}{{$x := 2}}{{if true}}{{$x = 3}}{{end}}{{end}}{{$x}}",
|
||||||
"1", tVal, true,
|
"1", tVal, true},
|
||||||
},
|
|
||||||
|
|
||||||
// Type with String method.
|
// Type with String method.
|
||||||
{"V{6666}.String()", "-{{.V0}}-", "-{6666}-", tVal, true}, // NOTE: -<6666>- in text/template
|
{"V{6666}.String()", "-{{.V0}}-", "-{6666}-", tVal, true}, // NOTE: -<6666>- in text/template
|
||||||
|
@ -381,21 +377,15 @@ var execTests = []execTest{
|
||||||
{".Method3(nil constant)", "-{{.Method3 nil}}-", "-Method3: <nil>-", tVal, true},
|
{".Method3(nil constant)", "-{{.Method3 nil}}-", "-Method3: <nil>-", tVal, true},
|
||||||
{".Method3(nil value)", "-{{.Method3 .MXI.unset}}-", "-Method3: <nil>-", tVal, true},
|
{".Method3(nil value)", "-{{.Method3 .MXI.unset}}-", "-Method3: <nil>-", tVal, true},
|
||||||
{"method on var", "{{if $x := .}}-{{$x.Method2 .U16 $x.X}}{{end}}-", "-Method2: 16 x-", tVal, true},
|
{"method on var", "{{if $x := .}}-{{$x.Method2 .U16 $x.X}}{{end}}-", "-Method2: 16 x-", tVal, true},
|
||||||
{
|
{"method on chained var",
|
||||||
"method on chained var",
|
|
||||||
"{{range .MSIone}}{{if $.U.TrueFalse $.True}}{{$.U.TrueFalse $.True}}{{else}}WRONG{{end}}{{end}}",
|
"{{range .MSIone}}{{if $.U.TrueFalse $.True}}{{$.U.TrueFalse $.True}}{{else}}WRONG{{end}}{{end}}",
|
||||||
"true", tVal, true,
|
"true", tVal, true},
|
||||||
},
|
{"chained method",
|
||||||
{
|
|
||||||
"chained method",
|
|
||||||
"{{range .MSIone}}{{if $.GetU.TrueFalse $.True}}{{$.U.TrueFalse $.True}}{{else}}WRONG{{end}}{{end}}",
|
"{{range .MSIone}}{{if $.GetU.TrueFalse $.True}}{{$.U.TrueFalse $.True}}{{else}}WRONG{{end}}{{end}}",
|
||||||
"true", tVal, true,
|
"true", tVal, true},
|
||||||
},
|
{"chained method on variable",
|
||||||
{
|
|
||||||
"chained method on variable",
|
|
||||||
"{{with $x := .}}{{with .SI}}{{$.GetU.TrueFalse $.True}}{{end}}{{end}}",
|
"{{with $x := .}}{{with .SI}}{{$.GetU.TrueFalse $.True}}{{end}}{{end}}",
|
||||||
"true", tVal, true,
|
"true", tVal, true},
|
||||||
},
|
|
||||||
{".NilOKFunc not nil", "{{call .NilOKFunc .PI}}", "false", tVal, true},
|
{".NilOKFunc not nil", "{{call .NilOKFunc .PI}}", "false", tVal, true},
|
||||||
{".NilOKFunc nil", "{{call .NilOKFunc nil}}", "true", tVal, true},
|
{".NilOKFunc nil", "{{call .NilOKFunc nil}}", "true", tVal, true},
|
||||||
{"method on nil value from slice", "-{{range .}}{{.Method1 1234}}{{end}}-", "-1234-", tSliceOfNil, true},
|
{"method on nil value from slice", "-{{range .}}{{.Method1 1234}}{{end}}-", "-1234-", tSliceOfNil, true},
|
||||||
|
@ -481,14 +471,10 @@ var execTests = []execTest{
|
||||||
{"printf lots", `{{printf "%d %s %g %s" 127 "hello" 7-3i .Method0}}`, "127 hello (7-3i) M0", tVal, true},
|
{"printf lots", `{{printf "%d %s %g %s" 127 "hello" 7-3i .Method0}}`, "127 hello (7-3i) M0", tVal, true},
|
||||||
|
|
||||||
// HTML.
|
// HTML.
|
||||||
{
|
{"html", `{{html "<script>alert(\"XSS\");</script>"}}`,
|
||||||
"html", `{{html "<script>alert(\"XSS\");</script>"}}`,
|
"<script>alert("XSS");</script>", nil, true},
|
||||||
"<script>alert("XSS");</script>", nil, true,
|
{"html pipeline", `{{printf "<script>alert(\"XSS\");</script>" | html}}`,
|
||||||
},
|
"<script>alert("XSS");</script>", nil, true},
|
||||||
{
|
|
||||||
"html pipeline", `{{printf "<script>alert(\"XSS\");</script>" | html}}`,
|
|
||||||
"<script>alert("XSS");</script>", nil, true,
|
|
||||||
},
|
|
||||||
{"html", `{{html .PS}}`, "a string", tVal, true},
|
{"html", `{{html .PS}}`, "a string", tVal, true},
|
||||||
{"html typed nil", `{{html .NIL}}`, "<nil>", tVal, true},
|
{"html typed nil", `{{html .NIL}}`, "<nil>", tVal, true},
|
||||||
{"html untyped nil", `{{html .Empty0}}`, "<nil>", tVal, true}, // NOTE: "<no value>" in text/template
|
{"html untyped nil", `{{html .Empty0}}`, "<nil>", tVal, true}, // NOTE: "<no value>" in text/template
|
||||||
|
@ -854,7 +840,7 @@ var delimPairs = []string{
|
||||||
|
|
||||||
func TestDelims(t *testing.T) {
|
func TestDelims(t *testing.T) {
|
||||||
const hello = "Hello, world"
|
const hello = "Hello, world"
|
||||||
value := struct{ Str string }{hello}
|
var value = struct{ Str string }{hello}
|
||||||
for i := 0; i < len(delimPairs); i += 2 {
|
for i := 0; i < len(delimPairs); i += 2 {
|
||||||
text := ".Str"
|
text := ".Str"
|
||||||
left := delimPairs[i+0]
|
left := delimPairs[i+0]
|
||||||
|
@ -877,7 +863,7 @@ func TestDelims(t *testing.T) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("delim %q text %q parse err %s", left, text, err)
|
t.Fatalf("delim %q text %q parse err %s", left, text, err)
|
||||||
}
|
}
|
||||||
b := new(strings.Builder)
|
var b = new(strings.Builder)
|
||||||
err = tmpl.Execute(b, value)
|
err = tmpl.Execute(b, value)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("delim %q exec err %s", left, err)
|
t.Fatalf("delim %q exec err %s", left, err)
|
||||||
|
@ -978,7 +964,7 @@ const treeTemplate = `
|
||||||
`
|
`
|
||||||
|
|
||||||
func TestTree(t *testing.T) {
|
func TestTree(t *testing.T) {
|
||||||
tree := &Tree{
|
var tree = &Tree{
|
||||||
1,
|
1,
|
||||||
&Tree{
|
&Tree{
|
||||||
2, &Tree{
|
2, &Tree{
|
||||||
|
@ -1229,7 +1215,7 @@ var cmpTests = []cmpTest{
|
||||||
|
|
||||||
func TestComparison(t *testing.T) {
|
func TestComparison(t *testing.T) {
|
||||||
b := new(strings.Builder)
|
b := new(strings.Builder)
|
||||||
cmpStruct := struct {
|
var cmpStruct = struct {
|
||||||
Uthree, Ufour uint
|
Uthree, Ufour uint
|
||||||
NegOne, Three int
|
NegOne, Three int
|
||||||
Ptr, NilPtr *int
|
Ptr, NilPtr *int
|
||||||
|
|
|
@ -2,8 +2,8 @@
|
||||||
// Use of this source code is governed by a BSD-style
|
// Use of this source code is governed by a BSD-style
|
||||||
// license that can be found in the LICENSE file.
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
//go:build !windows
|
//go:build go1.13 && !windows
|
||||||
// +build !windows
|
// +build go1.13,!windows
|
||||||
|
|
||||||
package template
|
package template
|
||||||
|
|
||||||
|
|
|
@ -10,6 +10,7 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
htmltemplate "html/template"
|
htmltemplate "html/template"
|
||||||
"reflect"
|
"reflect"
|
||||||
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
"unicode/utf8"
|
"unicode/utf8"
|
||||||
)
|
)
|
||||||
|
@ -145,6 +146,8 @@ func indirectToJSONMarshaler(a any) any {
|
||||||
return v.Interface()
|
return v.Interface()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var scriptTagRe = regexp.MustCompile("(?i)<(/?)script")
|
||||||
|
|
||||||
// jsValEscaper escapes its inputs to a JS Expression (section 11.14) that has
|
// jsValEscaper escapes its inputs to a JS Expression (section 11.14) that has
|
||||||
// neither side-effects nor free variables outside (NaN, Infinity).
|
// neither side-effects nor free variables outside (NaN, Infinity).
|
||||||
func jsValEscaper(args ...any) string {
|
func jsValEscaper(args ...any) string {
|
||||||
|
@ -182,9 +185,9 @@ func jsValEscaper(args ...any) string {
|
||||||
// In particular we:
|
// In particular we:
|
||||||
// * replace "*/" comment end tokens with "* /", which does not
|
// * replace "*/" comment end tokens with "* /", which does not
|
||||||
// terminate the comment
|
// terminate the comment
|
||||||
// * replace "</script" with "\x3C/script", and "<!--" with
|
// * replace "<script" and "</script" with "\x3Cscript" and "\x3C/script"
|
||||||
// "\x3C!--", which prevents confusing script block termination
|
// (case insensitively), and "<!--" with "\x3C!--", which prevents
|
||||||
// semantics
|
// confusing script block termination semantics
|
||||||
//
|
//
|
||||||
// We also put a space before the comment so that if it is flush against
|
// We also put a space before the comment so that if it is flush against
|
||||||
// a division operator it is not turned into a line comment:
|
// a division operator it is not turned into a line comment:
|
||||||
|
@ -193,8 +196,8 @@ func jsValEscaper(args ...any) string {
|
||||||
// x//* error marshaling y:
|
// x//* error marshaling y:
|
||||||
// second line of error message */null
|
// second line of error message */null
|
||||||
errStr := err.Error()
|
errStr := err.Error()
|
||||||
|
errStr = string(scriptTagRe.ReplaceAll([]byte(errStr), []byte(`\x3C${1}script`)))
|
||||||
errStr = strings.ReplaceAll(errStr, "*/", "* /")
|
errStr = strings.ReplaceAll(errStr, "*/", "* /")
|
||||||
errStr = strings.ReplaceAll(errStr, "</script", `\x3C/script`)
|
|
||||||
errStr = strings.ReplaceAll(errStr, "<!--", `\x3C!--`)
|
errStr = strings.ReplaceAll(errStr, "<!--", `\x3C!--`)
|
||||||
return fmt.Sprintf(" /* %s */null ", errStr)
|
return fmt.Sprintf(" /* %s */null ", errStr)
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,8 +2,8 @@
|
||||||
// Use of this source code is governed by a BSD-style
|
// Use of this source code is governed by a BSD-style
|
||||||
// license that can be found in the LICENSE file.
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
//go:build !windows
|
//go:build go1.13 && !windows
|
||||||
// +build !windows
|
// +build go1.13,!windows
|
||||||
|
|
||||||
package template
|
package template
|
||||||
|
|
||||||
|
@ -110,7 +110,7 @@ func TestNextJsCtx(t *testing.T) {
|
||||||
type jsonErrType struct{}
|
type jsonErrType struct{}
|
||||||
|
|
||||||
func (e *jsonErrType) MarshalJSON() ([]byte, error) {
|
func (e *jsonErrType) MarshalJSON() ([]byte, error) {
|
||||||
return nil, errors.New("beep */ boop </script blip <!--")
|
return nil, errors.New("a */ b <script c </script d <!-- e <sCrIpT f </sCrIpT")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestJSValEscaper(t *testing.T) {
|
func TestJSValEscaper(t *testing.T) {
|
||||||
|
@ -163,7 +163,7 @@ func TestJSValEscaper(t *testing.T) {
|
||||||
{"</script", `"\u003c/script"`, false},
|
{"</script", `"\u003c/script"`, false},
|
||||||
{"\U0001D11E", "\"\U0001D11E\"", false}, // or "\uD834\uDD1E"
|
{"\U0001D11E", "\"\U0001D11E\"", false}, // or "\uD834\uDD1E"
|
||||||
{nil, " null ", false},
|
{nil, " null ", false},
|
||||||
{&jsonErrType{}, " /* json: error calling MarshalJSON for type *template.jsonErrType: beep * / boop \\x3C/script blip \\x3C!-- */null ", true},
|
{&jsonErrType{}, " /* json: error calling MarshalJSON for type *template.jsonErrType: a * / b \\x3Cscript c \\x3C/script d \\x3C!-- e \\x3Cscript f \\x3C/script */null ", true},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, test := range tests {
|
for _, test := range tests {
|
||||||
|
@ -221,8 +221,7 @@ func TestJSStrEscaper(t *testing.T) {
|
||||||
{"<!--", `\u003c!--`},
|
{"<!--", `\u003c!--`},
|
||||||
{"-->", `--\u003e`},
|
{"-->", `--\u003e`},
|
||||||
// From https://code.google.com/p/doctype/wiki/ArticleUtf7
|
// From https://code.google.com/p/doctype/wiki/ArticleUtf7
|
||||||
{
|
{"+ADw-script+AD4-alert(1)+ADw-/script+AD4-",
|
||||||
"+ADw-script+AD4-alert(1)+ADw-/script+AD4-",
|
|
||||||
`\u002bADw-script\u002bAD4-alert(1)\u002bADw-\/script\u002bAD4-`,
|
`\u002bADw-script\u002bAD4-alert(1)\u002bADw-\/script\u002bAD4-`,
|
||||||
},
|
},
|
||||||
// Invalid UTF-8 sequence
|
// Invalid UTF-8 sequence
|
||||||
|
|
|
@ -4,8 +4,8 @@
|
||||||
|
|
||||||
// Tests for multiple-template execution, copied from text/template.
|
// Tests for multiple-template execution, copied from text/template.
|
||||||
|
|
||||||
//go:build !windows
|
//go:build go1.13 && !windows
|
||||||
// +build !windows
|
// +build go1.13,!windows
|
||||||
|
|
||||||
package template
|
package template
|
||||||
|
|
||||||
|
@ -268,7 +268,7 @@ func TestIssue19294(t *testing.T) {
|
||||||
// by the contents of "stylesheet", but if the internal map associating
|
// by the contents of "stylesheet", but if the internal map associating
|
||||||
// names with templates is built in the wrong order, the empty block
|
// names with templates is built in the wrong order, the empty block
|
||||||
// looks non-empty and this doesn't happen.
|
// looks non-empty and this doesn't happen.
|
||||||
inlined := map[string]string{
|
var inlined = map[string]string{
|
||||||
"stylesheet": `{{define "stylesheet"}}stylesheet{{end}}`,
|
"stylesheet": `{{define "stylesheet"}}stylesheet{{end}}`,
|
||||||
"xhtml": `{{block "stylesheet" .}}{{end}}`,
|
"xhtml": `{{block "stylesheet" .}}{{end}}`,
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,6 +2,9 @@
|
||||||
// Use of this source code is governed by a BSD-style
|
// Use of this source code is governed by a BSD-style
|
||||||
// license that can be found in the LICENSE file.
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
//go:build go1.13
|
||||||
|
// +build go1.13
|
||||||
|
|
||||||
package template_test
|
package template_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
@ -15,6 +18,7 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestTemplateClone(t *testing.T) {
|
func TestTemplateClone(t *testing.T) {
|
||||||
|
|
||||||
orig := New("name")
|
orig := New("name")
|
||||||
clone, err := orig.Clone()
|
clone, err := orig.Clone()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -2,8 +2,8 @@
|
||||||
// Use of this source code is governed by a BSD-style
|
// Use of this source code is governed by a BSD-style
|
||||||
// license that can be found in the LICENSE file.
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
//go:build !windows
|
//go:build go1.13 && !windows
|
||||||
// +build !windows
|
// +build go1.13,!windows
|
||||||
|
|
||||||
package template
|
package template
|
||||||
|
|
||||||
|
@ -43,6 +43,7 @@ func TestFindEndTag(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func BenchmarkTemplateSpecialTags(b *testing.B) {
|
func BenchmarkTemplateSpecialTags(b *testing.B) {
|
||||||
|
|
||||||
r := struct {
|
r := struct {
|
||||||
Name, Gift string
|
Name, Gift string
|
||||||
}{"Aunt Mildred", "bone china tea set"}
|
}{"Aunt Mildred", "bone china tea set"}
|
||||||
|
|
|
@ -2,8 +2,8 @@
|
||||||
// Use of this source code is governed by a BSD-style
|
// Use of this source code is governed by a BSD-style
|
||||||
// license that can be found in the LICENSE file.
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
//go:build !windows
|
//go:build go1.13 && !windows
|
||||||
// +build !windows
|
// +build go1.13,!windows
|
||||||
|
|
||||||
package template
|
package template
|
||||||
|
|
||||||
|
|
|
@ -31,20 +31,17 @@ import (
|
||||||
// If exec is not supported, testenv.SyscallIsNotSupported will return true
|
// If exec is not supported, testenv.SyscallIsNotSupported will return true
|
||||||
// for the resulting error.
|
// for the resulting error.
|
||||||
func MustHaveExec(t testing.TB) {
|
func MustHaveExec(t testing.TB) {
|
||||||
tryExecOnce.Do(func() {
|
if err := tryExec(); err != nil {
|
||||||
tryExecErr = tryExec()
|
msg := fmt.Sprintf("cannot exec subprocess on %s/%s: %v", runtime.GOOS, runtime.GOARCH, err)
|
||||||
})
|
if t == nil {
|
||||||
if tryExecErr != nil {
|
panic(msg)
|
||||||
t.Skipf("skipping test: cannot exec subprocess on %s/%s: %v", runtime.GOOS, runtime.GOARCH, tryExecErr)
|
}
|
||||||
|
t.Helper()
|
||||||
|
t.Skip("skipping test:", msg)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var tryExec = sync.OnceValue(func() error {
|
||||||
tryExecOnce sync.Once
|
|
||||||
tryExecErr error
|
|
||||||
)
|
|
||||||
|
|
||||||
func tryExec() error {
|
|
||||||
switch runtime.GOOS {
|
switch runtime.GOOS {
|
||||||
case "wasip1", "js", "ios":
|
case "wasip1", "js", "ios":
|
||||||
default:
|
default:
|
||||||
|
@ -70,15 +67,37 @@ func tryExec() error {
|
||||||
|
|
||||||
// We know that this is a test executable. We should be able to run it with a
|
// We know that this is a test executable. We should be able to run it with a
|
||||||
// no-op flag to check for overall exec support.
|
// no-op flag to check for overall exec support.
|
||||||
exe, err := os.Executable()
|
exe, err := exePath()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("can't probe for exec support: %w", err)
|
return fmt.Errorf("can't probe for exec support: %w", err)
|
||||||
}
|
}
|
||||||
cmd := exec.Command(exe, "-test.list=^$")
|
cmd := exec.Command(exe, "-test.list=^$")
|
||||||
cmd.Env = origEnv
|
cmd.Env = origEnv
|
||||||
return cmd.Run()
|
return cmd.Run()
|
||||||
|
})
|
||||||
|
|
||||||
|
// Executable is a wrapper around [MustHaveExec] and [os.Executable].
|
||||||
|
// It returns the path name for the executable that started the current process,
|
||||||
|
// or skips the test if the current system can't start new processes,
|
||||||
|
// or fails the test if the path can not be obtained.
|
||||||
|
func Executable(t testing.TB) string {
|
||||||
|
MustHaveExec(t)
|
||||||
|
|
||||||
|
exe, err := exePath()
|
||||||
|
if err != nil {
|
||||||
|
msg := fmt.Sprintf("os.Executable error: %v", err)
|
||||||
|
if t == nil {
|
||||||
|
panic(msg)
|
||||||
|
}
|
||||||
|
t.Fatal(msg)
|
||||||
|
}
|
||||||
|
return exe
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var exePath = sync.OnceValues(func() (string, error) {
|
||||||
|
return os.Executable()
|
||||||
|
})
|
||||||
|
|
||||||
var execPaths sync.Map // path -> error
|
var execPaths sync.Map // path -> error
|
||||||
|
|
||||||
// MustHaveExecPath checks that the current system can start the named executable
|
// MustHaveExecPath checks that the current system can start the named executable
|
||||||
|
@ -93,6 +112,7 @@ func MustHaveExecPath(t testing.TB, path string) {
|
||||||
err, _ = execPaths.LoadOrStore(path, err)
|
err, _ = execPaths.LoadOrStore(path, err)
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
t.Helper()
|
||||||
t.Skipf("skipping test: %s: %s", path, err)
|
t.Skipf("skipping test: %s: %s", path, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,7 +12,6 @@ package testenv
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"errors"
|
|
||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
|
@ -43,15 +42,22 @@ func Builder() string {
|
||||||
|
|
||||||
// HasGoBuild reports whether the current system can build programs with “go build”
|
// HasGoBuild reports whether the current system can build programs with “go build”
|
||||||
// and then run them with os.StartProcess or exec.Command.
|
// and then run them with os.StartProcess or exec.Command.
|
||||||
// Modified by Hugo (not needed)
|
|
||||||
func HasGoBuild() bool {
|
func HasGoBuild() bool {
|
||||||
return false
|
if os.Getenv("GO_GCFLAGS") != "" {
|
||||||
|
// It's too much work to require every caller of the go command
|
||||||
|
// to pass along "-gcflags="+os.Getenv("GO_GCFLAGS").
|
||||||
|
// For now, if $GO_GCFLAGS is set, report that we simply can't
|
||||||
|
// run go build.
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return tryGoBuild() == nil
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var tryGoBuild = sync.OnceValue(func() error {
|
||||||
goBuildOnce sync.Once
|
// Removed by Hugo, not used.
|
||||||
goBuildErr error
|
return nil
|
||||||
)
|
})
|
||||||
|
|
||||||
// MustHaveGoBuild checks that the current system can build programs with “go build”
|
// MustHaveGoBuild checks that the current system can build programs with “go build”
|
||||||
// and then run them with os.StartProcess or exec.Command.
|
// and then run them with os.StartProcess or exec.Command.
|
||||||
|
@ -63,7 +69,7 @@ func MustHaveGoBuild(t testing.TB) {
|
||||||
}
|
}
|
||||||
if !HasGoBuild() {
|
if !HasGoBuild() {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
t.Skipf("skipping test: 'go build' unavailable: %v", goBuildErr)
|
t.Skipf("skipping test: 'go build' unavailable: %v", tryGoBuild())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -77,6 +83,7 @@ func HasGoRun() bool {
|
||||||
// If not, MustHaveGoRun calls t.Skip with an explanation.
|
// If not, MustHaveGoRun calls t.Skip with an explanation.
|
||||||
func MustHaveGoRun(t testing.TB) {
|
func MustHaveGoRun(t testing.TB) {
|
||||||
if !HasGoRun() {
|
if !HasGoRun() {
|
||||||
|
t.Helper()
|
||||||
t.Skipf("skipping test: 'go run' not available on %s/%s", runtime.GOOS, runtime.GOARCH)
|
t.Skipf("skipping test: 'go run' not available on %s/%s", runtime.GOOS, runtime.GOARCH)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -96,6 +103,7 @@ func HasParallelism() bool {
|
||||||
// threads in parallel. If not, MustHaveParallelism calls t.Skip with an explanation.
|
// threads in parallel. If not, MustHaveParallelism calls t.Skip with an explanation.
|
||||||
func MustHaveParallelism(t testing.TB) {
|
func MustHaveParallelism(t testing.TB) {
|
||||||
if !HasParallelism() {
|
if !HasParallelism() {
|
||||||
|
t.Helper()
|
||||||
t.Skipf("skipping test: no parallelism available on %s/%s", runtime.GOOS, runtime.GOARCH)
|
t.Skipf("skipping test: no parallelism available on %s/%s", runtime.GOOS, runtime.GOARCH)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -119,82 +127,67 @@ func GoToolPath(t testing.TB) string {
|
||||||
return path
|
return path
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var findGOROOT = sync.OnceValues(func() (path string, err error) {
|
||||||
gorootOnce sync.Once
|
if path := runtime.GOROOT(); path != "" {
|
||||||
gorootPath string
|
// If runtime.GOROOT() is non-empty, assume that it is valid.
|
||||||
gorootErr error
|
//
|
||||||
)
|
// (It might not be: for example, the user may have explicitly set GOROOT
|
||||||
|
// to the wrong directory. But this case is
|
||||||
|
// rare, and if that happens the user can fix what they broke.)
|
||||||
|
return path, nil
|
||||||
|
}
|
||||||
|
|
||||||
func findGOROOT() (string, error) {
|
// runtime.GOROOT doesn't know where GOROOT is (perhaps because the test
|
||||||
gorootOnce.Do(func() {
|
// binary was built with -trimpath).
|
||||||
gorootPath = runtime.GOROOT()
|
//
|
||||||
if gorootPath != "" {
|
// Since this is internal/testenv, we can cheat and assume that the caller
|
||||||
// If runtime.GOROOT() is non-empty, assume that it is valid.
|
// is a test of some package in a subdirectory of GOROOT/src. ('go test'
|
||||||
//
|
// runs the test in the directory containing the packaged under test.) That
|
||||||
// (It might not be: for example, the user may have explicitly set GOROOT
|
// means that if we start walking up the tree, we should eventually find
|
||||||
// to the wrong directory. But this case is
|
// GOROOT/src/go.mod, and we can report the parent directory of that.
|
||||||
// rare, and if that happens the user can fix what they broke.)
|
//
|
||||||
return
|
// Notably, this works even if we can't run 'go env GOROOT' as a
|
||||||
|
// subprocess.
|
||||||
|
|
||||||
|
cwd, err := os.Getwd()
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("finding GOROOT: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
dir := cwd
|
||||||
|
for {
|
||||||
|
parent := filepath.Dir(dir)
|
||||||
|
if parent == dir {
|
||||||
|
// dir is either "." or only a volume name.
|
||||||
|
return "", fmt.Errorf("failed to locate GOROOT/src in any parent directory")
|
||||||
}
|
}
|
||||||
|
|
||||||
// runtime.GOROOT doesn't know where GOROOT is (perhaps because the test
|
if base := filepath.Base(dir); base != "src" {
|
||||||
// binary was built with -trimpath).
|
dir = parent
|
||||||
//
|
continue // dir cannot be GOROOT/src if it doesn't end in "src".
|
||||||
// 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'
|
|
||||||
// runs the test in the directory containing the packaged under test.) That
|
|
||||||
// means that if we start walking up the tree, we should eventually find
|
|
||||||
// GOROOT/src/go.mod, and we can report the parent directory of that.
|
|
||||||
//
|
|
||||||
// Notably, this works even if we can't run 'go env GOROOT' as a
|
|
||||||
// subprocess.
|
|
||||||
|
|
||||||
cwd, err := os.Getwd()
|
b, err := os.ReadFile(filepath.Join(dir, "go.mod"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
gorootErr = fmt.Errorf("finding GOROOT: %w", err)
|
if os.IsNotExist(err) {
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
dir := cwd
|
|
||||||
for {
|
|
||||||
parent := filepath.Dir(dir)
|
|
||||||
if parent == dir {
|
|
||||||
// dir is either "." or only a volume name.
|
|
||||||
gorootErr = fmt.Errorf("failed to locate GOROOT/src in any parent directory")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if base := filepath.Base(dir); base != "src" {
|
|
||||||
dir = parent
|
dir = parent
|
||||||
continue // dir cannot be GOROOT/src if it doesn't end in "src".
|
continue
|
||||||
}
|
}
|
||||||
|
return "", fmt.Errorf("finding GOROOT: %w", err)
|
||||||
|
}
|
||||||
|
goMod := string(b)
|
||||||
|
|
||||||
b, err := os.ReadFile(filepath.Join(dir, "go.mod"))
|
for goMod != "" {
|
||||||
if err != nil {
|
var line string
|
||||||
if os.IsNotExist(err) {
|
line, goMod, _ = strings.Cut(goMod, "\n")
|
||||||
dir = parent
|
fields := strings.Fields(line)
|
||||||
continue
|
if len(fields) >= 2 && fields[0] == "module" && fields[1] == "std" {
|
||||||
}
|
// Found "module std", which is the module declaration in GOROOT/src!
|
||||||
gorootErr = fmt.Errorf("finding GOROOT: %w", err)
|
return parent, nil
|
||||||
return
|
|
||||||
}
|
|
||||||
goMod := string(b)
|
|
||||||
|
|
||||||
for goMod != "" {
|
|
||||||
var line string
|
|
||||||
line, goMod, _ = strings.Cut(goMod, "\n")
|
|
||||||
fields := strings.Fields(line)
|
|
||||||
if len(fields) >= 2 && fields[0] == "module" && fields[1] == "std" {
|
|
||||||
// Found "module std", which is the module declaration in GOROOT/src!
|
|
||||||
gorootPath = parent
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
}
|
||||||
|
})
|
||||||
return gorootPath, gorootErr
|
|
||||||
}
|
|
||||||
|
|
||||||
// GOROOT reports the path to the directory containing the root of the Go
|
// GOROOT reports the path to the directory containing the root of the Go
|
||||||
// project source tree. This is normally equivalent to runtime.GOROOT, but
|
// project source tree. This is normally equivalent to runtime.GOROOT, but
|
||||||
|
@ -217,28 +210,22 @@ func GOROOT(t testing.TB) string {
|
||||||
|
|
||||||
// GoTool reports the path to the Go tool.
|
// GoTool reports the path to the Go tool.
|
||||||
func GoTool() (string, error) {
|
func GoTool() (string, error) {
|
||||||
if !HasGoBuild() {
|
// Removed by Hugo, not used.
|
||||||
return "", errors.New("platform cannot run go tool")
|
return "", nil
|
||||||
}
|
|
||||||
goToolOnce.Do(func() {
|
|
||||||
goToolPath, goToolErr = exec.LookPath("go")
|
|
||||||
})
|
|
||||||
return goToolPath, goToolErr
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var goTool = sync.OnceValues(func() (string, error) {
|
||||||
goToolOnce sync.Once
|
return exec.LookPath("go")
|
||||||
goToolPath string
|
})
|
||||||
goToolErr error
|
|
||||||
)
|
|
||||||
|
|
||||||
// HasSrc reports whether the entire source tree is available under GOROOT.
|
// MustHaveSource checks that the entire source tree is available under GOROOT.
|
||||||
func HasSrc() bool {
|
// If not, it calls t.Skip with an explanation.
|
||||||
|
func MustHaveSource(t testing.TB) {
|
||||||
switch runtime.GOOS {
|
switch runtime.GOOS {
|
||||||
case "ios":
|
case "ios":
|
||||||
return false
|
t.Helper()
|
||||||
|
t.Skip("skipping test: no source tree on " + runtime.GOOS)
|
||||||
}
|
}
|
||||||
return true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// HasExternalNetwork reports whether the current system can use
|
// HasExternalNetwork reports whether the current system can use
|
||||||
|
@ -263,41 +250,39 @@ func MustHaveExternalNetwork(t testing.TB) {
|
||||||
|
|
||||||
// HasCGO reports whether the current system can use cgo.
|
// HasCGO reports whether the current system can use cgo.
|
||||||
func HasCGO() bool {
|
func HasCGO() bool {
|
||||||
hasCgoOnce.Do(func() {
|
return hasCgo()
|
||||||
goTool, err := GoTool()
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
cmd := exec.Command(goTool, "env", "CGO_ENABLED")
|
|
||||||
cmd.Env = origEnv
|
|
||||||
out, err := cmd.Output()
|
|
||||||
if err != nil {
|
|
||||||
panic(fmt.Sprintf("%v: %v", cmd, out))
|
|
||||||
}
|
|
||||||
hasCgo, err = strconv.ParseBool(string(bytes.TrimSpace(out)))
|
|
||||||
if err != nil {
|
|
||||||
panic(fmt.Sprintf("%v: non-boolean output %q", cmd, out))
|
|
||||||
}
|
|
||||||
})
|
|
||||||
return hasCgo
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var hasCgo = sync.OnceValue(func() bool {
|
||||||
hasCgoOnce sync.Once
|
goTool, err := goTool()
|
||||||
hasCgo bool
|
if err != nil {
|
||||||
)
|
return false
|
||||||
|
}
|
||||||
|
cmd := exec.Command(goTool, "env", "CGO_ENABLED")
|
||||||
|
cmd.Env = origEnv
|
||||||
|
out, err := cmd.Output()
|
||||||
|
if err != nil {
|
||||||
|
panic(fmt.Sprintf("%v: %v", cmd, out))
|
||||||
|
}
|
||||||
|
ok, err := strconv.ParseBool(string(bytes.TrimSpace(out)))
|
||||||
|
if err != nil {
|
||||||
|
panic(fmt.Sprintf("%v: non-boolean output %q", cmd, out))
|
||||||
|
}
|
||||||
|
return ok
|
||||||
|
})
|
||||||
|
|
||||||
// MustHaveCGO calls t.Skip if cgo is not available.
|
// MustHaveCGO calls t.Skip if cgo is not available.
|
||||||
func MustHaveCGO(t testing.TB) {
|
func MustHaveCGO(t testing.TB) {
|
||||||
if !HasCGO() {
|
if !HasCGO() {
|
||||||
|
t.Helper()
|
||||||
t.Skipf("skipping test: no cgo")
|
t.Skipf("skipping test: no cgo")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// CanInternalLink reports whether the current system can link programs with
|
// CanInternalLink reports whether the current system can link programs with
|
||||||
// internal linking.
|
// internal linking.
|
||||||
// Modified by Hugo (not needed)
|
|
||||||
func CanInternalLink(withCgo bool) bool {
|
func CanInternalLink(withCgo bool) bool {
|
||||||
|
// Removed by Hugo, not used.
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -306,6 +291,7 @@ func CanInternalLink(withCgo bool) bool {
|
||||||
// If not, MustInternalLink calls t.Skip with an explanation.
|
// If not, MustInternalLink calls t.Skip with an explanation.
|
||||||
func MustInternalLink(t testing.TB, withCgo bool) {
|
func MustInternalLink(t testing.TB, withCgo bool) {
|
||||||
if !CanInternalLink(withCgo) {
|
if !CanInternalLink(withCgo) {
|
||||||
|
t.Helper()
|
||||||
if withCgo && CanInternalLink(false) {
|
if withCgo && CanInternalLink(false) {
|
||||||
t.Skipf("skipping test: internal linking on %s/%s is not supported with cgo", runtime.GOOS, runtime.GOARCH)
|
t.Skipf("skipping test: internal linking on %s/%s is not supported with cgo", runtime.GOOS, runtime.GOARCH)
|
||||||
}
|
}
|
||||||
|
@ -316,15 +302,15 @@ func MustInternalLink(t testing.TB, withCgo bool) {
|
||||||
// MustInternalLinkPIE checks whether the current system can link PIE binary using
|
// MustInternalLinkPIE checks whether the current system can link PIE binary using
|
||||||
// internal linking.
|
// internal linking.
|
||||||
// If not, MustInternalLinkPIE calls t.Skip with an explanation.
|
// If not, MustInternalLinkPIE calls t.Skip with an explanation.
|
||||||
// Modified by Hugo (not needed)
|
|
||||||
func MustInternalLinkPIE(t testing.TB) {
|
func MustInternalLinkPIE(t testing.TB) {
|
||||||
|
// Removed by Hugo, not used.
|
||||||
}
|
}
|
||||||
|
|
||||||
// MustHaveBuildMode reports whether the current system can build programs in
|
// MustHaveBuildMode reports whether the current system can build programs in
|
||||||
// the given build mode.
|
// the given build mode.
|
||||||
// If not, MustHaveBuildMode calls t.Skip with an explanation.
|
// If not, MustHaveBuildMode calls t.Skip with an explanation.
|
||||||
// Modified by Hugo (not needed)
|
|
||||||
func MustHaveBuildMode(t testing.TB, buildmode string) {
|
func MustHaveBuildMode(t testing.TB, buildmode string) {
|
||||||
|
// Removed by Hugo, not used.
|
||||||
}
|
}
|
||||||
|
|
||||||
// HasSymlink reports whether the current system can use os.Symlink.
|
// HasSymlink reports whether the current system can use os.Symlink.
|
||||||
|
@ -338,6 +324,7 @@ func HasSymlink() bool {
|
||||||
func MustHaveSymlink(t testing.TB) {
|
func MustHaveSymlink(t testing.TB) {
|
||||||
ok, reason := hasSymlink()
|
ok, reason := hasSymlink()
|
||||||
if !ok {
|
if !ok {
|
||||||
|
t.Helper()
|
||||||
t.Skipf("skipping test: cannot make symlinks on %s/%s: %s", runtime.GOOS, runtime.GOARCH, reason)
|
t.Skipf("skipping test: cannot make symlinks on %s/%s: %s", runtime.GOOS, runtime.GOARCH, reason)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -354,6 +341,7 @@ func HasLink() bool {
|
||||||
// If not, MustHaveLink calls t.Skip with an explanation.
|
// If not, MustHaveLink calls t.Skip with an explanation.
|
||||||
func MustHaveLink(t testing.TB) {
|
func MustHaveLink(t testing.TB) {
|
||||||
if !HasLink() {
|
if !HasLink() {
|
||||||
|
t.Helper()
|
||||||
t.Skipf("skipping test: hardlinks are not supported on %s/%s", runtime.GOOS, runtime.GOARCH)
|
t.Skipf("skipping test: hardlinks are not supported on %s/%s", runtime.GOOS, runtime.GOARCH)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -361,15 +349,15 @@ func MustHaveLink(t testing.TB) {
|
||||||
var flaky = flag.Bool("flaky", false, "run known-flaky tests too")
|
var flaky = flag.Bool("flaky", false, "run known-flaky tests too")
|
||||||
|
|
||||||
func SkipFlaky(t testing.TB, issue int) {
|
func SkipFlaky(t testing.TB, issue int) {
|
||||||
t.Helper()
|
|
||||||
if !*flaky {
|
if !*flaky {
|
||||||
|
t.Helper()
|
||||||
t.Skipf("skipping known flaky test without the -flaky flag; see golang.org/issue/%d", issue)
|
t.Skipf("skipping known flaky test without the -flaky flag; see golang.org/issue/%d", issue)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func SkipFlakyNet(t testing.TB) {
|
func SkipFlakyNet(t testing.TB) {
|
||||||
t.Helper()
|
|
||||||
if v, _ := strconv.ParseBool(os.Getenv("GO_BUILDER_FLAKY_NET")); v {
|
if v, _ := strconv.ParseBool(os.Getenv("GO_BUILDER_FLAKY_NET")); v {
|
||||||
|
t.Helper()
|
||||||
t.Skip("skipping test on builder known to have frequent network failures")
|
t.Skip("skipping test on builder known to have frequent network failures")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -455,6 +443,6 @@ func SyscallIsNotSupported(err error) bool {
|
||||||
// ParallelOn64Bit calls t.Parallel() unless there is a case that cannot be parallel.
|
// 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
|
// 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.
|
// 32-bit machines, typically because the test uses lots of memory.
|
||||||
// Disabled by Hugo.
|
|
||||||
func ParallelOn64Bit(t *testing.T) {
|
func ParallelOn64Bit(t *testing.T) {
|
||||||
|
// Removed by Hugo, not used.
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,9 +11,10 @@ import (
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"runtime"
|
"runtime"
|
||||||
|
"sync"
|
||||||
)
|
)
|
||||||
|
|
||||||
func hasSymlink() (ok bool, reason string) {
|
var hasSymlink = sync.OnceValues(func() (ok bool, reason string) {
|
||||||
switch runtime.GOOS {
|
switch runtime.GOOS {
|
||||||
case "plan9":
|
case "plan9":
|
||||||
return false, ""
|
return false, ""
|
||||||
|
@ -43,4 +44,4 @@ func hasSymlink() (ok bool, reason string) {
|
||||||
}
|
}
|
||||||
|
|
||||||
return true, ""
|
return true, ""
|
||||||
}
|
})
|
||||||
|
|
|
@ -15,6 +15,7 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestGoToolLocation(t *testing.T) {
|
func TestGoToolLocation(t *testing.T) {
|
||||||
|
t.Skip("This test is not relevant for Hugo")
|
||||||
testenv.MustHaveGoBuild(t)
|
testenv.MustHaveGoBuild(t)
|
||||||
|
|
||||||
var exeSuffix string
|
var exeSuffix string
|
||||||
|
@ -54,8 +55,83 @@ func TestGoToolLocation(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Modified by Hugo (not needed)
|
|
||||||
func TestHasGoBuild(t *testing.T) {
|
func TestHasGoBuild(t *testing.T) {
|
||||||
|
if !testenv.HasGoBuild() {
|
||||||
|
switch runtime.GOOS {
|
||||||
|
case "js", "wasip1":
|
||||||
|
// No exec syscall, so these shouldn't be able to 'go build'.
|
||||||
|
t.Logf("HasGoBuild is false on %s", runtime.GOOS)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
b := testenv.Builder()
|
||||||
|
if b == "" {
|
||||||
|
// We shouldn't make assumptions about what kind of sandbox or build
|
||||||
|
// environment external Go users may be running in.
|
||||||
|
t.Skipf("skipping: 'go build' unavailable")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Since we control the Go builders, we know which ones ought
|
||||||
|
// to be able to run 'go build'. Check that they can.
|
||||||
|
//
|
||||||
|
// (Note that we don't verify that any builders *can't* run 'go build'.
|
||||||
|
// If a builder starts running 'go build' tests when it shouldn't,
|
||||||
|
// we will presumably find out about it when those tests fail.)
|
||||||
|
switch runtime.GOOS {
|
||||||
|
case "ios":
|
||||||
|
if isCorelliumBuilder(b) {
|
||||||
|
// The corellium environment is self-hosting, so it should be able
|
||||||
|
// to build even though real "ios" devices can't exec.
|
||||||
|
} else {
|
||||||
|
// The usual iOS sandbox does not allow the app to start another
|
||||||
|
// process. If we add builders on stock iOS devices, they presumably
|
||||||
|
// will not be able to exec, so we may as well allow that now.
|
||||||
|
t.Logf("HasGoBuild is false on %s", b)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
case "android":
|
||||||
|
panic("Removed by Hugo, should not be used")
|
||||||
|
}
|
||||||
|
|
||||||
|
if strings.Contains(b, "-noopt") {
|
||||||
|
// The -noopt builder sets GO_GCFLAGS, which causes tests of 'go build' to
|
||||||
|
// be skipped.
|
||||||
|
t.Logf("HasGoBuild is false on %s", b)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Fatalf("HasGoBuild unexpectedly false on %s", b)
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Logf("HasGoBuild is true; checking consistency with other functions")
|
||||||
|
|
||||||
|
hasExec := false
|
||||||
|
hasExecGo := false
|
||||||
|
t.Run("MustHaveExec", func(t *testing.T) {
|
||||||
|
testenv.MustHaveExec(t)
|
||||||
|
hasExec = true
|
||||||
|
})
|
||||||
|
t.Run("MustHaveExecPath", func(t *testing.T) {
|
||||||
|
testenv.MustHaveExecPath(t, "go")
|
||||||
|
hasExecGo = true
|
||||||
|
})
|
||||||
|
if !hasExec {
|
||||||
|
t.Errorf(`MustHaveExec(t) skipped unexpectedly`)
|
||||||
|
}
|
||||||
|
if !hasExecGo {
|
||||||
|
t.Errorf(`MustHaveExecPath(t, "go") skipped unexpectedly`)
|
||||||
|
}
|
||||||
|
|
||||||
|
dir := t.TempDir()
|
||||||
|
mainGo := filepath.Join(dir, "main.go")
|
||||||
|
if err := os.WriteFile(mainGo, []byte("package main\nfunc main() {}\n"), 0o644); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
cmd := testenv.Command(t, "go", "build", "-o", os.DevNull, mainGo)
|
||||||
|
out, err := cmd.CombinedOutput()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("%v: %v\n%s", cmd, err, out)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestMustHaveExec(t *testing.T) {
|
func TestMustHaveExec(t *testing.T) {
|
||||||
|
|
|
@ -5,16 +5,14 @@
|
||||||
package testenv
|
package testenv
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"sync"
|
"sync"
|
||||||
"syscall"
|
"syscall"
|
||||||
)
|
)
|
||||||
|
|
||||||
var symlinkOnce sync.Once
|
var hasSymlink = sync.OnceValues(func() (bool, string) {
|
||||||
var winSymlinkErr error
|
|
||||||
|
|
||||||
func initWinHasSymlink() {
|
|
||||||
tmpdir, err := os.MkdirTemp("", "symtest")
|
tmpdir, err := os.MkdirTemp("", "symtest")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic("failed to create temp directory: " + err.Error())
|
panic("failed to create temp directory: " + err.Error())
|
||||||
|
@ -22,26 +20,13 @@ func initWinHasSymlink() {
|
||||||
defer os.RemoveAll(tmpdir)
|
defer os.RemoveAll(tmpdir)
|
||||||
|
|
||||||
err = os.Symlink("target", filepath.Join(tmpdir, "symlink"))
|
err = os.Symlink("target", filepath.Join(tmpdir, "symlink"))
|
||||||
if err != nil {
|
switch {
|
||||||
err = err.(*os.LinkError).Err
|
case err == nil:
|
||||||
switch err {
|
|
||||||
case syscall.EWINDOWS, syscall.ERROR_PRIVILEGE_NOT_HELD:
|
|
||||||
winSymlinkErr = err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func hasSymlink() (ok bool, reason string) {
|
|
||||||
symlinkOnce.Do(initWinHasSymlink)
|
|
||||||
|
|
||||||
switch winSymlinkErr {
|
|
||||||
case nil:
|
|
||||||
return true, ""
|
return true, ""
|
||||||
case syscall.EWINDOWS:
|
case errors.Is(err, syscall.EWINDOWS):
|
||||||
return false, ": symlinks are not supported on your version of Windows"
|
return false, ": symlinks are not supported on your version of Windows"
|
||||||
case syscall.ERROR_PRIVILEGE_NOT_HELD:
|
case errors.Is(err, syscall.ERROR_PRIVILEGE_NOT_HELD):
|
||||||
return false, ": you don't have enough privileges to create symlinks"
|
return false, ": you don't have enough privileges to create symlinks"
|
||||||
}
|
}
|
||||||
|
|
||||||
return false, ""
|
return false, ""
|
||||||
}
|
})
|
||||||
|
|
|
@ -98,7 +98,8 @@ data, defined in detail in the corresponding sections that follow.
|
||||||
{{if pipeline}} T1 {{else}}{{if pipeline}} T0 {{end}}{{end}}
|
{{if pipeline}} T1 {{else}}{{if pipeline}} T0 {{end}}{{end}}
|
||||||
|
|
||||||
{{range pipeline}} T1 {{end}}
|
{{range pipeline}} T1 {{end}}
|
||||||
The value of the pipeline must be an array, slice, map, or channel.
|
The value of the pipeline must be an array, slice, map, iter.Seq,
|
||||||
|
iter.Seq2, integer or channel.
|
||||||
If the value of the pipeline has length zero, nothing is output;
|
If the value of the pipeline has length zero, nothing is output;
|
||||||
otherwise, dot is set to the successive elements of the array,
|
otherwise, dot is set to the successive elements of the array,
|
||||||
slice, or map and T1 is executed. If the value is a map and the
|
slice, or map and T1 is executed. If the value is a map and the
|
||||||
|
@ -106,7 +107,8 @@ data, defined in detail in the corresponding sections that follow.
|
||||||
visited in sorted key order.
|
visited in sorted key order.
|
||||||
|
|
||||||
{{range pipeline}} T1 {{else}} T0 {{end}}
|
{{range pipeline}} T1 {{else}} T0 {{end}}
|
||||||
The value of the pipeline must be an array, slice, map, or channel.
|
The value of the pipeline must be an array, slice, map, iter.Seq,
|
||||||
|
iter.Seq2, integer or channel.
|
||||||
If the value of the pipeline has length zero, dot is unaffected and
|
If the value of the pipeline has length zero, dot is unaffected and
|
||||||
T0 is executed; otherwise, dot is set to the successive elements
|
T0 is executed; otherwise, dot is set to the successive elements
|
||||||
of the array, slice, or map and T1 is executed.
|
of the array, slice, or map and T1 is executed.
|
||||||
|
@ -162,37 +164,55 @@ An argument is a simple value, denoted by one of the following.
|
||||||
the host machine's ints are 32 or 64 bits.
|
the host machine's ints are 32 or 64 bits.
|
||||||
- The keyword nil, representing an untyped Go nil.
|
- The keyword nil, representing an untyped Go nil.
|
||||||
- The character '.' (period):
|
- The character '.' (period):
|
||||||
|
|
||||||
.
|
.
|
||||||
|
|
||||||
The result is the value of dot.
|
The result is the value of dot.
|
||||||
- A variable name, which is a (possibly empty) alphanumeric string
|
- A variable name, which is a (possibly empty) alphanumeric string
|
||||||
preceded by a dollar sign, such as
|
preceded by a dollar sign, such as
|
||||||
|
|
||||||
$piOver2
|
$piOver2
|
||||||
|
|
||||||
or
|
or
|
||||||
|
|
||||||
$
|
$
|
||||||
|
|
||||||
The result is the value of the variable.
|
The result is the value of the variable.
|
||||||
Variables are described below.
|
Variables are described below.
|
||||||
- The name of a field of the data, which must be a struct, preceded
|
- The name of a field of the data, which must be a struct, preceded
|
||||||
by a period, such as
|
by a period, such as
|
||||||
|
|
||||||
.Field
|
.Field
|
||||||
|
|
||||||
The result is the value of the field. Field invocations may be
|
The result is the value of the field. Field invocations may be
|
||||||
chained:
|
chained:
|
||||||
|
|
||||||
.Field1.Field2
|
.Field1.Field2
|
||||||
|
|
||||||
Fields can also be evaluated on variables, including chaining:
|
Fields can also be evaluated on variables, including chaining:
|
||||||
|
|
||||||
$x.Field1.Field2
|
$x.Field1.Field2
|
||||||
- The name of a key of the data, which must be a map, preceded
|
- The name of a key of the data, which must be a map, preceded
|
||||||
by a period, such as
|
by a period, such as
|
||||||
|
|
||||||
.Key
|
.Key
|
||||||
|
|
||||||
The result is the map element value indexed by the key.
|
The result is the map element value indexed by the key.
|
||||||
Key invocations may be chained and combined with fields to any
|
Key invocations may be chained and combined with fields to any
|
||||||
depth:
|
depth:
|
||||||
|
|
||||||
.Field1.Key1.Field2.Key2
|
.Field1.Key1.Field2.Key2
|
||||||
|
|
||||||
Although the key must be an alphanumeric identifier, unlike with
|
Although the key must be an alphanumeric identifier, unlike with
|
||||||
field names they do not need to start with an upper case letter.
|
field names they do not need to start with an upper case letter.
|
||||||
Keys can also be evaluated on variables, including chaining:
|
Keys can also be evaluated on variables, including chaining:
|
||||||
|
|
||||||
$x.key1.key2
|
$x.key1.key2
|
||||||
- The name of a niladic method of the data, preceded by a period,
|
- The name of a niladic method of the data, preceded by a period,
|
||||||
such as
|
such as
|
||||||
|
|
||||||
.Method
|
.Method
|
||||||
|
|
||||||
The result is the value of invoking the method with dot as the
|
The result is the value of invoking the method with dot as the
|
||||||
receiver, dot.Method(). Such a method must have one return value (of
|
receiver, dot.Method(). Such a method must have one return value (of
|
||||||
any type) or two return values, the second of which is an error.
|
any type) or two return values, the second of which is an error.
|
||||||
|
@ -200,16 +220,22 @@ An argument is a simple value, denoted by one of the following.
|
||||||
and an error is returned to the caller as the value of Execute.
|
and an error is returned to the caller as the value of Execute.
|
||||||
Method invocations may be chained and combined with fields and keys
|
Method invocations may be chained and combined with fields and keys
|
||||||
to any depth:
|
to any depth:
|
||||||
|
|
||||||
.Field1.Key1.Method1.Field2.Key2.Method2
|
.Field1.Key1.Method1.Field2.Key2.Method2
|
||||||
|
|
||||||
Methods can also be evaluated on variables, including chaining:
|
Methods can also be evaluated on variables, including chaining:
|
||||||
|
|
||||||
$x.Method1.Field
|
$x.Method1.Field
|
||||||
- The name of a niladic function, such as
|
- The name of a niladic function, such as
|
||||||
|
|
||||||
fun
|
fun
|
||||||
|
|
||||||
The result is the value of invoking the function, fun(). The return
|
The result is the value of invoking the function, fun(). The return
|
||||||
types and values behave as in methods. Functions and function
|
types and values behave as in methods. Functions and function
|
||||||
names are described below.
|
names are described below.
|
||||||
- A parenthesized instance of one the above, for grouping. The result
|
- A parenthesized instance of one the above, for grouping. The result
|
||||||
may be accessed by a field or map key invocation.
|
may be accessed by a field or map key invocation.
|
||||||
|
|
||||||
print (.F1 arg1) (.F2 arg2)
|
print (.F1 arg1) (.F2 arg2)
|
||||||
(.StructValuedMethod "arg").Field
|
(.StructValuedMethod "arg").Field
|
||||||
|
|
||||||
|
|
|
@ -35,7 +35,7 @@ Josie
|
||||||
Name, Gift string
|
Name, Gift string
|
||||||
Attended bool
|
Attended bool
|
||||||
}
|
}
|
||||||
recipients := []Recipient{
|
var recipients = []Recipient{
|
||||||
{"Aunt Mildred", "bone china tea set", true},
|
{"Aunt Mildred", "bone china tea set", true},
|
||||||
{"Uncle John", "moleskin pants", false},
|
{"Uncle John", "moleskin pants", false},
|
||||||
{"Cousin Rodney", "", false},
|
{"Cousin Rodney", "", false},
|
||||||
|
|
|
@ -2,6 +2,9 @@
|
||||||
// Use of this source code is governed by a BSD-style
|
// Use of this source code is governed by a BSD-style
|
||||||
// license that can be found in the LICENSE file.
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
//go:build go1.13
|
||||||
|
// +build go1.13
|
||||||
|
|
||||||
package template_test
|
package template_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
|
|
@ -395,6 +395,22 @@ func (s *state) walkRange(dot reflect.Value, r *parse.RangeNode) {
|
||||||
s.walk(elem, r.List)
|
s.walk(elem, r.List)
|
||||||
}
|
}
|
||||||
switch val.Kind() {
|
switch val.Kind() {
|
||||||
|
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64,
|
||||||
|
reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
|
||||||
|
if len(r.Pipe.Decl) > 1 {
|
||||||
|
s.errorf("can't use %v to iterate over more than one variable", val)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
run := false
|
||||||
|
for v := range val.Seq() {
|
||||||
|
run = true
|
||||||
|
// Pass element as second value, as we do for channels.
|
||||||
|
oneIteration(reflect.Value{}, v)
|
||||||
|
}
|
||||||
|
if !run {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
return
|
||||||
case reflect.Array, reflect.Slice:
|
case reflect.Array, reflect.Slice:
|
||||||
if val.Len() == 0 {
|
if val.Len() == 0 {
|
||||||
break
|
break
|
||||||
|
@ -434,6 +450,43 @@ func (s *state) walkRange(dot reflect.Value, r *parse.RangeNode) {
|
||||||
return
|
return
|
||||||
case reflect.Invalid:
|
case reflect.Invalid:
|
||||||
break // An invalid value is likely a nil map, etc. and acts like an empty map.
|
break // An invalid value is likely a nil map, etc. and acts like an empty map.
|
||||||
|
case reflect.Func:
|
||||||
|
if val.Type().CanSeq() {
|
||||||
|
if len(r.Pipe.Decl) > 1 {
|
||||||
|
s.errorf("can't use %v iterate over more than one variable", val)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
run := false
|
||||||
|
for v := range val.Seq() {
|
||||||
|
run = true
|
||||||
|
// Pass element as second value,
|
||||||
|
// as we do for channels.
|
||||||
|
oneIteration(reflect.Value{}, v)
|
||||||
|
}
|
||||||
|
if !run {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if val.Type().CanSeq2() {
|
||||||
|
run := false
|
||||||
|
for i, v := range val.Seq2() {
|
||||||
|
run = true
|
||||||
|
if len(r.Pipe.Decl) > 1 {
|
||||||
|
oneIteration(i, v)
|
||||||
|
} else {
|
||||||
|
// If there is only one range variable,
|
||||||
|
// oneIteration will use the
|
||||||
|
// second value.
|
||||||
|
oneIteration(reflect.Value{}, i)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !run {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
fallthrough
|
||||||
default:
|
default:
|
||||||
s.errorf("range can't iterate over %v", val)
|
s.errorf("range can't iterate over %v", val)
|
||||||
}
|
}
|
||||||
|
@ -757,7 +810,7 @@ func (s *state) evalCallOld(dot, fun reflect.Value, isBuiltin bool, node parse.N
|
||||||
return v
|
return v
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if final != missingVal {
|
if !final.Equal(missingVal) {
|
||||||
// The last argument to and/or is coming from
|
// The last argument to and/or is coming from
|
||||||
// the pipeline. We didn't short circuit on an earlier
|
// the pipeline. We didn't short circuit on an earlier
|
||||||
// argument, so we are going to return this one.
|
// argument, so we are going to return this one.
|
||||||
|
@ -803,7 +856,13 @@ func (s *state) evalCallOld(dot, fun reflect.Value, isBuiltin bool, node parse.N
|
||||||
// Special case for the "call" builtin.
|
// Special case for the "call" builtin.
|
||||||
// Insert the name of the callee function as the first argument.
|
// Insert the name of the callee function as the first argument.
|
||||||
if isBuiltin && name == "call" {
|
if isBuiltin && name == "call" {
|
||||||
calleeName := args[0].String()
|
var calleeName string
|
||||||
|
if len(args) == 0 {
|
||||||
|
// final must be present or we would have errored out above.
|
||||||
|
calleeName = final.String()
|
||||||
|
} else {
|
||||||
|
calleeName = args[0].String()
|
||||||
|
}
|
||||||
argv = append([]reflect.Value{reflect.ValueOf(calleeName)}, argv...)
|
argv = append([]reflect.Value{reflect.ValueOf(calleeName)}, argv...)
|
||||||
fun = reflect.ValueOf(call)
|
fun = reflect.ValueOf(call)
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,6 +13,7 @@ import (
|
||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
"iter"
|
||||||
"reflect"
|
"reflect"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
@ -412,6 +413,9 @@ var execTests = []execTest{
|
||||||
{"Interface Call", `{{stringer .S}}`, "foozle", map[string]any{"S": bytes.NewBufferString("foozle")}, true},
|
{"Interface Call", `{{stringer .S}}`, "foozle", map[string]any{"S": bytes.NewBufferString("foozle")}, true},
|
||||||
{".ErrFunc", "{{call .ErrFunc}}", "bla", tVal, true},
|
{".ErrFunc", "{{call .ErrFunc}}", "bla", tVal, true},
|
||||||
{"call nil", "{{call nil}}", "", tVal, false},
|
{"call nil", "{{call nil}}", "", tVal, false},
|
||||||
|
{"empty call", "{{call}}", "", tVal, false},
|
||||||
|
{"empty call after pipe valid", "{{.ErrFunc | call}}", "bla", tVal, true},
|
||||||
|
{"empty call after pipe invalid", "{{1 | call}}", "", tVal, false},
|
||||||
|
|
||||||
// Erroneous function calls (check args).
|
// Erroneous function calls (check args).
|
||||||
{".BinaryFuncTooFew", "{{call .BinaryFunc `1`}}", "", tVal, false},
|
{".BinaryFuncTooFew", "{{call .BinaryFunc `1`}}", "", tVal, false},
|
||||||
|
@ -618,6 +622,30 @@ var execTests = []execTest{
|
||||||
{"declare in range", "{{range $x := .PSI}}<{{$foo:=$x}}{{$x}}>{{end}}", "<21><22><23>", tVal, true},
|
{"declare in range", "{{range $x := .PSI}}<{{$foo:=$x}}{{$x}}>{{end}}", "<21><22><23>", tVal, true},
|
||||||
{"range count", `{{range $i, $x := count 5}}[{{$i}}]{{$x}}{{end}}`, "[0]a[1]b[2]c[3]d[4]e", tVal, true},
|
{"range count", `{{range $i, $x := count 5}}[{{$i}}]{{$x}}{{end}}`, "[0]a[1]b[2]c[3]d[4]e", tVal, true},
|
||||||
{"range nil count", `{{range $i, $x := count 0}}{{else}}empty{{end}}`, "empty", tVal, true},
|
{"range nil count", `{{range $i, $x := count 0}}{{else}}empty{{end}}`, "empty", tVal, true},
|
||||||
|
{"range iter.Seq[int]", `{{range $i := .}}{{$i}}{{end}}`, "01", fVal1(2), true},
|
||||||
|
{"i = range iter.Seq[int]", `{{$i := 0}}{{range $i = .}}{{$i}}{{end}}`, "01", fVal1(2), true},
|
||||||
|
{"range iter.Seq[int] over two var", `{{range $i, $c := .}}{{$c}}{{end}}`, "", fVal1(2), false},
|
||||||
|
{"i, c := range iter.Seq2[int,int]", `{{range $i, $c := .}}{{$i}}{{$c}}{{end}}`, "0112", fVal2(2), true},
|
||||||
|
{"i, c = range iter.Seq2[int,int]", `{{$i := 0}}{{$c := 0}}{{range $i, $c = .}}{{$i}}{{$c}}{{end}}`, "0112", fVal2(2), true},
|
||||||
|
{"i = range iter.Seq2[int,int]", `{{$i := 0}}{{range $i = .}}{{$i}}{{end}}`, "01", fVal2(2), true},
|
||||||
|
{"i := range iter.Seq2[int,int]", `{{range $i := .}}{{$i}}{{end}}`, "01", fVal2(2), true},
|
||||||
|
{"i,c,x range iter.Seq2[int,int]", `{{$i := 0}}{{$c := 0}}{{$x := 0}}{{range $i, $c = .}}{{$i}}{{$c}}{{end}}`, "0112", fVal2(2), true},
|
||||||
|
{"i,x range iter.Seq[int]", `{{$i := 0}}{{$x := 0}}{{range $i = .}}{{$i}}{{end}}`, "01", fVal1(2), true},
|
||||||
|
{"range iter.Seq[int] else", `{{range $i := .}}{{$i}}{{else}}empty{{end}}`, "empty", fVal1(0), true},
|
||||||
|
{"range iter.Seq2[int,int] else", `{{range $i := .}}{{$i}}{{else}}empty{{end}}`, "empty", fVal2(0), true},
|
||||||
|
{"range int8", rangeTestInt, rangeTestData[int8](), int8(5), true},
|
||||||
|
{"range int16", rangeTestInt, rangeTestData[int16](), int16(5), true},
|
||||||
|
{"range int32", rangeTestInt, rangeTestData[int32](), int32(5), true},
|
||||||
|
{"range int64", rangeTestInt, rangeTestData[int64](), int64(5), true},
|
||||||
|
{"range int", rangeTestInt, rangeTestData[int](), int(5), true},
|
||||||
|
{"range uint8", rangeTestInt, rangeTestData[uint8](), uint8(5), true},
|
||||||
|
{"range uint16", rangeTestInt, rangeTestData[uint16](), uint16(5), true},
|
||||||
|
{"range uint32", rangeTestInt, rangeTestData[uint32](), uint32(5), true},
|
||||||
|
{"range uint64", rangeTestInt, rangeTestData[uint64](), uint64(5), true},
|
||||||
|
{"range uint", rangeTestInt, rangeTestData[uint](), uint(5), true},
|
||||||
|
{"range uintptr", rangeTestInt, rangeTestData[uintptr](), uintptr(5), true},
|
||||||
|
{"range uintptr(0)", `{{range $v := .}}{{print $v}}{{else}}empty{{end}}`, "empty", uintptr(0), true},
|
||||||
|
{"range 5", `{{range $v := 5}}{{printf "%T%d" $v $v}}{{end}}`, rangeTestData[int](), nil, true},
|
||||||
|
|
||||||
// Cute examples.
|
// Cute examples.
|
||||||
{"or as if true", `{{or .SI "slice is empty"}}`, "[3 4 5]", tVal, true},
|
{"or as if true", `{{or .SI "slice is empty"}}`, "[3 4 5]", tVal, true},
|
||||||
|
@ -722,6 +750,37 @@ var execTests = []execTest{
|
||||||
{"issue60801", "{{$k := 0}}{{$v := 0}}{{range $k, $v = .AI}}{{$k}}={{$v}} {{end}}", "0=3 1=4 2=5 ", tVal, true},
|
{"issue60801", "{{$k := 0}}{{$v := 0}}{{range $k, $v = .AI}}{{$k}}={{$v}} {{end}}", "0=3 1=4 2=5 ", tVal, true},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func fVal1(i int) iter.Seq[int] {
|
||||||
|
return func(yield func(int) bool) {
|
||||||
|
for v := range i {
|
||||||
|
if !yield(v) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func fVal2(i int) iter.Seq2[int, int] {
|
||||||
|
return func(yield func(int, int) bool) {
|
||||||
|
for v := range i {
|
||||||
|
if !yield(v, v+1) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const rangeTestInt = `{{range $v := .}}{{printf "%T%d" $v $v}}{{end}}`
|
||||||
|
|
||||||
|
func rangeTestData[T int | int8 | int16 | int32 | int64 | uint | uint8 | uint16 | uint32 | uint64 | uintptr]() string {
|
||||||
|
I := T(5)
|
||||||
|
var buf strings.Builder
|
||||||
|
for i := T(0); i < I; i++ {
|
||||||
|
fmt.Fprintf(&buf, "%T%d", i, i)
|
||||||
|
}
|
||||||
|
return buf.String()
|
||||||
|
}
|
||||||
|
|
||||||
func zeroArgs() string {
|
func zeroArgs() string {
|
||||||
return "zeroArgs"
|
return "zeroArgs"
|
||||||
}
|
}
|
||||||
|
|
|
@ -304,14 +304,14 @@ func (s *state) evalCall(dot, fun reflect.Value, isBuiltin bool, node parse.Node
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
|
|
||||||
if args != nil {
|
if args != nil {
|
||||||
args = args[1:] // Zeroth arg is function name/node; not passed to function.
|
args = args[1:] // Zeroth arg is function name/node; not passed to function.
|
||||||
}
|
}
|
||||||
|
|
||||||
typ := fun.Type()
|
typ := fun.Type()
|
||||||
numFirst := len(first)
|
numFirst := len(first) // Added for Hugo
|
||||||
numIn := len(args) + numFirst // Added for Hugo
|
numIn := len(args) + numFirst // Added for Hugo
|
||||||
if final != missingVal {
|
if !isMissing(final) {
|
||||||
numIn++
|
numIn++
|
||||||
}
|
}
|
||||||
numFixed := len(args) + len(first) // Adjusted for Hugo
|
numFixed := len(args) + len(first) // Adjusted for Hugo
|
||||||
|
@ -346,7 +346,7 @@ func (s *state) evalCall(dot, fun reflect.Value, isBuiltin bool, node parse.Node
|
||||||
return v
|
return v
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if final != missingVal {
|
if !final.Equal(missingVal) {
|
||||||
// The last argument to and/or is coming from
|
// The last argument to and/or is coming from
|
||||||
// the pipeline. We didn't short circuit on an earlier
|
// the pipeline. We didn't short circuit on an earlier
|
||||||
// argument, so we are going to return this one.
|
// argument, so we are going to return this one.
|
||||||
|
@ -373,7 +373,7 @@ func (s *state) evalCall(dot, fun reflect.Value, isBuiltin bool, node parse.Node
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Add final value if necessary.
|
// Add final value if necessary.
|
||||||
if final != missingVal {
|
if !isMissing(final) {
|
||||||
t := typ.In(typ.NumIn() - 1)
|
t := typ.In(typ.NumIn() - 1)
|
||||||
if typ.IsVariadic() {
|
if typ.IsVariadic() {
|
||||||
if numIn-1 < numFixed {
|
if numIn-1 < numFixed {
|
||||||
|
@ -392,7 +392,13 @@ func (s *state) evalCall(dot, fun reflect.Value, isBuiltin bool, node parse.Node
|
||||||
// Special case for the "call" builtin.
|
// Special case for the "call" builtin.
|
||||||
// Insert the name of the callee function as the first argument.
|
// Insert the name of the callee function as the first argument.
|
||||||
if isBuiltin && name == "call" {
|
if isBuiltin && name == "call" {
|
||||||
calleeName := args[0].String()
|
var calleeName string
|
||||||
|
if len(args) == 0 {
|
||||||
|
// final must be present or we would have errored out above.
|
||||||
|
calleeName = final.String()
|
||||||
|
} else {
|
||||||
|
calleeName = args[0].String()
|
||||||
|
}
|
||||||
argv = append([]reflect.Value{reflect.ValueOf(calleeName)}, argv...)
|
argv = append([]reflect.Value{reflect.ValueOf(calleeName)}, argv...)
|
||||||
fun = reflect.ValueOf(call)
|
fun = reflect.ValueOf(call)
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,16 +2,18 @@
|
||||||
// Use of this source code is governed by a BSD-style
|
// Use of this source code is governed by a BSD-style
|
||||||
// license that can be found in the LICENSE file.
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
//go:build go1.13
|
||||||
|
// +build go1.13
|
||||||
|
|
||||||
package template_test
|
package template_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"github.com/gohugoio/hugo/tpl/internal/go_templates/testenv"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/gohugoio/hugo/tpl/internal/go_templates/testenv"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Issue 36021: verify that text/template doesn't prevent the linker from removing
|
// Issue 36021: verify that text/template doesn't prevent the linker from removing
|
||||||
|
@ -42,7 +44,7 @@ func main() {
|
||||||
`
|
`
|
||||||
td := t.TempDir()
|
td := t.TempDir()
|
||||||
|
|
||||||
if err := os.WriteFile(filepath.Join(td, "x.go"), []byte(prog), 0o644); err != nil {
|
if err := os.WriteFile(filepath.Join(td, "x.go"), []byte(prog), 0644); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
cmd := exec.Command(testenv.GoToolPath(t), "build", "-o", "x.exe", "x.go")
|
cmd := exec.Command(testenv.GoToolPath(t), "build", "-o", "x.exe", "x.go")
|
||||||
|
|
|
@ -2,8 +2,8 @@
|
||||||
// Use of this source code is governed by a BSD-style
|
// Use of this source code is governed by a BSD-style
|
||||||
// license that can be found in the LICENSE file.
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
//go:build !windows
|
//go:build go1.13 && !windows
|
||||||
// +build !windows
|
// +build go1.13,!windows
|
||||||
|
|
||||||
package template
|
package template
|
||||||
|
|
||||||
|
@ -11,11 +11,10 @@ package template
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"github.com/gohugoio/hugo/tpl/internal/go_templates/texttemplate/parse"
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/gohugoio/hugo/tpl/internal/go_templates/texttemplate/parse"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -32,32 +31,22 @@ type multiParseTest struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
var multiParseTests = []multiParseTest{
|
var multiParseTests = []multiParseTest{
|
||||||
{
|
{"empty", "", noError,
|
||||||
"empty", "", noError,
|
|
||||||
nil,
|
nil,
|
||||||
nil,
|
nil},
|
||||||
},
|
{"one", `{{define "foo"}} FOO {{end}}`, noError,
|
||||||
{
|
|
||||||
"one", `{{define "foo"}} FOO {{end}}`, noError,
|
|
||||||
[]string{"foo"},
|
[]string{"foo"},
|
||||||
[]string{" FOO "},
|
[]string{" FOO "}},
|
||||||
},
|
{"two", `{{define "foo"}} FOO {{end}}{{define "bar"}} BAR {{end}}`, noError,
|
||||||
{
|
|
||||||
"two", `{{define "foo"}} FOO {{end}}{{define "bar"}} BAR {{end}}`, noError,
|
|
||||||
[]string{"foo", "bar"},
|
[]string{"foo", "bar"},
|
||||||
[]string{" FOO ", " BAR "},
|
[]string{" FOO ", " BAR "}},
|
||||||
},
|
|
||||||
// errors
|
// errors
|
||||||
{
|
{"missing end", `{{define "foo"}} FOO `, hasError,
|
||||||
"missing end", `{{define "foo"}} FOO `, hasError,
|
|
||||||
nil,
|
nil,
|
||||||
|
nil},
|
||||||
|
{"malformed name", `{{define "foo}} FOO `, hasError,
|
||||||
nil,
|
nil,
|
||||||
},
|
nil},
|
||||||
{
|
|
||||||
"malformed name", `{{define "foo}} FOO `, hasError,
|
|
||||||
nil,
|
|
||||||
nil,
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestMultiParse(t *testing.T) {
|
func TestMultiParse(t *testing.T) {
|
||||||
|
@ -443,7 +432,7 @@ func TestIssue19294(t *testing.T) {
|
||||||
// by the contents of "stylesheet", but if the internal map associating
|
// by the contents of "stylesheet", but if the internal map associating
|
||||||
// names with templates is built in the wrong order, the empty block
|
// names with templates is built in the wrong order, the empty block
|
||||||
// looks non-empty and this doesn't happen.
|
// looks non-empty and this doesn't happen.
|
||||||
inlined := map[string]string{
|
var inlined = map[string]string{
|
||||||
"stylesheet": `{{define "stylesheet"}}stylesheet{{end}}`,
|
"stylesheet": `{{define "stylesheet"}}stylesheet{{end}}`,
|
||||||
"xhtml": `{{block "stylesheet" .}}{{end}}`,
|
"xhtml": `{{block "stylesheet" .}}{{end}}`,
|
||||||
}
|
}
|
||||||
|
|
|
@ -352,6 +352,7 @@ func lexComment(l *lexer) stateFn {
|
||||||
if !delim {
|
if !delim {
|
||||||
return l.errorf("comment ends before closing delimiter")
|
return l.errorf("comment ends before closing delimiter")
|
||||||
}
|
}
|
||||||
|
l.line += strings.Count(l.input[l.start:l.pos], "\n")
|
||||||
i := l.thisItem(itemComment)
|
i := l.thisItem(itemComment)
|
||||||
if trimSpace {
|
if trimSpace {
|
||||||
l.pos += trimMarkerLen
|
l.pos += trimMarkerLen
|
||||||
|
|
|
@ -548,6 +548,16 @@ var lexPosTests = []lexTest{
|
||||||
{itemRightDelim, 11, "}}", 2},
|
{itemRightDelim, 11, "}}", 2},
|
||||||
{itemEOF, 13, "", 2},
|
{itemEOF, 13, "", 2},
|
||||||
}},
|
}},
|
||||||
|
{"longcomment", "{{/*\n*/}}\n{{undefinedFunction \"test\"}}", []item{
|
||||||
|
{itemComment, 2, "/*\n*/", 1},
|
||||||
|
{itemText, 9, "\n", 2},
|
||||||
|
{itemLeftDelim, 10, "{{", 3},
|
||||||
|
{itemIdentifier, 12, "undefinedFunction", 3},
|
||||||
|
{itemSpace, 29, " ", 3},
|
||||||
|
{itemString, 30, "\"test\"", 3},
|
||||||
|
{itemRightDelim, 36, "}}", 3},
|
||||||
|
{itemEOF, 38, "", 3},
|
||||||
|
}},
|
||||||
}
|
}
|
||||||
|
|
||||||
// The other tests don't check position, to make the test cases easier to construct.
|
// The other tests don't check position, to make the test cases easier to construct.
|
||||||
|
|
|
@ -2,6 +2,9 @@
|
||||||
// Use of this source code is governed by a BSD-style
|
// Use of this source code is governed by a BSD-style
|
||||||
// license that can be found in the LICENSE file.
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
//go:build go1.13
|
||||||
|
// +build go1.13
|
||||||
|
|
||||||
package parse
|
package parse
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
@ -33,9 +36,9 @@ var numberTests = []numberTest{
|
||||||
{"7_3", true, true, true, false, 73, 73, 73, 0},
|
{"7_3", true, true, true, false, 73, 73, 73, 0},
|
||||||
{"0b10_010_01", true, true, true, false, 73, 73, 73, 0},
|
{"0b10_010_01", true, true, true, false, 73, 73, 73, 0},
|
||||||
{"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, 0o73, 0o73, 0o73, 0},
|
{"073", true, true, true, false, 073, 073, 073, 0},
|
||||||
{"0o73", true, true, true, false, 0o73, 0o73, 0o73, 0},
|
{"0o73", true, true, true, false, 073, 073, 073, 0},
|
||||||
{"0O73", true, true, true, false, 0o73, 0o73, 0o73, 0},
|
{"0O73", true, true, true, false, 073, 073, 073, 0},
|
||||||
{"0x73", true, true, true, false, 0x73, 0x73, 0x73, 0},
|
{"0x73", true, true, true, false, 0x73, 0x73, 0x73, 0},
|
||||||
{"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},
|
{"0x7_3", true, true, true, false, 0x73, 0x73, 0x73, 0},
|
||||||
|
@ -61,7 +64,7 @@ var numberTests = []numberTest{
|
||||||
{"-12+0i", true, false, true, true, -12, 0, -12, -12},
|
{"-12+0i", true, false, true, true, -12, 0, -12, -12},
|
||||||
{"13+0i", true, true, true, true, 13, 13, 13, 13},
|
{"13+0i", true, true, true, true, 13, 13, 13, 13},
|
||||||
// funny bases
|
// funny bases
|
||||||
{"0123", true, true, true, false, 0o123, 0o123, 0o123, 0},
|
{"0123", true, true, true, false, 0123, 0123, 0123, 0},
|
||||||
{"-0x0", true, true, true, false, 0, 0, 0, 0},
|
{"-0x0", true, true, true, false, 0, 0, 0, 0},
|
||||||
{"0xdeadbeef", true, true, true, false, 0xdeadbeef, 0xdeadbeef, 0xdeadbeef, 0},
|
{"0xdeadbeef", true, true, true, false, 0xdeadbeef, 0xdeadbeef, 0xdeadbeef, 0},
|
||||||
// character constants
|
// character constants
|
||||||
|
@ -176,150 +179,78 @@ const (
|
||||||
)
|
)
|
||||||
|
|
||||||
var parseTests = []parseTest{
|
var parseTests = []parseTest{
|
||||||
{
|
{"empty", "", noError,
|
||||||
"empty", "", noError,
|
``},
|
||||||
``,
|
{"comment", "{{/*\n\n\n*/}}", noError,
|
||||||
},
|
``},
|
||||||
{
|
{"spaces", " \t\n", noError,
|
||||||
"comment", "{{/*\n\n\n*/}}", noError,
|
`" \t\n"`},
|
||||||
``,
|
{"text", "some text", noError,
|
||||||
},
|
`"some text"`},
|
||||||
{
|
{"emptyAction", "{{}}", hasError,
|
||||||
"spaces", " \t\n", noError,
|
`{{}}`},
|
||||||
`" \t\n"`,
|
{"field", "{{.X}}", noError,
|
||||||
},
|
`{{.X}}`},
|
||||||
{
|
{"simple command", "{{printf}}", noError,
|
||||||
"text", "some text", noError,
|
`{{printf}}`},
|
||||||
`"some text"`,
|
{"$ invocation", "{{$}}", noError,
|
||||||
},
|
"{{$}}"},
|
||||||
{
|
{"variable invocation", "{{with $x := 3}}{{$x 23}}{{end}}", noError,
|
||||||
"emptyAction", "{{}}", hasError,
|
"{{with $x := 3}}{{$x 23}}{{end}}"},
|
||||||
`{{}}`,
|
{"variable with fields", "{{$.I}}", noError,
|
||||||
},
|
"{{$.I}}"},
|
||||||
{
|
{"multi-word command", "{{printf `%d` 23}}", noError,
|
||||||
"field", "{{.X}}", noError,
|
"{{printf `%d` 23}}"},
|
||||||
`{{.X}}`,
|
{"pipeline", "{{.X|.Y}}", noError,
|
||||||
},
|
`{{.X | .Y}}`},
|
||||||
{
|
{"pipeline with decl", "{{$x := .X|.Y}}", noError,
|
||||||
"simple command", "{{printf}}", noError,
|
`{{$x := .X | .Y}}`},
|
||||||
`{{printf}}`,
|
{"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,
|
||||||
"$ invocation", "{{$}}", 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,
|
||||||
"variable invocation", "{{with $x := 3}}{{$x 23}}{{end}}", noError,
|
`{{if .X}}"true"{{else}}"false"{{end}}`},
|
||||||
"{{with $x := 3}}{{$x 23}}{{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,
|
||||||
"variable with fields", "{{$.I}}", noError,
|
`"+"{{if .X}}"X"{{else}}{{if .Y}}"Y"{{else}}{{if .Z}}"Z"{{end}}{{end}}{{end}}"+"`},
|
||||||
"{{$.I}}",
|
{"simple range", "{{range .X}}hello{{end}}", noError,
|
||||||
},
|
`{{range .X}}"hello"{{end}}`},
|
||||||
{
|
{"chained field range", "{{range .X.Y.Z}}hello{{end}}", noError,
|
||||||
"multi-word command", "{{printf `%d` 23}}", noError,
|
`{{range .X.Y.Z}}"hello"{{end}}`},
|
||||||
"{{printf `%d` 23}}",
|
{"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,
|
||||||
"pipeline", "{{.X|.Y}}", noError,
|
`{{range .X}}"true"{{else}}"false"{{end}}`},
|
||||||
`{{.X | .Y}}`,
|
{"range over pipeline", "{{range .X|.M}}true{{else}}false{{end}}", noError,
|
||||||
},
|
`{{range .X | .M}}"true"{{else}}"false"{{end}}`},
|
||||||
{
|
{"range []int", "{{range .SI}}{{.}}{{end}}", noError,
|
||||||
"pipeline with decl", "{{$x := .X|.Y}}", noError,
|
`{{range .SI}}{{.}}{{end}}`},
|
||||||
`{{$x := .X | .Y}}`,
|
{"range 1 var", "{{range $x := .SI}}{{.}}{{end}}", noError,
|
||||||
},
|
`{{range $x := .SI}}{{.}}{{end}}`},
|
||||||
{
|
{"range 2 vars", "{{range $x, $y := .SI}}{{.}}{{end}}", noError,
|
||||||
"nested pipeline", "{{.X (.Y .Z) (.A | .B .C) (.E)}}", noError,
|
`{{range $x, $y := .SI}}{{.}}{{end}}`},
|
||||||
`{{.X (.Y .Z) (.A | .B .C) (.E)}}`,
|
{"range with break", "{{range .SI}}{{.}}{{break}}{{end}}", noError,
|
||||||
},
|
`{{range .SI}}{{.}}{{break}}{{end}}`},
|
||||||
{
|
{"range with continue", "{{range .SI}}{{.}}{{continue}}{{end}}", noError,
|
||||||
"field applied to parentheses", "{{(.Y .Z).Field}}", noError,
|
`{{range .SI}}{{.}}{{continue}}{{end}}`},
|
||||||
`{{(.Y .Z).Field}}`,
|
{"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,
|
||||||
"simple if", "{{if .X}}hello{{end}}", noError,
|
`{{template "x"}}`},
|
||||||
`{{if .X}}"hello"{{end}}`,
|
{"template with arg", "{{template `x` .Y}}", noError,
|
||||||
},
|
`{{template "x" .Y}}`},
|
||||||
{
|
{"with", "{{with .X}}hello{{end}}", noError,
|
||||||
"if with else", "{{if .X}}true{{else}}false{{end}}", noError,
|
`{{with .X}}"hello"{{end}}`},
|
||||||
`{{if .X}}"true"{{else}}"false"{{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,
|
||||||
"if with else if", "{{if .X}}true{{else if .Y}}false{{end}}", noError,
|
`{{with .X}}"hello"{{else}}{{with .Y}}"goodbye"{{end}}{{end}}`},
|
||||||
`{{if .X}}"true"{{else}}{{if .Y}}"false"{{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}}`},
|
||||||
{
|
|
||||||
"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.
|
// Trimming spaces.
|
||||||
{"trim left", "x \r\n\t{{- 3}}", noError, `"x"{{3}}`},
|
{"trim left", "x \r\n\t{{- 3}}", noError, `"x"{{3}}`},
|
||||||
{"trim right", "{{3 -}}\n\n\ty", noError, `{{3}}"y"`},
|
{"trim right", "{{3 -}}\n\n\ty", noError, `{{3}}"y"`},
|
||||||
|
@ -328,24 +259,18 @@ var parseTests = []parseTest{
|
||||||
{"comment trim left", "x \r\n\t{{- /* hi */}}", noError, `"x"`},
|
{"comment trim left", "x \r\n\t{{- /* hi */}}", noError, `"x"`},
|
||||||
{"comment trim right", "{{/* hi */ -}}\n\n\ty", noError, `"y"`},
|
{"comment trim right", "{{/* hi */ -}}\n\n\ty", noError, `"y"`},
|
||||||
{"comment trim left and right", "x \r\n\t{{- /* */ -}}\n\n\ty", noError, `"x""y"`},
|
{"comment trim left and right", "x \r\n\t{{- /* */ -}}\n\n\ty", noError, `"x""y"`},
|
||||||
{
|
{"block definition", `{{block "foo" .}}hello{{end}}`, noError,
|
||||||
"block definition", `{{block "foo" .}}hello{{end}}`, noError,
|
`{{template "foo" .}}`},
|
||||||
`{{template "foo" .}}`,
|
|
||||||
},
|
|
||||||
|
|
||||||
{"newline in assignment", "{{ $x \n := \n 1 \n }}", noError, "{{$x := 1}}"},
|
{"newline in assignment", "{{ $x \n := \n 1 \n }}", noError, "{{$x := 1}}"},
|
||||||
{"newline in empty action", "{{\n}}", hasError, "{{\n}}"},
|
{"newline in empty action", "{{\n}}", hasError, "{{\n}}"},
|
||||||
{"newline in pipeline", "{{\n\"x\"\n|\nprintf\n}}", noError, `{{"x" | printf}}`},
|
{"newline in pipeline", "{{\n\"x\"\n|\nprintf\n}}", noError, `{{"x" | printf}}`},
|
||||||
{"newline in comment", "{{/*\nhello\n*/}}", noError, ""},
|
{"newline in comment", "{{/*\nhello\n*/}}", noError, ""},
|
||||||
{"newline in comment", "{{-\n/*\nhello\n*/\n-}}", noError, ""},
|
{"newline in comment", "{{-\n/*\nhello\n*/\n-}}", noError, ""},
|
||||||
{
|
{"spaces around continue", "{{range .SI}}{{.}}{{ continue }}{{end}}", noError,
|
||||||
"spaces around continue", "{{range .SI}}{{.}}{{ continue }}{{end}}", noError,
|
`{{range .SI}}{{.}}{{continue}}{{end}}`},
|
||||||
`{{range .SI}}{{.}}{{continue}}{{end}}`,
|
{"spaces around break", "{{range .SI}}{{.}}{{ break }}{{end}}", noError,
|
||||||
},
|
`{{range .SI}}{{.}}{{break}}{{end}}`},
|
||||||
{
|
|
||||||
"spaces around break", "{{range .SI}}{{.}}{{ break }}{{end}}", noError,
|
|
||||||
`{{range .SI}}{{.}}{{break}}{{end}}`,
|
|
||||||
},
|
|
||||||
|
|
||||||
// Errors.
|
// Errors.
|
||||||
{"unclosed action", "hello{{range", hasError, ""},
|
{"unclosed action", "hello{{range", hasError, ""},
|
||||||
|
@ -487,7 +412,7 @@ func TestKeywordsAndFuncs(t *testing.T) {
|
||||||
{
|
{
|
||||||
// 'break' is a defined function, don't treat it as a keyword: it should
|
// 'break' is a defined function, don't treat it as a keyword: it should
|
||||||
// accept an argument successfully.
|
// accept an argument successfully.
|
||||||
funcsWithKeywordFunc := map[string]any{
|
var funcsWithKeywordFunc = map[string]any{
|
||||||
"break": func(in any) any { return in },
|
"break": func(in any) any { return in },
|
||||||
}
|
}
|
||||||
tmpl, err := New("").Parse(inp, "", "", make(map[string]*Tree), funcsWithKeywordFunc)
|
tmpl, err := New("").Parse(inp, "", "", make(map[string]*Tree), funcsWithKeywordFunc)
|
||||||
|
@ -574,168 +499,104 @@ func TestErrorContextWithTreeCopy(t *testing.T) {
|
||||||
// All failures, and the result is a string that must appear in the error message.
|
// All failures, and the result is a string that must appear in the error message.
|
||||||
var errorTests = []parseTest{
|
var errorTests = []parseTest{
|
||||||
// Check line numbers are accurate.
|
// Check line numbers are accurate.
|
||||||
{
|
{"unclosed1",
|
||||||
"unclosed1",
|
|
||||||
"line1\n{{",
|
"line1\n{{",
|
||||||
hasError, `unclosed1:2: unclosed action`,
|
hasError, `unclosed1:2: unclosed action`},
|
||||||
},
|
{"unclosed2",
|
||||||
{
|
|
||||||
"unclosed2",
|
|
||||||
"line1\n{{define `x`}}line2\n{{",
|
"line1\n{{define `x`}}line2\n{{",
|
||||||
hasError, `unclosed2:3: unclosed action`,
|
hasError, `unclosed2:3: unclosed action`},
|
||||||
},
|
{"unclosed3",
|
||||||
{
|
|
||||||
"unclosed3",
|
|
||||||
"line1\n{{\"x\"\n\"y\"\n",
|
"line1\n{{\"x\"\n\"y\"\n",
|
||||||
hasError, `unclosed3:4: unclosed action started at unclosed3:2`,
|
hasError, `unclosed3:4: unclosed action started at unclosed3:2`},
|
||||||
},
|
{"unclosed4",
|
||||||
{
|
|
||||||
"unclosed4",
|
|
||||||
"{{\n\n\n\n\n",
|
"{{\n\n\n\n\n",
|
||||||
hasError, `unclosed4:6: unclosed action started at unclosed4:1`,
|
hasError, `unclosed4:6: unclosed action started at unclosed4:1`},
|
||||||
},
|
{"var1",
|
||||||
{
|
|
||||||
"var1",
|
|
||||||
"line1\n{{\nx\n}}",
|
"line1\n{{\nx\n}}",
|
||||||
hasError, `var1:3: function "x" not defined`,
|
hasError, `var1:3: function "x" not defined`},
|
||||||
},
|
|
||||||
// Specific errors.
|
// Specific errors.
|
||||||
{
|
{"function",
|
||||||
"function",
|
|
||||||
"{{foo}}",
|
"{{foo}}",
|
||||||
hasError, `function "foo" not defined`,
|
hasError, `function "foo" not defined`},
|
||||||
},
|
{"comment1",
|
||||||
{
|
|
||||||
"comment1",
|
|
||||||
"{{/*}}",
|
"{{/*}}",
|
||||||
hasError, `comment1:1: unclosed comment`,
|
hasError, `comment1:1: unclosed comment`},
|
||||||
},
|
{"comment2",
|
||||||
{
|
|
||||||
"comment2",
|
|
||||||
"{{/*\nhello\n}}",
|
"{{/*\nhello\n}}",
|
||||||
hasError, `comment2:1: unclosed comment`,
|
hasError, `comment2:1: unclosed comment`},
|
||||||
},
|
{"lparen",
|
||||||
{
|
|
||||||
"lparen",
|
|
||||||
"{{.X (1 2 3}}",
|
"{{.X (1 2 3}}",
|
||||||
hasError, `unclosed left paren`,
|
hasError, `unclosed left paren`},
|
||||||
},
|
{"rparen",
|
||||||
{
|
|
||||||
"rparen",
|
|
||||||
"{{.X 1 2 3 ) }}",
|
"{{.X 1 2 3 ) }}",
|
||||||
hasError, "unexpected right paren",
|
hasError, "unexpected right paren"},
|
||||||
},
|
{"rparen2",
|
||||||
{
|
|
||||||
"rparen2",
|
|
||||||
"{{(.X 1 2 3",
|
"{{(.X 1 2 3",
|
||||||
hasError, `unclosed action`,
|
hasError, `unclosed action`},
|
||||||
},
|
{"space",
|
||||||
{
|
|
||||||
"space",
|
|
||||||
"{{`x`3}}",
|
"{{`x`3}}",
|
||||||
hasError, `in operand`,
|
hasError, `in operand`},
|
||||||
},
|
{"idchar",
|
||||||
{
|
|
||||||
"idchar",
|
|
||||||
"{{a#}}",
|
"{{a#}}",
|
||||||
hasError, `'#'`,
|
hasError, `'#'`},
|
||||||
},
|
{"charconst",
|
||||||
{
|
|
||||||
"charconst",
|
|
||||||
"{{'a}}",
|
"{{'a}}",
|
||||||
hasError, `unterminated character constant`,
|
hasError, `unterminated character constant`},
|
||||||
},
|
{"stringconst",
|
||||||
{
|
|
||||||
"stringconst",
|
|
||||||
`{{"a}}`,
|
`{{"a}}`,
|
||||||
hasError, `unterminated quoted string`,
|
hasError, `unterminated quoted string`},
|
||||||
},
|
{"rawstringconst",
|
||||||
{
|
|
||||||
"rawstringconst",
|
|
||||||
"{{`a}}",
|
"{{`a}}",
|
||||||
hasError, `unterminated raw quoted string`,
|
hasError, `unterminated raw quoted string`},
|
||||||
},
|
{"number",
|
||||||
{
|
|
||||||
"number",
|
|
||||||
"{{0xi}}",
|
"{{0xi}}",
|
||||||
hasError, `number syntax`,
|
hasError, `number syntax`},
|
||||||
},
|
{"multidefine",
|
||||||
{
|
|
||||||
"multidefine",
|
|
||||||
"{{define `a`}}a{{end}}{{define `a`}}b{{end}}",
|
"{{define `a`}}a{{end}}{{define `a`}}b{{end}}",
|
||||||
hasError, `multiple definition of template`,
|
hasError, `multiple definition of template`},
|
||||||
},
|
{"eof",
|
||||||
{
|
|
||||||
"eof",
|
|
||||||
"{{range .X}}",
|
"{{range .X}}",
|
||||||
hasError, `unexpected EOF`,
|
hasError, `unexpected EOF`},
|
||||||
},
|
{"variable",
|
||||||
{
|
|
||||||
"variable",
|
|
||||||
// Declare $x so it's defined, to avoid that error, and then check we don't parse a declaration.
|
// 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}}",
|
"{{$x := 23}}{{with $x.y := 3}}{{$x 23}}{{end}}",
|
||||||
hasError, `unexpected ":="`,
|
hasError, `unexpected ":="`},
|
||||||
},
|
{"multidecl",
|
||||||
{
|
|
||||||
"multidecl",
|
|
||||||
"{{$a,$b,$c := 23}}",
|
"{{$a,$b,$c := 23}}",
|
||||||
hasError, `too many declarations`,
|
hasError, `too many declarations`},
|
||||||
},
|
{"undefvar",
|
||||||
{
|
|
||||||
"undefvar",
|
|
||||||
"{{$a}}",
|
"{{$a}}",
|
||||||
hasError, `undefined variable`,
|
hasError, `undefined variable`},
|
||||||
},
|
{"wrongdot",
|
||||||
{
|
|
||||||
"wrongdot",
|
|
||||||
"{{true.any}}",
|
"{{true.any}}",
|
||||||
hasError, `unexpected . after term`,
|
hasError, `unexpected . after term`},
|
||||||
},
|
{"wrongpipeline",
|
||||||
{
|
|
||||||
"wrongpipeline",
|
|
||||||
"{{12|false}}",
|
"{{12|false}}",
|
||||||
hasError, `non executable command in pipeline`,
|
hasError, `non executable command in pipeline`},
|
||||||
},
|
{"emptypipeline",
|
||||||
{
|
|
||||||
"emptypipeline",
|
|
||||||
`{{ ( ) }}`,
|
`{{ ( ) }}`,
|
||||||
hasError, `missing value for parenthesized pipeline`,
|
hasError, `missing value for parenthesized pipeline`},
|
||||||
},
|
{"multilinerawstring",
|
||||||
{
|
|
||||||
"multilinerawstring",
|
|
||||||
"{{ $v := `\n` }} {{",
|
"{{ $v := `\n` }} {{",
|
||||||
hasError, `multilinerawstring:2: unclosed action`,
|
hasError, `multilinerawstring:2: unclosed action`},
|
||||||
},
|
{"rangeundefvar",
|
||||||
{
|
|
||||||
"rangeundefvar",
|
|
||||||
"{{range $k}}{{end}}",
|
"{{range $k}}{{end}}",
|
||||||
hasError, `undefined variable`,
|
hasError, `undefined variable`},
|
||||||
},
|
{"rangeundefvars",
|
||||||
{
|
|
||||||
"rangeundefvars",
|
|
||||||
"{{range $k, $v}}{{end}}",
|
"{{range $k, $v}}{{end}}",
|
||||||
hasError, `undefined variable`,
|
hasError, `undefined variable`},
|
||||||
},
|
{"rangemissingvalue1",
|
||||||
{
|
|
||||||
"rangemissingvalue1",
|
|
||||||
"{{range $k,}}{{end}}",
|
"{{range $k,}}{{end}}",
|
||||||
hasError, `missing value for range`,
|
hasError, `missing value for range`},
|
||||||
},
|
{"rangemissingvalue2",
|
||||||
{
|
|
||||||
"rangemissingvalue2",
|
|
||||||
"{{range $k, $v := }}{{end}}",
|
"{{range $k, $v := }}{{end}}",
|
||||||
hasError, `missing value for range`,
|
hasError, `missing value for range`},
|
||||||
},
|
{"rangenotvariable1",
|
||||||
{
|
|
||||||
"rangenotvariable1",
|
|
||||||
"{{range $k, .}}{{end}}",
|
"{{range $k, .}}{{end}}",
|
||||||
hasError, `range can only initialize variables`,
|
hasError, `range can only initialize variables`},
|
||||||
},
|
{"rangenotvariable2",
|
||||||
{
|
|
||||||
"rangenotvariable2",
|
|
||||||
"{{range $k, 123 := .}}{{end}}",
|
"{{range $k, 123 := .}}{{end}}",
|
||||||
hasError, `range can only initialize variables`,
|
hasError, `range can only initialize variables`},
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestErrors(t *testing.T) {
|
func TestErrors(t *testing.T) {
|
||||||
|
|
|
@ -6,6 +6,7 @@ package template
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/gohugoio/hugo/tpl/internal/go_templates/texttemplate/parse"
|
"github.com/gohugoio/hugo/tpl/internal/go_templates/texttemplate/parse"
|
||||||
|
"maps"
|
||||||
"reflect"
|
"reflect"
|
||||||
"sync"
|
"sync"
|
||||||
)
|
)
|
||||||
|
@ -102,12 +103,8 @@ func (t *Template) Clone() (*Template, error) {
|
||||||
}
|
}
|
||||||
t.muFuncs.RLock()
|
t.muFuncs.RLock()
|
||||||
defer t.muFuncs.RUnlock()
|
defer t.muFuncs.RUnlock()
|
||||||
for k, v := range t.parseFuncs {
|
maps.Copy(nt.parseFuncs, t.parseFuncs)
|
||||||
nt.parseFuncs[k] = v
|
maps.Copy(nt.execFuncs, t.execFuncs)
|
||||||
}
|
|
||||||
for k, v := range t.execFuncs {
|
|
||||||
nt.execFuncs[k] = v
|
|
||||||
}
|
|
||||||
return nt, nil
|
return nt, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Add table
Reference in a new issue