check

package module
v0.1.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Dec 21, 2025 License: MIT Imports: 8 Imported by: 0

README

Check Go Reference

Check is a Go library for statically type-checking text/template and html/template. It helps catch template/type mismatches early, making refactoring safer when changing types or templates.

To use it, call Execute and provide:

  • a types.Type for the template’s data (.), and
  • the template’s parse.Tree.

See example_test.go for a working example.

Originally built as part of muxt, this package also powers the muxt check CLI command. If you only need command-line checks, use muxt check directly. Unlike muxt, which requires templates to be defined as global variables, this package lets you map templates to data parameters more flexibly (at the cost of some verbosity).

For a more robust and easier-to-configure alternative, consider jba/templatecheck.

Key Types and Functions

  • Global Holds type and template resolution state. Constructed with NewGlobal.

  • Execute Entry point to validate a template tree against a given types.Type.

  • TreeFinder / FindTreeFunc Resolves other templates by name (wrapping Template.Lookup).

  • Functions A set of callable template functions. Implements CallChecker.

    • Use DefaultFunctions(pkg *types.Package) to get the standard built-ins.
    • Extend with Functions.Add.
  • CallChecker Interface for validating function calls within templates.

Limitations

  1. Type required You must provide a types.Type that represents the template’s root context (.).

  2. Function sets Currently, default functions do not differentiate between text/template and html/template.

  3. Third-party template packages Compatibility with specialized template libraries (e.g. safehtml) has not been fully tested.

  4. Runtime-only errors Execute checks static type consistency but cannot detect runtime conditions such as out-of-range indexes. The standard library will try to dereference boxed types that may contain any type. Errors introduced by changes on a boxed type can not be caught by this package.

Documentation

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

func Execute added in v0.0.2

func Execute(global *Global, tree *parse.Tree, data types.Type) error
Example
package main

import (
	"fmt"
	"go/token"
	"log"
	"slices"
	"text/template"
	"text/template/parse"

	"golang.org/x/tools/go/packages"

	"github.com/typelate/check"
)

type Person struct {
	Name string
}

func main() {
	// 1. Load Go packages with type info.
	fset := token.NewFileSet()
	pkgs, err := packages.Load(&packages.Config{
		Fset:  fset,
		Tests: true,
		Mode: packages.NeedTypes |
			packages.NeedTypesInfo |
			packages.NeedSyntax |
			packages.NeedFiles |
			packages.NeedName |
			packages.NeedModule,
		Dir: ".",
	}, ".")
	if err != nil {
		log.Fatal(err)
	}
	const testPackageName = "check_test"
	packageIndex := slices.IndexFunc(pkgs, func(p *packages.Package) bool {
		return p.Name == testPackageName
	})
	if packageIndex < 0 {
		log.Fatalf("%s package not found", testPackageName)
	}
	testPackage := pkgs[packageIndex]

	// 2. Parse a template.
	tmpl, err := template.New("example").Parse(
		/* language=gotemplate */ `
{{define "unknown field" -}}
	{{.UnknownField}}
{{- end}}
{{define "known field" -}}
	Hello, {{.Name}}!
{{- end}}"
`)
	if err != nil {
		log.Fatalf("parse error: %v", err)
	}

	// 3. Create a TreeFinder (wraps Template.Lookup).
	treeFinder := check.FindTreeFunc(func(name string) (*parse.Tree, bool) {
		if named := tmpl.Lookup(name); named != nil {
			return named.Tree, true
		}
		return nil, false
	})

	// 4. Build a function checker.
	functions := check.DefaultFunctions(testPackage.Types)

	// 5. Initialize a Global.
	global := check.NewGlobal(testPackage.Types, fset, treeFinder, functions)

	// 6. Look up a type used by the template.
	personObj := testPackage.Types.Scope().Lookup("Person")
	if personObj == nil {
		log.Fatalf("type Person not found in %s", testPackage.PkgPath)
	}

	// 7. Type-check the template.
	{
		const templateName = "unknown field"
		if err := check.Execute(global, tmpl.Lookup("unknown field").Tree, personObj.Type()); err != nil {
			fmt.Println(err.Error())
		} else {
			fmt.Printf("template %q type-check passed\n", templateName)
		}
	}
	{
		const templateName = "known field"
		if err := check.Execute(global, tmpl.Lookup("known field").Tree, personObj.Type()); err != nil {
			fmt.Println(err.Error())
		} else {
			fmt.Printf("template %q type-check passed\n", templateName)
		}
	}
}
Output:

type check failed: example:3:3: executing "unknown field" at <.UnknownField>: UnknownField not found on github.com/typelate/check_test.Person
template "known field" type-check passed

Types

type CallChecker

type CallChecker interface {
	CheckCall(*Global, string, []parse.Node, []types.Type) (types.Type, error)
}

type Error added in v0.0.5

type Error struct {
	Tree *parse.Tree
	Node parse.Node
	// contains filtered or unexported fields
}

func (*Error) Error added in v0.0.5

func (e *Error) Error() string

func (*Error) Unwrap added in v0.0.5

func (e *Error) Unwrap() error

type FindTreeFunc

type FindTreeFunc func(name string) (*parse.Tree, bool)

func (FindTreeFunc) FindTree

func (fn FindTreeFunc) FindTree(name string) (*parse.Tree, bool)

type Functions

type Functions map[string]*types.Signature

func DefaultFunctions

func DefaultFunctions(pkg *types.Package) Functions

DefaultFunctions returns the standard functions defined in both html/template and text/template.

func (Functions) Add

func (functions Functions) Add(m Functions) Functions

func (Functions) CheckCall

func (functions Functions) CheckCall(global *Global, funcIdent string, argNodes []parse.Node, argTypes []types.Type) (types.Type, error)

type Global

type Global struct {
	InspectTemplateNode TemplateNodeInspectorFunc

	// Qualifier controls how types are printed in error messages.
	// If nil, types are printed with their full package path.
	// See types.WriteType for details.
	Qualifier types.Qualifier
	// contains filtered or unexported fields
}

func NewGlobal

func NewGlobal(pkg *types.Package, fileSet *token.FileSet, trees TreeFinder, fnChecker CallChecker) *Global

func (*Global) TypeString added in v0.1.0

func (g *Global) TypeString(typ types.Type) string

TypeString returns the string representation of typ using the configured Qualifier.

type TemplateNodeInspectorFunc added in v0.0.5

type TemplateNodeInspectorFunc func(t *parse.Tree, node *parse.TemplateNode, tp types.Type)

type TreeFinder

type TreeFinder interface {
	FindTree(name string) (*parse.Tree, bool)
}

TreeFinder should wrap https://pkg.go.dev/html/template#Template.Lookup and return the Tree field from the Template If you are using text/template the lookup function from that package should also work.

type TypeNodeMapping

type TypeNodeMapping map[types.Type][]parse.Node