Documentation
¶
Index ¶
- func Execute(global *Global, tree *parse.Tree, data types.Type) error
- func Package(pkg *packages.Package, inspectCall ExecuteTemplateNodeInspectorFunc, ...) error
- type CallChecker
- type DeferredCall
- type Error
- type ExecuteTemplateNodeInspectorFunc
- type FindTreeFunc
- type Functions
- type Global
- type PackageWarningFunc
- type TemplateNodeInspectorFunc
- type TreeFinder
- type TypeNodeMapping
- type WarningCategory
- type WarningFunc
Examples ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func Execute ¶
Example ¶
package main
import (
"fmt"
"go/token"
"log"
"slices"
"text/template"
"text/template/parse"
"golang.org/x/tools/go/packages"
"github.com/tooolbox/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: example:3:3: executing "unknown field" at <.UnknownField>: UnknownField not found on github.com/tooolbox/check_test.Person (E001) template "known field" type-check passed
func Package ¶
func Package(pkg *packages.Package, inspectCall ExecuteTemplateNodeInspectorFunc, inspectTemplate TemplateNodeInspectorFunc, warn PackageWarningFunc) error
Package discovers all .ExecuteTemplate calls in the given package, resolves receiver variables to their template construction chains, and type-checks each call.
ExecuteTemplate must be called with a string literal for the second parameter. If warn is non-nil, it is called for non-fatal issues such as unused templates or unguarded pointer access.
Types ¶
type CallChecker ¶
type DeferredCall ¶
type DeferredCall struct {
// FuncObj is the exported function that wraps the ExecuteTemplate call.
FuncObj types.Object
// NameParamIdx is the parameter index providing the template name (-1 if resolved).
NameParamIdx int
// DataParamIdx is the parameter index providing the data (-1 if resolved).
DataParamIdx int
// ReceiverParamIdx is the parameter index providing the template receiver (-1 if resolved).
ReceiverParamIdx int
// contains filtered or unexported fields
}
DeferredCall represents an ExecuteTemplate call whose template name or data type could not be resolved within its own package. Callers from other packages may provide the concrete values via call-graph tracing.
func PackageWithDeferred ¶
func PackageWithDeferred(pkg *packages.Package, inspectCall ExecuteTemplateNodeInspectorFunc, inspectTemplate TemplateNodeInspectorFunc, warn PackageWarningFunc, imported []DeferredCall, allPkgs []*packages.Package) ([]DeferredCall, error)
PackageWithDeferred is like Package but also accepts deferred calls from dependency packages and returns any new deferred calls discovered in this package. This enables cross-package call-graph tracing. allPkgs, if provided, enables cross-package parameter resolution for template construction tracing.
type FindTreeFunc ¶
type Functions ¶
func DefaultFunctions ¶
DefaultFunctions returns the standard functions defined in html/template and text/template. It looks up escape functions (js, html, urlquery) from whichever template package is imported.
type Global ¶
type Global struct {
InspectTemplateNode TemplateNodeInspectorFunc
InspectCallNode ExecuteTemplateNodeInspectorFunc
Warn WarningFunc
// 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
type PackageWarningFunc ¶
type PackageWarningFunc func(category WarningCategory, pos token.Position, message string)
PackageWarningFunc is called when a non-fatal issue is detected. The category identifies the warning type, allowing callers to filter.
type TreeFinder ¶
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 WarningCategory ¶
type WarningCategory int
WarningCategory identifies the kind of warning.
const ( // WarnNonStaticTemplateName indicates an ExecuteTemplate call with a // non-static string for the template name. WarnNonStaticTemplateName WarningCategory = iota + 1 // WarnUnusedTemplate indicates a template that is defined but never // referenced by any ExecuteTemplate call or {{template}} action. WarnUnusedTemplate // WarnNilDereference indicates field access on a pointer type without // a nil guard ({{with}} or {{if}}). WarnNilDereference // WarnInterfaceFieldAccess indicates field access on an interface type // that cannot be statically verified. WarnInterfaceFieldAccess // WarnUnusedVariable indicates a template variable that is declared // (via $x := ...) but never referenced. WarnUnusedVariable // WarnDeadBranch indicates a conditional branch that can never execute // because the condition is a literal true, false, or nil constant. WarnDeadBranch // WarnInconsistentTemplateTypes indicates that a sub-template is // invoked from multiple {{template}} call sites with incompatible // data types, which will produce different runtime behaviour depending // on the caller. WarnInconsistentTemplateTypes )
func (WarningCategory) Code ¶
func (c WarningCategory) Code() string
Code returns the short diagnostic code for the warning category (e.g. "W001").
type WarningFunc ¶
WarningFunc is called when a non-fatal issue is detected during type-checking, such as field access on an interface type or unguarded pointer dereference.