From c1301f7a966cd61823c9b41988fed5fb5cc2521b Mon Sep 17 00:00:00 2001 From: Moshe Immerman Date: Fri, 13 Nov 2026 06:51:38 +0200 Subject: [PATCH] fix(texttemplate): satisfy golangci-lint in the forked upstream code and refresh conformance cases The fork brought upstream text/template code under this repo's golangci-lint rules, which failed main with 14 findings. Rename sentinel errors to errFoo, use camelCase test names, return error last, collapse an else-if, check an ignored Parse error, and use log.Panic in examples so deferred cleanup runs. Each change is tagged gomplate: lint and does not change behaviour. Regenerate conformance.json, which was stale after GO_TEMPLATE.md gained the values provider examples. Claude-Session-Id: 9cbaec31-655b-4670-a331-adddc1c8c5d2 --- texttemplate/README.md | 2 +- texttemplate/examplefiles_test.go | 33 +++--- texttemplate/exec.go | 14 +-- texttemplate/exec_test.go | 37 +++--- texttemplate/funcs.go | 6 +- .../lang/src/generated/conformance.json | 108 ++++++++++++++++++ 6 files changed, 156 insertions(+), 44 deletions(-) diff --git a/texttemplate/README.md b/texttemplate/README.md index 4922bd429..a373490e1 100644 --- a/texttemplate/README.md +++ b/texttemplate/README.md @@ -25,4 +25,4 @@ Options are per execution, so one parsed template can be shared by executions wi ## Upstream sync -Every deviation from upstream is tagged `gomplate:`; `rg "gomplate:" texttemplate` lists them. The additions themselves live in `values.go`. To sync, copy the upstream files over, re-apply the tagged hunks and run `go test ./texttemplate`. +Every deviation from upstream is tagged `gomplate:`; `rg "gomplate:" texttemplate` lists them. The additions themselves live in `values.go`. Changes tagged `gomplate: lint` only satisfy this repository's golangci-lint rules (error naming, camelCase, `else if`, no `log.Fatal` after `defer`) and do not change behaviour. To sync, copy the upstream files over, re-apply the tagged hunks and run `go test ./texttemplate`. diff --git a/texttemplate/examplefiles_test.go b/texttemplate/examplefiles_test.go index 8d3b22103..8bb303234 100644 --- a/texttemplate/examplefiles_test.go +++ b/texttemplate/examplefiles_test.go @@ -29,8 +29,11 @@ func createTestDir(files []templateFile) string { if err != nil { log.Fatal(err) } - defer f.Close() + // gomplate: lint, close before a fatal exit instead of deferring _, err = io.WriteString(f, file.contents) + if closeErr := f.Close(); err == nil { + err = closeErr + } if err != nil { log.Fatal(err) } @@ -51,7 +54,7 @@ func ExampleTemplate_glob() { // T2.tmpl defines a template T2. {"T2.tmpl", `{{define "T2"}}This is T2{{end}}`}, }) - // Clean up after the test; another quirk of running as an example. + // Clean up after the test; another quirk of running as an example. gomplate: lint, log.Panic below so this defer runs. defer os.RemoveAll(dir) // pattern is the glob pattern used to find all the template files. @@ -64,7 +67,7 @@ func ExampleTemplate_glob() { err := tmpl.Execute(os.Stdout, nil) if err != nil { - log.Fatalf("template execution: %s", err) + log.Panicf("template execution: %s", err) } // Output: // T0 invokes T1: (T1 invokes T2: (This is T2)) @@ -83,7 +86,7 @@ func ExampleTemplate_helpers() { // T2.tmpl defines a template T2. {"T2.tmpl", `{{define "T2"}}This is T2{{end}}`}, }) - // Clean up after the test; another quirk of running as an example. + // Clean up after the test; another quirk of running as an example. gomplate: lint, log.Panic below so this defer runs. defer os.RemoveAll(dir) // pattern is the glob pattern used to find all the template files. @@ -95,22 +98,22 @@ func ExampleTemplate_helpers() { // Add one driver template to the bunch; we do this with an explicit template definition. _, err := templates.Parse("{{define `driver1`}}Driver 1 calls T1: ({{template `T1`}})\n{{end}}") if err != nil { - log.Fatal("parsing driver1: ", err) + log.Panic("parsing driver1: ", err) } // Add another driver template. _, err = templates.Parse("{{define `driver2`}}Driver 2 calls T2: ({{template `T2`}})\n{{end}}") if err != nil { - log.Fatal("parsing driver2: ", err) + log.Panic("parsing driver2: ", err) } // We load all the templates before execution. This package does not require // that behavior but html/template's escaping does, so it's a good habit. err = templates.ExecuteTemplate(os.Stdout, "driver1", nil) if err != nil { - log.Fatalf("driver1 execution: %s", err) + log.Panicf("driver1 execution: %s", err) } err = templates.ExecuteTemplate(os.Stdout, "driver2", nil) if err != nil { - log.Fatalf("driver2 execution: %s", err) + log.Panicf("driver2 execution: %s", err) } // Output: // Driver 1 calls T1: (T1 invokes T2: (This is T2)) @@ -129,7 +132,7 @@ func ExampleTemplate_share() { // T1.tmpl defines a template, T1 that invokes T2. Note T2 is not defined {"T1.tmpl", `{{define "T1"}}T1 invokes T2: ({{template "T2"}}){{end}}`}, }) - // Clean up after the test; another quirk of running as an example. + // Clean up after the test; another quirk of running as an example. gomplate: lint, log.Panic below so this defer runs. defer os.RemoveAll(dir) // pattern is the glob pattern used to find all the template files. @@ -145,35 +148,35 @@ func ExampleTemplate_share() { // 1. Clone the helper set to create a new name space from which to run them. first, err := drivers.Clone() if err != nil { - log.Fatal("cloning helpers: ", err) + log.Panic("cloning helpers: ", err) } // 2. Define T2, version A, and parse it. _, err = first.Parse("{{define `T2`}}T2, version A{{end}}") if err != nil { - log.Fatal("parsing T2: ", err) + log.Panic("parsing T2: ", err) } // Now repeat the whole thing, using a different version of T2. // 1. Clone the drivers. second, err := drivers.Clone() if err != nil { - log.Fatal("cloning drivers: ", err) + log.Panic("cloning drivers: ", err) } // 2. Define T2, version B, and parse it. _, err = second.Parse("{{define `T2`}}T2, version B{{end}}") if err != nil { - log.Fatal("parsing T2: ", err) + log.Panic("parsing T2: ", err) } // Execute the templates in the reverse order to verify the // first is unaffected by the second. err = second.ExecuteTemplate(os.Stdout, "T0.tmpl", "second") if err != nil { - log.Fatalf("second execution: %s", err) + log.Panicf("second execution: %s", err) } err = first.ExecuteTemplate(os.Stdout, "T0.tmpl", "first") if err != nil { - log.Fatalf("first: execution: %s", err) + log.Panicf("first: execution: %s", err) } // Output: diff --git a/texttemplate/exec.go b/texttemplate/exec.go index 6457e4da6..64088c483 100644 --- a/texttemplate/exec.go +++ b/texttemplate/exec.go @@ -258,8 +258,8 @@ func (t *Template) DefinedTemplates() string { // Sentinel errors for use with panic to signal early exits from range loops. var ( - walkBreak = errors.New("break") - walkContinue = errors.New("continue") + errWalkBreak = errors.New("break") // gomplate: lint, renamed from walkBreak + errWalkContinue = errors.New("continue") // gomplate: lint, renamed from walkContinue ) // Walk functions step through the major pieces of the template structure, @@ -275,10 +275,10 @@ func (s *state) walk(dot reflect.Value, node parse.Node) { s.printValue(node, s.consume(val)) // gomplate: consume } case *parse.BreakNode: - panic(walkBreak) + panic(errWalkBreak) case *parse.CommentNode: case *parse.ContinueNode: - panic(walkContinue) + panic(errWalkContinue) case *parse.IfNode: s.walkIfOrWith(parse.NodeIf, dot, node.Pipe, node.List, node.ElseList) case *parse.ListNode: @@ -358,7 +358,7 @@ func isTrue(val reflect.Value) (truth, ok bool) { func (s *state) walkRange(dot reflect.Value, r *parse.RangeNode) { s.at(r) defer func() { - if r := recover(); r != nil && r != walkBreak { + if r := recover(); r != nil && r != errWalkBreak { panic(r) } }() @@ -393,8 +393,8 @@ func (s *state) walkRange(dot reflect.Value, r *parse.RangeNode) { } defer s.pop(mark) defer func() { - // Consume panic(walkContinue) - if r := recover(); r != nil && r != walkContinue { + // Consume panic(errWalkContinue) + if r := recover(); r != nil && r != errWalkContinue { panic(r) } }() diff --git a/texttemplate/exec_test.go b/texttemplate/exec_test.go index ed2245df4..6c6293905 100644 --- a/texttemplate/exec_test.go +++ b/texttemplate/exec_test.go @@ -87,7 +87,7 @@ type T struct { ErrFunc func() (string, error) PanicFunc func() string TooFewReturnCountFunc func() - TooManyReturnCountFunc func() (string, error, int) + TooManyReturnCountFunc func() (string, int, error) // gomplate: lint, error last InvalidReturnTypeFunc func() (string, bool) // Template to test evaluation of templates. Tmpl *Template @@ -179,7 +179,7 @@ var tVal = &T{ ErrFunc: func() (string, error) { return "bla", nil }, PanicFunc: func() string { panic("test panic") }, TooFewReturnCountFunc: func() {}, - TooManyReturnCountFunc: func() (string, error, int) { return "", nil, 0 }, + TooManyReturnCountFunc: func() (string, int, error) { return "", 0, nil }, InvalidReturnTypeFunc: func() (string, bool) { return "", false }, Tmpl: Must(New("x").Parse("test template")), // "x" is the value of .X } @@ -244,12 +244,12 @@ func (t *T) MAdd(a int, b []int) []int { return v } -var myError = errors.New("my error") +var errMy = errors.New("my error") // gomplate: lint, renamed from myError // MyError returns a value and an error according to its argument. func (t *T) MyError(error bool) (bool, error) { if error { - return true, myError + return true, errMy } return false, nil } @@ -975,11 +975,11 @@ func TestExecuteError(t *testing.T) { err = tmpl.Execute(b, tVal) if err == nil { t.Errorf("expected error; got none") - } else if !strings.Contains(err.Error(), myError.Error()) { + } else if !strings.Contains(err.Error(), errMy.Error()) { if *debug { fmt.Printf("test execute error: %s\n", err) } - t.Errorf("expected myError; got %s", err) + t.Errorf("expected errMy; got %s", err) } } @@ -1439,12 +1439,12 @@ func TestUnterminatedStringError(t *testing.T) { const alwaysErrorText = "always be failing" -var alwaysError = errors.New(alwaysErrorText) +var errAlways = errors.New(alwaysErrorText) // gomplate: lint, renamed from alwaysError type ErrorWriter int func (e ErrorWriter) Write(p []byte) (int, error) { - return 0, alwaysError + return 0, errAlways } func TestExecuteGivesExecError(t *testing.T) { @@ -1515,8 +1515,8 @@ func TestBadFuncNames(t *testing.T) { } func TestIsTrue(t *testing.T) { - var nil_ptr *int - var nil_chan chan int + var nilPtr *int // gomplate: lint, camelCase names in TestIsTrue + var nilChan chan int tests := []struct { v any want bool @@ -1538,16 +1538,16 @@ func TestIsTrue(t *testing.T) { {map[string]int{"a": 1, "b": 2}, true}, {map[string]int{}, false}, {make(chan int), true}, - {nil_chan, false}, + {nilChan, false}, {new(int), true}, - {nil_ptr, false}, + {nilPtr, false}, {unsafe.Pointer(new(int)), true}, - {unsafe.Pointer(nil_ptr), false}, + {unsafe.Pointer(nilPtr), false}, } - for _, test_case := range tests { - got, _ := IsTrue(test_case.v) - if got != test_case.want { - t.Fatalf("expect result %v, got %v", test_case.want, got) + for _, testCase := range tests { + got, _ := IsTrue(testCase.v) + if got != testCase.want { + t.Fatalf("expect result %v, got %v", testCase.want, got) } } } @@ -1937,6 +1937,9 @@ func TestIssue31810(t *testing.T) { // Works if the function is explicitly called. const textCall = "{{ (call .) }}" tmpl, err = New("").Parse(textCall) + if err != nil { // gomplate: lint + t.Fatal(err) + } b.Reset() err = tmpl.Execute(&b, f) if err != nil { diff --git a/texttemplate/funcs.go b/texttemplate/funcs.go index 6da854181..9a7d09b91 100644 --- a/texttemplate/funcs.go +++ b/texttemplate/funcs.go @@ -329,10 +329,8 @@ func call(name string, fn reflect.Value, args ...reflect.Value) (reflect.Value, return reflect.Value{}, fmt.Errorf("wrong number of args for %s: got %d want at least %d", name, len(args), numIn-1) } dddType = typ.In(numIn - 1).Elem() - } else { - if len(args) != numIn { - return reflect.Value{}, fmt.Errorf("wrong number of args for %s: got %d want %d", name, len(args), numIn) - } + } else if len(args) != numIn { // gomplate: lint + return reflect.Value{}, fmt.Errorf("wrong number of args for %s: got %d want %d", name, len(args), numIn) } argv := make([]reflect.Value, len(args)) for i, arg := range args { diff --git a/web/packages/lang/src/generated/conformance.json b/web/packages/lang/src/generated/conformance.json index 9ffda4875..7a6806ac2 100644 --- a/web/packages/lang/src/generated/conformance.json +++ b/web/packages/lang/src/generated/conformance.json @@ -1090,6 +1090,19 @@ ], "origin": "README.md" }, + { + "language": "cel", + "source": "ValuesProvider: redacting{},", + "boundaries": [ + 0, + 14, + 16, + 25, + 26, + 27 + ], + "origin": "GO_TEMPLATE.md" + }, { "language": "cel", "source": "[\"a\", \"b\", \"a\", \"c\"].distinct()", @@ -2120,6 +2133,38 @@ ], "origin": "CEL.md" }, + { + "language": "cel", + "source": "func (p redacting) Field(receiver reflect.Value, name string) (reflect.Value, bool, error) {", + "boundaries": [ + 0, + 5, + 6, + 8, + 17, + 19, + 24, + 25, + 34, + 41, + 42, + 47, + 49, + 54, + 60, + 62, + 63, + 70, + 71, + 76, + 78, + 82, + 84, + 89, + 91 + ], + "origin": "GO_TEMPLATE.md" + }, { "language": "cel", "source": "gomplate.Template{Expression: \"person.display_name\"},", @@ -2149,6 +2194,18 @@ ], "origin": "CEL.md" }, + { + "language": "cel", + "source": "if name == \"password\" {", + "boundaries": [ + 0, + 3, + 8, + 11, + 22 + ], + "origin": "GO_TEMPLATE.md" + }, { "language": "cel", "source": "import \"github.com/flanksource/gomplate/v4\"", @@ -3978,6 +4035,42 @@ ], "origin": "CEL.md" }, + { + "language": "cel", + "source": "return p.DefaultValuesProvider.Field(receiver, name)", + "boundaries": [ + 0, + 7, + 8, + 9, + 30, + 31, + 36, + 37, + 45, + 47, + 51 + ], + "origin": "GO_TEMPLATE.md" + }, + { + "language": "cel", + "source": "return reflect.ValueOf(\"****\"), true, nil", + "boundaries": [ + 0, + 7, + 14, + 15, + 22, + 23, + 29, + 30, + 32, + 36, + 38 + ], + "origin": "GO_TEMPLATE.md" + }, { "language": "cel", "source": "script: \u003e", @@ -4495,6 +4588,21 @@ ], "origin": "CEL.md" }, + { + "language": "cel", + "source": "type redacting struct{ gomplate.DefaultValuesProvider }", + "boundaries": [ + 0, + 5, + 15, + 21, + 23, + 31, + 32, + 54 + ], + "origin": "GO_TEMPLATE.md" + }, { "language": "cel", "source": "type(\"hello\")",