1// Copyright 2011 The Go Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style
3// license that can be found in the LICENSE file.
4
5package parse
6
7import (
8	"flag"
9	"fmt"
10	"strings"
11	"testing"
12)
13
14var debug = flag.Bool("debug", false, "show the errors produced by the main tests")
15
16type numberTest struct {
17	text      string
18	isInt     bool
19	isUint    bool
20	isFloat   bool
21	isComplex bool
22	int64
23	uint64
24	float64
25	complex128
26}
27
28var numberTests = []numberTest{
29	// basics
30	{"0", true, true, true, false, 0, 0, 0, 0},
31	{"-0", true, true, true, false, 0, 0, 0, 0}, // check that -0 is a uint.
32	{"73", true, true, true, false, 73, 73, 73, 0},
33	{"7_3", true, true, true, false, 73, 73, 73, 0},
34	{"0b10_010_01", true, true, true, false, 73, 73, 73, 0},
35	{"0B10_010_01", true, true, true, false, 73, 73, 73, 0},
36	{"073", true, true, true, false, 073, 073, 073, 0},
37	{"0o73", true, true, true, false, 073, 073, 073, 0},
38	{"0O73", true, true, true, false, 073, 073, 073, 0},
39	{"0x73", true, true, true, false, 0x73, 0x73, 0x73, 0},
40	{"0X73", true, true, true, false, 0x73, 0x73, 0x73, 0},
41	{"0x7_3", true, true, true, false, 0x73, 0x73, 0x73, 0},
42	{"-73", true, false, true, false, -73, 0, -73, 0},
43	{"+73", true, false, true, false, 73, 0, 73, 0},
44	{"100", true, true, true, false, 100, 100, 100, 0},
45	{"1e9", true, true, true, false, 1e9, 1e9, 1e9, 0},
46	{"-1e9", true, false, true, false, -1e9, 0, -1e9, 0},
47	{"-1.2", false, false, true, false, 0, 0, -1.2, 0},
48	{"1e19", false, true, true, false, 0, 1e19, 1e19, 0},
49	{"1e1_9", false, true, true, false, 0, 1e19, 1e19, 0},
50	{"1E19", false, true, true, false, 0, 1e19, 1e19, 0},
51	{"-1e19", false, false, true, false, 0, 0, -1e19, 0},
52	{"0x_1p4", true, true, true, false, 16, 16, 16, 0},
53	{"0X_1P4", true, true, true, false, 16, 16, 16, 0},
54	{"0x_1p-4", false, false, true, false, 0, 0, 1 / 16., 0},
55	{"4i", false, false, false, true, 0, 0, 0, 4i},
56	{"-1.2+4.2i", false, false, false, true, 0, 0, 0, -1.2 + 4.2i},
57	{"073i", false, false, false, true, 0, 0, 0, 73i}, // not octal!
58	// complex with 0 imaginary are float (and maybe integer)
59	{"0i", true, true, true, true, 0, 0, 0, 0},
60	{"-1.2+0i", false, false, true, true, 0, 0, -1.2, -1.2},
61	{"-12+0i", true, false, true, true, -12, 0, -12, -12},
62	{"13+0i", true, true, true, true, 13, 13, 13, 13},
63	// funny bases
64	{"0123", true, true, true, false, 0123, 0123, 0123, 0},
65	{"-0x0", true, true, true, false, 0, 0, 0, 0},
66	{"0xdeadbeef", true, true, true, false, 0xdeadbeef, 0xdeadbeef, 0xdeadbeef, 0},
67	// character constants
68	{`'a'`, true, true, true, false, 'a', 'a', 'a', 0},
69	{`'\n'`, true, true, true, false, '\n', '\n', '\n', 0},
70	{`'\\'`, true, true, true, false, '\\', '\\', '\\', 0},
71	{`'\''`, true, true, true, false, '\'', '\'', '\'', 0},
72	{`'\xFF'`, true, true, true, false, 0xFF, 0xFF, 0xFF, 0},
73	{`'パ'`, true, true, true, false, 0x30d1, 0x30d1, 0x30d1, 0},
74	{`'\u30d1'`, true, true, true, false, 0x30d1, 0x30d1, 0x30d1, 0},
75	{`'\U000030d1'`, true, true, true, false, 0x30d1, 0x30d1, 0x30d1, 0},
76	// some broken syntax
77	{text: "+-2"},
78	{text: "0x123."},
79	{text: "1e."},
80	{text: "0xi."},
81	{text: "1+2."},
82	{text: "'x"},
83	{text: "'xx'"},
84	{text: "'433937734937734969526500969526500'"}, // Integer too large - issue 10634.
85	// Issue 8622 - 0xe parsed as floating point. Very embarrassing.
86	{"0xef", true, true, true, false, 0xef, 0xef, 0xef, 0},
87}
88
89func TestNumberParse(t *testing.T) {
90	for _, test := range numberTests {
91		// If fmt.Sscan thinks it's complex, it's complex. We can't trust the output
92		// because imaginary comes out as a number.
93		var c complex128
94		typ := itemNumber
95		var tree *Tree
96		if test.text[0] == '\'' {
97			typ = itemCharConstant
98		} else {
99			_, err := fmt.Sscan(test.text, &c)
100			if err == nil {
101				typ = itemComplex
102			}
103		}
104		n, err := tree.newNumber(0, test.text, typ)
105		ok := test.isInt || test.isUint || test.isFloat || test.isComplex
106		if ok && err != nil {
107			t.Errorf("unexpected error for %q: %s", test.text, err)
108			continue
109		}
110		if !ok && err == nil {
111			t.Errorf("expected error for %q", test.text)
112			continue
113		}
114		if !ok {
115			if *debug {
116				fmt.Printf("%s\n\t%s\n", test.text, err)
117			}
118			continue
119		}
120		if n.IsComplex != test.isComplex {
121			t.Errorf("complex incorrect for %q; should be %t", test.text, test.isComplex)
122		}
123		if test.isInt {
124			if !n.IsInt {
125				t.Errorf("expected integer for %q", test.text)
126			}
127			if n.Int64 != test.int64 {
128				t.Errorf("int64 for %q should be %d Is %d", test.text, test.int64, n.Int64)
129			}
130		} else if n.IsInt {
131			t.Errorf("did not expect integer for %q", test.text)
132		}
133		if test.isUint {
134			if !n.IsUint {
135				t.Errorf("expected unsigned integer for %q", test.text)
136			}
137			if n.Uint64 != test.uint64 {
138				t.Errorf("uint64 for %q should be %d Is %d", test.text, test.uint64, n.Uint64)
139			}
140		} else if n.IsUint {
141			t.Errorf("did not expect unsigned integer for %q", test.text)
142		}
143		if test.isFloat {
144			if !n.IsFloat {
145				t.Errorf("expected float for %q", test.text)
146			}
147			if n.Float64 != test.float64 {
148				t.Errorf("float64 for %q should be %g Is %g", test.text, test.float64, n.Float64)
149			}
150		} else if n.IsFloat {
151			t.Errorf("did not expect float for %q", test.text)
152		}
153		if test.isComplex {
154			if !n.IsComplex {
155				t.Errorf("expected complex for %q", test.text)
156			}
157			if n.Complex128 != test.complex128 {
158				t.Errorf("complex128 for %q should be %g Is %g", test.text, test.complex128, n.Complex128)
159			}
160		} else if n.IsComplex {
161			t.Errorf("did not expect complex for %q", test.text)
162		}
163	}
164}
165
166type parseTest struct {
167	name   string
168	input  string
169	ok     bool
170	result string // what the user would see in an error message.
171}
172
173const (
174	noError  = true
175	hasError = false
176)
177
178var parseTests = []parseTest{
179	{"empty", "", noError,
180		``},
181	{"comment", "{{/*\n\n\n*/}}", noError,
182		``},
183	{"spaces", " \t\n", noError,
184		`" \t\n"`},
185	{"text", "some text", noError,
186		`"some text"`},
187	{"emptyAction", "{{}}", hasError,
188		`{{}}`},
189	{"field", "{{.X}}", noError,
190		`{{.X}}`},
191	{"simple command", "{{printf}}", noError,
192		`{{printf}}`},
193	{"$ invocation", "{{$}}", noError,
194		"{{$}}"},
195	{"variable invocation", "{{with $x := 3}}{{$x 23}}{{end}}", noError,
196		"{{with $x := 3}}{{$x 23}}{{end}}"},
197	{"variable with fields", "{{$.I}}", noError,
198		"{{$.I}}"},
199	{"multi-word command", "{{printf `%d` 23}}", noError,
200		"{{printf `%d` 23}}"},
201	{"pipeline", "{{.X|.Y}}", noError,
202		`{{.X | .Y}}`},
203	{"pipeline with decl", "{{$x := .X|.Y}}", noError,
204		`{{$x := .X | .Y}}`},
205	{"nested pipeline", "{{.X (.Y .Z) (.A | .B .C) (.E)}}", noError,
206		`{{.X (.Y .Z) (.A | .B .C) (.E)}}`},
207	{"field applied to parentheses", "{{(.Y .Z).Field}}", noError,
208		`{{(.Y .Z).Field}}`},
209	{"simple if", "{{if .X}}hello{{end}}", noError,
210		`{{if .X}}"hello"{{end}}`},
211	{"if with else", "{{if .X}}true{{else}}false{{end}}", noError,
212		`{{if .X}}"true"{{else}}"false"{{end}}`},
213	{"if with else if", "{{if .X}}true{{else if .Y}}false{{end}}", noError,
214		`{{if .X}}"true"{{else}}{{if .Y}}"false"{{end}}{{end}}`},
215	{"if else chain", "+{{if .X}}X{{else if .Y}}Y{{else if .Z}}Z{{end}}+", noError,
216		`"+"{{if .X}}"X"{{else}}{{if .Y}}"Y"{{else}}{{if .Z}}"Z"{{end}}{{end}}{{end}}"+"`},
217	{"simple range", "{{range .X}}hello{{end}}", noError,
218		`{{range .X}}"hello"{{end}}`},
219	{"chained field range", "{{range .X.Y.Z}}hello{{end}}", noError,
220		`{{range .X.Y.Z}}"hello"{{end}}`},
221	{"nested range", "{{range .X}}hello{{range .Y}}goodbye{{end}}{{end}}", noError,
222		`{{range .X}}"hello"{{range .Y}}"goodbye"{{end}}{{end}}`},
223	{"range with else", "{{range .X}}true{{else}}false{{end}}", noError,
224		`{{range .X}}"true"{{else}}"false"{{end}}`},
225	{"range over pipeline", "{{range .X|.M}}true{{else}}false{{end}}", noError,
226		`{{range .X | .M}}"true"{{else}}"false"{{end}}`},
227	{"range []int", "{{range .SI}}{{.}}{{end}}", noError,
228		`{{range .SI}}{{.}}{{end}}`},
229	{"range 1 var", "{{range $x := .SI}}{{.}}{{end}}", noError,
230		`{{range $x := .SI}}{{.}}{{end}}`},
231	{"range 2 vars", "{{range $x, $y := .SI}}{{.}}{{end}}", noError,
232		`{{range $x, $y := .SI}}{{.}}{{end}}`},
233	{"range with break", "{{range .SI}}{{.}}{{break}}{{end}}", noError,
234		`{{range .SI}}{{.}}{{break}}{{end}}`},
235	{"range with continue", "{{range .SI}}{{.}}{{continue}}{{end}}", noError,
236		`{{range .SI}}{{.}}{{continue}}{{end}}`},
237	{"constants", "{{range .SI 1 -3.2i true false 'a' nil}}{{end}}", noError,
238		`{{range .SI 1 -3.2i true false 'a' nil}}{{end}}`},
239	{"template", "{{template `x`}}", noError,
240		`{{template "x"}}`},
241	{"template with arg", "{{template `x` .Y}}", noError,
242		`{{template "x" .Y}}`},
243	{"with", "{{with .X}}hello{{end}}", noError,
244		`{{with .X}}"hello"{{end}}`},
245	{"with with else", "{{with .X}}hello{{else}}goodbye{{end}}", noError,
246		`{{with .X}}"hello"{{else}}"goodbye"{{end}}`},
247	{"with with else with", "{{with .X}}hello{{else with .Y}}goodbye{{end}}", noError,
248		`{{with .X}}"hello"{{else}}{{with .Y}}"goodbye"{{end}}{{end}}`},
249	{"with else chain", "{{with .X}}X{{else with .Y}}Y{{else with .Z}}Z{{end}}", noError,
250		`{{with .X}}"X"{{else}}{{with .Y}}"Y"{{else}}{{with .Z}}"Z"{{end}}{{end}}{{end}}`},
251	// Trimming spaces.
252	{"trim left", "x \r\n\t{{- 3}}", noError, `"x"{{3}}`},
253	{"trim right", "{{3 -}}\n\n\ty", noError, `{{3}}"y"`},
254	{"trim left and right", "x \r\n\t{{- 3 -}}\n\n\ty", noError, `"x"{{3}}"y"`},
255	{"trim with extra spaces", "x\n{{-  3   -}}\ny", noError, `"x"{{3}}"y"`},
256	{"comment trim left", "x \r\n\t{{- /* hi */}}", noError, `"x"`},
257	{"comment trim right", "{{/* hi */ -}}\n\n\ty", noError, `"y"`},
258	{"comment trim left and right", "x \r\n\t{{- /* */ -}}\n\n\ty", noError, `"x""y"`},
259	{"block definition", `{{block "foo" .}}hello{{end}}`, noError,
260		`{{template "foo" .}}`},
261
262	{"newline in assignment", "{{ $x \n := \n 1 \n }}", noError, "{{$x := 1}}"},
263	{"newline in empty action", "{{\n}}", hasError, "{{\n}}"},
264	{"newline in pipeline", "{{\n\"x\"\n|\nprintf\n}}", noError, `{{"x" | printf}}`},
265	{"newline in comment", "{{/*\nhello\n*/}}", noError, ""},
266	{"newline in comment", "{{-\n/*\nhello\n*/\n-}}", noError, ""},
267	{"spaces around continue", "{{range .SI}}{{.}}{{ continue }}{{end}}", noError,
268		`{{range .SI}}{{.}}{{continue}}{{end}}`},
269	{"spaces around break", "{{range .SI}}{{.}}{{ break }}{{end}}", noError,
270		`{{range .SI}}{{.}}{{break}}{{end}}`},
271
272	// Errors.
273	{"unclosed action", "hello{{range", hasError, ""},
274	{"unmatched end", "{{end}}", hasError, ""},
275	{"unmatched else", "{{else}}", hasError, ""},
276	{"unmatched else after if", "{{if .X}}hello{{end}}{{else}}", hasError, ""},
277	{"multiple else", "{{if .X}}1{{else}}2{{else}}3{{end}}", hasError, ""},
278	{"missing end", "hello{{range .x}}", hasError, ""},
279	{"missing end after else", "hello{{range .x}}{{else}}", hasError, ""},
280	{"undefined function", "hello{{undefined}}", hasError, ""},
281	{"undefined variable", "{{$x}}", hasError, ""},
282	{"variable undefined after end", "{{with $x := 4}}{{end}}{{$x}}", hasError, ""},
283	{"variable undefined in template", "{{template $v}}", hasError, ""},
284	{"declare with field", "{{with $x.Y := 4}}{{end}}", hasError, ""},
285	{"template with field ref", "{{template .X}}", hasError, ""},
286	{"template with var", "{{template $v}}", hasError, ""},
287	{"invalid punctuation", "{{printf 3, 4}}", hasError, ""},
288	{"multidecl outside range", "{{with $v, $u := 3}}{{end}}", hasError, ""},
289	{"too many decls in range", "{{range $u, $v, $w := 3}}{{end}}", hasError, ""},
290	{"dot applied to parentheses", "{{printf (printf .).}}", hasError, ""},
291	{"adjacent args", "{{printf 3`x`}}", hasError, ""},
292	{"adjacent args with .", "{{printf `x`.}}", hasError, ""},
293	{"extra end after if", "{{if .X}}a{{else if .Y}}b{{end}}{{end}}", hasError, ""},
294	{"break outside range", "{{range .}}{{end}} {{break}}", hasError, ""},
295	{"continue outside range", "{{range .}}{{end}} {{continue}}", hasError, ""},
296	{"break in range else", "{{range .}}{{else}}{{break}}{{end}}", hasError, ""},
297	{"continue in range else", "{{range .}}{{else}}{{continue}}{{end}}", hasError, ""},
298	// Other kinds of assignments and operators aren't available yet.
299	{"bug0a", "{{$x := 0}}{{$x}}", noError, "{{$x := 0}}{{$x}}"},
300	{"bug0b", "{{$x += 1}}{{$x}}", hasError, ""},
301	{"bug0c", "{{$x ! 2}}{{$x}}", hasError, ""},
302	{"bug0d", "{{$x % 3}}{{$x}}", hasError, ""},
303	// Check the parse fails for := rather than comma.
304	{"bug0e", "{{range $x := $y := 3}}{{end}}", hasError, ""},
305	// Another bug: variable read must ignore following punctuation.
306	{"bug1a", "{{$x:=.}}{{$x!2}}", hasError, ""},                     // ! is just illegal here.
307	{"bug1b", "{{$x:=.}}{{$x+2}}", hasError, ""},                     // $x+2 should not parse as ($x) (+2).
308	{"bug1c", "{{$x:=.}}{{$x +2}}", noError, "{{$x := .}}{{$x +2}}"}, // It's OK with a space.
309	// Check the range handles assignment vs. declaration properly.
310	{"bug2a", "{{range $x := 0}}{{$x}}{{end}}", noError, "{{range $x := 0}}{{$x}}{{end}}"},
311	{"bug2b", "{{range $x = 0}}{{$x}}{{end}}", noError, "{{range $x = 0}}{{$x}}{{end}}"},
312	// dot following a literal value
313	{"dot after integer", "{{1.E}}", hasError, ""},
314	{"dot after float", "{{0.1.E}}", hasError, ""},
315	{"dot after boolean", "{{true.E}}", hasError, ""},
316	{"dot after char", "{{'a'.any}}", hasError, ""},
317	{"dot after string", `{{"hello".guys}}`, hasError, ""},
318	{"dot after dot", "{{..E}}", hasError, ""},
319	{"dot after nil", "{{nil.E}}", hasError, ""},
320	// Wrong pipeline
321	{"wrong pipeline dot", "{{12|.}}", hasError, ""},
322	{"wrong pipeline number", "{{.|12|printf}}", hasError, ""},
323	{"wrong pipeline string", "{{.|printf|\"error\"}}", hasError, ""},
324	{"wrong pipeline char", "{{12|printf|'e'}}", hasError, ""},
325	{"wrong pipeline boolean", "{{.|true}}", hasError, ""},
326	{"wrong pipeline nil", "{{'c'|nil}}", hasError, ""},
327	{"empty pipeline", `{{printf "%d" ( ) }}`, hasError, ""},
328	// Missing pipeline in block
329	{"block definition", `{{block "foo"}}hello{{end}}`, hasError, ""},
330}
331
332var builtins = map[string]any{
333	"printf":   fmt.Sprintf,
334	"contains": strings.Contains,
335}
336
337func testParse(doCopy bool, t *testing.T) {
338	textFormat = "%q"
339	defer func() { textFormat = "%s" }()
340	for _, test := range parseTests {
341		tmpl, err := New(test.name).Parse(test.input, "", "", make(map[string]*Tree), builtins)
342		switch {
343		case err == nil && !test.ok:
344			t.Errorf("%q: expected error; got none", test.name)
345			continue
346		case err != nil && test.ok:
347			t.Errorf("%q: unexpected error: %v", test.name, err)
348			continue
349		case err != nil && !test.ok:
350			// expected error, got one
351			if *debug {
352				fmt.Printf("%s: %s\n\t%s\n", test.name, test.input, err)
353			}
354			continue
355		}
356		var result string
357		if doCopy {
358			result = tmpl.Root.Copy().String()
359		} else {
360			result = tmpl.Root.String()
361		}
362		if result != test.result {
363			t.Errorf("%s=(%q): got\n\t%v\nexpected\n\t%v", test.name, test.input, result, test.result)
364		}
365	}
366}
367
368func TestParse(t *testing.T) {
369	testParse(false, t)
370}
371
372// Same as TestParse, but we copy the node first
373func TestParseCopy(t *testing.T) {
374	testParse(true, t)
375}
376
377func TestParseWithComments(t *testing.T) {
378	textFormat = "%q"
379	defer func() { textFormat = "%s" }()
380	tests := [...]parseTest{
381		{"comment", "{{/*\n\n\n*/}}", noError, "{{/*\n\n\n*/}}"},
382		{"comment trim left", "x \r\n\t{{- /* hi */}}", noError, `"x"{{/* hi */}}`},
383		{"comment trim right", "{{/* hi */ -}}\n\n\ty", noError, `{{/* hi */}}"y"`},
384		{"comment trim left and right", "x \r\n\t{{- /* */ -}}\n\n\ty", noError, `"x"{{/* */}}"y"`},
385	}
386	for _, test := range tests {
387		t.Run(test.name, func(t *testing.T) {
388			tr := New(test.name)
389			tr.Mode = ParseComments
390			tmpl, err := tr.Parse(test.input, "", "", make(map[string]*Tree))
391			if err != nil {
392				t.Errorf("%q: expected error; got none", test.name)
393			}
394			if result := tmpl.Root.String(); result != test.result {
395				t.Errorf("%s=(%q): got\n\t%v\nexpected\n\t%v", test.name, test.input, result, test.result)
396			}
397		})
398	}
399}
400
401func TestKeywordsAndFuncs(t *testing.T) {
402	// Check collisions between functions and new keywords like 'break'. When a
403	// break function is provided, the parser should treat 'break' as a function,
404	// not a keyword.
405	textFormat = "%q"
406	defer func() { textFormat = "%s" }()
407
408	inp := `{{range .X}}{{break 20}}{{end}}`
409	{
410		// 'break' is a defined function, don't treat it as a keyword: it should
411		// accept an argument successfully.
412		var funcsWithKeywordFunc = map[string]any{
413			"break": func(in any) any { return in },
414		}
415		tmpl, err := New("").Parse(inp, "", "", make(map[string]*Tree), funcsWithKeywordFunc)
416		if err != nil || tmpl == nil {
417			t.Errorf("with break func: unexpected error: %v", err)
418		}
419	}
420
421	{
422		// No function called 'break'; treat it as a keyword. Results in a parse
423		// error.
424		tmpl, err := New("").Parse(inp, "", "", make(map[string]*Tree), make(map[string]any))
425		if err == nil || tmpl != nil {
426			t.Errorf("without break func: expected error; got none")
427		}
428	}
429}
430
431func TestSkipFuncCheck(t *testing.T) {
432	oldTextFormat := textFormat
433	textFormat = "%q"
434	defer func() { textFormat = oldTextFormat }()
435	tr := New("skip func check")
436	tr.Mode = SkipFuncCheck
437	tmpl, err := tr.Parse("{{fn 1 2}}", "", "", make(map[string]*Tree))
438	if err != nil {
439		t.Fatalf("unexpected error: %v", err)
440	}
441	expected := "{{fn 1 2}}"
442	if result := tmpl.Root.String(); result != expected {
443		t.Errorf("got\n\t%v\nexpected\n\t%v", result, expected)
444	}
445}
446
447type isEmptyTest struct {
448	name  string
449	input string
450	empty bool
451}
452
453var isEmptyTests = []isEmptyTest{
454	{"empty", ``, true},
455	{"nonempty", `hello`, false},
456	{"spaces only", " \t\n \t\n", true},
457	{"comment only", "{{/* comment */}}", true},
458	{"definition", `{{define "x"}}something{{end}}`, true},
459	{"definitions and space", "{{define `x`}}something{{end}}\n\n{{define `y`}}something{{end}}\n\n", true},
460	{"definitions and text", "{{define `x`}}something{{end}}\nx\n{{define `y`}}something{{end}}\ny\n", false},
461	{"definition and action", "{{define `x`}}something{{end}}{{if 3}}foo{{end}}", false},
462}
463
464func TestIsEmpty(t *testing.T) {
465	if !IsEmptyTree(nil) {
466		t.Errorf("nil tree is not empty")
467	}
468	for _, test := range isEmptyTests {
469		tree, err := New("root").Parse(test.input, "", "", make(map[string]*Tree), nil)
470		if err != nil {
471			t.Errorf("%q: unexpected error: %v", test.name, err)
472			continue
473		}
474		if empty := IsEmptyTree(tree.Root); empty != test.empty {
475			t.Errorf("%q: expected %t got %t", test.name, test.empty, empty)
476		}
477	}
478}
479
480func TestErrorContextWithTreeCopy(t *testing.T) {
481	tree, err := New("root").Parse("{{if true}}{{end}}", "", "", make(map[string]*Tree), nil)
482	if err != nil {
483		t.Fatalf("unexpected tree parse failure: %v", err)
484	}
485	treeCopy := tree.Copy()
486	wantLocation, wantContext := tree.ErrorContext(tree.Root.Nodes[0])
487	gotLocation, gotContext := treeCopy.ErrorContext(treeCopy.Root.Nodes[0])
488	if wantLocation != gotLocation {
489		t.Errorf("wrong error location want %q got %q", wantLocation, gotLocation)
490	}
491	if wantContext != gotContext {
492		t.Errorf("wrong error location want %q got %q", wantContext, gotContext)
493	}
494}
495
496// All failures, and the result is a string that must appear in the error message.
497var errorTests = []parseTest{
498	// Check line numbers are accurate.
499	{"unclosed1",
500		"line1\n{{",
501		hasError, `unclosed1:2: unclosed action`},
502	{"unclosed2",
503		"line1\n{{define `x`}}line2\n{{",
504		hasError, `unclosed2:3: unclosed action`},
505	{"unclosed3",
506		"line1\n{{\"x\"\n\"y\"\n",
507		hasError, `unclosed3:4: unclosed action started at unclosed3:2`},
508	{"unclosed4",
509		"{{\n\n\n\n\n",
510		hasError, `unclosed4:6: unclosed action started at unclosed4:1`},
511	{"var1",
512		"line1\n{{\nx\n}}",
513		hasError, `var1:3: function "x" not defined`},
514	// Specific errors.
515	{"function",
516		"{{foo}}",
517		hasError, `function "foo" not defined`},
518	{"comment1",
519		"{{/*}}",
520		hasError, `comment1:1: unclosed comment`},
521	{"comment2",
522		"{{/*\nhello\n}}",
523		hasError, `comment2:1: unclosed comment`},
524	{"lparen",
525		"{{.X (1 2 3}}",
526		hasError, `unclosed left paren`},
527	{"rparen",
528		"{{.X 1 2 3 ) }}",
529		hasError, "unexpected right paren"},
530	{"rparen2",
531		"{{(.X 1 2 3",
532		hasError, `unclosed action`},
533	{"space",
534		"{{`x`3}}",
535		hasError, `in operand`},
536	{"idchar",
537		"{{a#}}",
538		hasError, `'#'`},
539	{"charconst",
540		"{{'a}}",
541		hasError, `unterminated character constant`},
542	{"stringconst",
543		`{{"a}}`,
544		hasError, `unterminated quoted string`},
545	{"rawstringconst",
546		"{{`a}}",
547		hasError, `unterminated raw quoted string`},
548	{"number",
549		"{{0xi}}",
550		hasError, `number syntax`},
551	{"multidefine",
552		"{{define `a`}}a{{end}}{{define `a`}}b{{end}}",
553		hasError, `multiple definition of template`},
554	{"eof",
555		"{{range .X}}",
556		hasError, `unexpected EOF`},
557	{"variable",
558		// Declare $x so it's defined, to avoid that error, and then check we don't parse a declaration.
559		"{{$x := 23}}{{with $x.y := 3}}{{$x 23}}{{end}}",
560		hasError, `unexpected ":="`},
561	{"multidecl",
562		"{{$a,$b,$c := 23}}",
563		hasError, `too many declarations`},
564	{"undefvar",
565		"{{$a}}",
566		hasError, `undefined variable`},
567	{"wrongdot",
568		"{{true.any}}",
569		hasError, `unexpected . after term`},
570	{"wrongpipeline",
571		"{{12|false}}",
572		hasError, `non executable command in pipeline`},
573	{"emptypipeline",
574		`{{ ( ) }}`,
575		hasError, `missing value for parenthesized pipeline`},
576	{"multilinerawstring",
577		"{{ $v := `\n` }} {{",
578		hasError, `multilinerawstring:2: unclosed action`},
579	{"rangeundefvar",
580		"{{range $k}}{{end}}",
581		hasError, `undefined variable`},
582	{"rangeundefvars",
583		"{{range $k, $v}}{{end}}",
584		hasError, `undefined variable`},
585	{"rangemissingvalue1",
586		"{{range $k,}}{{end}}",
587		hasError, `missing value for range`},
588	{"rangemissingvalue2",
589		"{{range $k, $v := }}{{end}}",
590		hasError, `missing value for range`},
591	{"rangenotvariable1",
592		"{{range $k, .}}{{end}}",
593		hasError, `range can only initialize variables`},
594	{"rangenotvariable2",
595		"{{range $k, 123 := .}}{{end}}",
596		hasError, `range can only initialize variables`},
597}
598
599func TestErrors(t *testing.T) {
600	for _, test := range errorTests {
601		t.Run(test.name, func(t *testing.T) {
602			_, err := New(test.name).Parse(test.input, "", "", make(map[string]*Tree))
603			if err == nil {
604				t.Fatalf("expected error %q, got nil", test.result)
605			}
606			if !strings.Contains(err.Error(), test.result) {
607				t.Fatalf("error %q does not contain %q", err, test.result)
608			}
609		})
610	}
611}
612
613func TestBlock(t *testing.T) {
614	const (
615		input = `a{{block "inner" .}}bar{{.}}baz{{end}}b`
616		outer = `a{{template "inner" .}}b`
617		inner = `bar{{.}}baz`
618	)
619	treeSet := make(map[string]*Tree)
620	tmpl, err := New("outer").Parse(input, "", "", treeSet, nil)
621	if err != nil {
622		t.Fatal(err)
623	}
624	if g, w := tmpl.Root.String(), outer; g != w {
625		t.Errorf("outer template = %q, want %q", g, w)
626	}
627	inTmpl := treeSet["inner"]
628	if inTmpl == nil {
629		t.Fatal("block did not define template")
630	}
631	if g, w := inTmpl.Root.String(), inner; g != w {
632		t.Errorf("inner template = %q, want %q", g, w)
633	}
634}
635
636func TestLineNum(t *testing.T) {
637	// const count = 100
638	const count = 3
639	text := strings.Repeat("{{printf 1234}}\n", count)
640	tree, err := New("bench").Parse(text, "", "", make(map[string]*Tree), builtins)
641	if err != nil {
642		t.Fatal(err)
643	}
644	// Check the line numbers. Each line is an action containing a template, followed by text.
645	// That's two nodes per line.
646	nodes := tree.Root.Nodes
647	for i := 0; i < len(nodes); i += 2 {
648		line := 1 + i/2
649		// Action first.
650		action := nodes[i].(*ActionNode)
651		if action.Line != line {
652			t.Errorf("line %d: action is line %d", line, action.Line)
653		}
654		pipe := action.Pipe
655		if pipe.Line != line {
656			t.Errorf("line %d: pipe is line %d", line, pipe.Line)
657		}
658	}
659}
660
661func BenchmarkParseLarge(b *testing.B) {
662	text := strings.Repeat("{{1234}}\n", 10000)
663	for i := 0; i < b.N; i++ {
664		_, err := New("bench").Parse(text, "", "", make(map[string]*Tree), builtins)
665		if err != nil {
666			b.Fatal(err)
667		}
668	}
669}
670
671var sinkv, sinkl string
672
673func BenchmarkVariableString(b *testing.B) {
674	v := &VariableNode{
675		Ident: []string{"$", "A", "BB", "CCC", "THIS_IS_THE_VARIABLE_BEING_PROCESSED"},
676	}
677	b.ResetTimer()
678	b.ReportAllocs()
679	for i := 0; i < b.N; i++ {
680		sinkv = v.String()
681	}
682	if sinkv == "" {
683		b.Fatal("Benchmark was not run")
684	}
685}
686
687func BenchmarkListString(b *testing.B) {
688	text := `
689{{(printf .Field1.Field2.Field3).Value}}
690{{$x := (printf .Field1.Field2.Field3).Value}}
691{{$y := (printf $x.Field1.Field2.Field3).Value}}
692{{$z := $y.Field1.Field2.Field3}}
693{{if contains $y $z}}
694	{{printf "%q" $y}}
695{{else}}
696	{{printf "%q" $x}}
697{{end}}
698{{with $z.Field1 | contains "boring"}}
699	{{printf "%q" . | printf "%s"}}
700{{else}}
701	{{printf "%d %d %d" 11 11 11}}
702	{{printf "%d %d %s" 22 22 $x.Field1.Field2.Field3 | printf "%s"}}
703	{{printf "%v" (contains $z.Field1.Field2 $y)}}
704{{end}}
705`
706	tree, err := New("bench").Parse(text, "", "", make(map[string]*Tree), builtins)
707	if err != nil {
708		b.Fatal(err)
709	}
710	b.ResetTimer()
711	b.ReportAllocs()
712	for i := 0; i < b.N; i++ {
713		sinkl = tree.Root.String()
714	}
715	if sinkl == "" {
716		b.Fatal("Benchmark was not run")
717	}
718}
719