Documentation ¶
Overview ¶
Package templatecheck checks Go templates for problems. It can detect many errors that are normally caught only during execution. Use templatecheck to find template errors early, and along template execution paths that might only rarely be reached.
Type-Checked Templates ¶
Given a template t from one of the text/template, html/template, or github.com/google/safehtml/template packages, calling NewChecked[T](t) type-checks t and returns a CheckedTemplate[T]. The only things you can do with a CheckedTemplate[T] are get its name, and execute it with data of type T.
The execution will be free of type errors. To make that guarantee, the semantics of templates are restricted. CheckHTMLStrict has details, but the general idea is that types must be known so they can be checked. For example, the template
{{.F}}
will succeed as long as dot contains a field or zero-argument method named "F". The non-strict CheckXXX functions of this package will check that if the type of dot is known, but if it is unknown or an interface type, they will not complain, since template execution might succeed. By contrast, strict checking will fail unless it can be sure that the reference to "F" is valid, implying that it must know the type of dot.
When all of a program's templates are CheckedTemplates, it is not necessary to write tests to check templates as described below. All type-checking happens when the CheckedTemplates are created.
Checks ¶
The rest of this documentation describes the original templatecheck mechanism, accessed through the CheckXXX functions. It may still be useful, especially for existing programs, but CheckedTemplate[T] is preferable because it is safer.
A template must be invoked with the same Go type each time to use the templatecheck.CheckXXX functions. Passing that type to templatecheck gives it enough information to verify that all the field references in the template are valid. templatecheck can also verify that functions are called with the right number and types of arguments, that the argument to a range statement can actually be ranged over, and a few other things.
Consider a web server that parses a template for its home page:
import "html/template" var tmpl = template.Must(template.ParseFiles("index.tmpl")) type homePage struct { ... } func handler(w http.ResponseWriter, r *http.Request) { ... var buf bytes.Buffer err := tmpl.Execute(&buf, homePage{...}) ... }
Use templatecheck to catch errors in tests, instead of during serving:
func TestTemplates(t *testing.T) { if err := templatecheck.CheckHTML(tmpl, homePage{}); err != nil { t.Fatal(err) } }
Checking Associated Templates ¶
To check associated templates, use Template.Lookup. This can be necessary for non-strict checking if full type information isn't available to the main template. (It isn't needed for strict checking, because all calls to associated templates are type-checked.)
For example, here the base template is always invoked with a basePage, but the type of its Details field differs depending on the value of IsTop.
type basePage struct { IsTop bool Details any } type topDetails ... type bottomDetails ...
The template text is
{{if .IsTop}} {{template "top" .Details}} {{else}} {{template "bottom" .Details}} {{end}} {{define "top"}}...{{end}} {{define "bottom"}}...{{end}}
Checking only the main template will not provide much information about the two associated templates, because their data types are unknown. All three templates should be checked, like so:
t := template.Must(template.New("").Parse(base)) if err := templatecheck.CheckText(t, basePage{}) ... if err := templatecheck.CheckText(t.Lookup("top"), topDetails{}) ... if err := templatecheck.CheckText(t.Lookup("bottom"), bottomDetails{}) ...
Example ¶
package main import ( "fmt" "text/template" "github.com/jba/templatecheck" ) func main() { type data struct { Greeting string Name string } const contents = `{{.Greetings}}, {{.Name}} and welcome!` t := template.Must(template.New("greet").Parse(contents)) err := templatecheck.CheckText(t, data{}) fmt.Println(err) }
Output: template: greet:1:2: checking "greet" at <.Greetings>: can't use field Greetings in type templatecheck_test.data
Index ¶
- func CheckHTML(t *htmpl.Template, typeValue any) error
- func CheckHTMLStrict(t *htmpl.Template, typeValue any) error
- func CheckSafe(t *stmpl.Template, typeValue any) error
- func CheckSafeStrict(t *stmpl.Template, typeValue any) error
- func CheckText(t *ttmpl.Template, typeValue any) error
- func CheckTextStrict(t *ttmpl.Template, typeValue any) error
- type CheckedTemplate
Examples ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func CheckHTML ¶
CheckHTML checks an html/template for problems. The second argument is the type of dot passed to template.Execute.
CheckHTML assumes that the "missingkey" option for the template is "zero", meaning that a missing key in a map returns the zero value for the map's element type. This is not the default value for "missingkey", but it allows more checks.
func CheckHTMLStrict ¶ added in v0.7.0
CheckHTMLStrict checks an html/template for problems. The second argument is the type of dot passed to template.Execute.
CheckHTMLStrict makes the same assumption about the "missingkey" option as CheckHTML does.
CheckHTMLStrict guarantees that no type errors will occur if t is executed with a value of the given type. To do so, it restricts the semantics of the template language as follows:
- All calls to templates created with "define" or "block" must pass the same type for dot.
- Variables have a consistent type, determined when the variable is declared with ":=". If a variable is assigned a new value, the value's type must be assignable to the type used to declare it.
- All types in an expression must be known. That includes field access, function calls and arguments to "range".
- If the "and" or "or" functions are used for their value, then all their arguments must be of the same type. That restriction does not apply if they are used as an argument to "if", where only the truth value of the result matters.
func CheckSafe ¶
CheckSafe checks a github.com/google/safehtml/template for problems. See CheckHTML for details.
func CheckSafeStrict ¶ added in v0.7.0
CheckSafeStrict does strict checking. See CheckHTMLStrict for details.
func CheckTextStrict ¶ added in v0.7.0
CheckTextStrict does strict checking. See CheckHTMLStrict for details.
Types ¶
type CheckedTemplate ¶ added in v0.7.0
type CheckedTemplate[T any] struct { // contains filtered or unexported fields }
func MustChecked ¶ added in v0.7.0
func MustChecked[T any, M tmpl[M]](t M) *CheckedTemplate[T]
MustChecked is like NewChecked, but panics if type-checking fails.
func NewChecked ¶ added in v0.7.0
func NewChecked[T any, M tmpl[M]](t M) (*CheckedTemplate[T], error)
NewChecked type-checks t using one of the CheckXXXStrict functions. If the check succeeds, it returns a CheckedTemplate[T] that behaves like the argument template.
Subsequent changes to the argument template do not affect the CheckedTemplate.
func (*CheckedTemplate[T]) Execute ¶ added in v0.7.0
func (t *CheckedTemplate[T]) Execute(wr io.Writer, data T) error
Execute behaves like Template.Execute.
func (*CheckedTemplate[T]) Name ¶ added in v0.7.0
func (t *CheckedTemplate[T]) Name() string
Name returns the name of the template.