133728e barerepo 1mo
1
package httpd
133728e barerepo 1mo
3
import (
133728e barerepo 1mo
4
"html/template"
133728e barerepo 1mo
5
"reflect"
133728e barerepo 1mo
6
"testing"
133728e barerepo 1mo
7
"text/template/parse"
133728e barerepo 1mo
10
// A branch nobody renders hides a field nobody supplies, which published a private repository once.
133728e barerepo 1mo
11
func TestEveryTemplateFieldExistsOnItsData(t *testing.T) {
133728e barerepo 1mo
12
for name, c := range renderCases() {
133728e barerepo 1mo
13
page, ok := pages[name]
133728e barerepo 1mo
14
if !ok {
133728e barerepo 1mo
15
continue
133728e barerepo 1mo
17
// The walk starts at layout, because a page file is only its define blocks.
133728e barerepo 1mo
18
layout := page.Lookup("layout")
133728e barerepo 1mo
19
if layout == nil {
133728e barerepo 1mo
20
t.Errorf("%s has no layout", name)
133728e barerepo 1mo
21
continue
133728e barerepo 1mo
23
typ := reflect.TypeOf(c.data)
133728e barerepo 1mo
24
for _, field := range topLevelFields(layout.Tree, page) {
133728e barerepo 1mo
25
if !hasField(typ, field) {
133728e barerepo 1mo
26
t.Errorf("%s asks for .%s and its data has no such field, so a branch that "+
133728e barerepo 1mo
27
"reaches it renders nothing and says nothing", name, field)
133728e barerepo 1mo
33
// topLevelFields names every field read against the page's own dot, so range bodies are left alone.
133728e barerepo 1mo
34
func topLevelFields(tree *parse.Tree, page *template.Template) []string {
133728e barerepo 1mo
35
if tree == nil {
133728e barerepo 1mo
36
return nil
133728e barerepo 1mo
38
var out []string
133728e barerepo 1mo
39
var walk func(parse.Node)
133728e barerepo 1mo
40
walk = func(n parse.Node) {
133728e barerepo 1mo
41
switch t := n.(type) {
133728e barerepo 1mo
42
case nil:
133728e barerepo 1mo
43
return
133728e barerepo 1mo
44
case *parse.ListNode:
133728e barerepo 1mo
45
if t == nil {
133728e barerepo 1mo
46
return
133728e barerepo 1mo
48
for _, c := range t.Nodes {
133728e barerepo 1mo
49
walk(c)
133728e barerepo 1mo
51
case *parse.ActionNode:
133728e barerepo 1mo
52
walk(t.Pipe)
133728e barerepo 1mo
53
case *parse.IfNode:
133728e barerepo 1mo
54
walk(t.Pipe)
133728e barerepo 1mo
55
walk(t.List)
133728e barerepo 1mo
56
walk(t.ElseList)
133728e barerepo 1mo
57
case *parse.PipeNode:
133728e barerepo 1mo
58
if t == nil {
133728e barerepo 1mo
59
return
133728e barerepo 1mo
61
for _, c := range t.Cmds {
133728e barerepo 1mo
62
walk(c)
133728e barerepo 1mo
64
case *parse.CommandNode:
133728e barerepo 1mo
65
for _, a := range t.Args {
133728e barerepo 1mo
66
walk(a)
133728e barerepo 1mo
68
case *parse.FieldNode:
133728e barerepo 1mo
69
if len(t.Ident) > 0 {
133728e barerepo 1mo
70
out = append(out, t.Ident[0])
133728e barerepo 1mo
72
case *parse.TemplateNode:
133728e barerepo 1mo
73
// Only follow a template invoked with the same dot, since another argument is another type.
133728e barerepo 1mo
74
if isDot(t.Pipe) {
133728e barerepo 1mo
75
if inner := page.Lookup(t.Name); inner != nil {
133728e barerepo 1mo
76
out = append(out, topLevelFields(inner.Tree, page)...)
133728e barerepo 1mo
81
walk(tree.Root)
133728e barerepo 1mo
82
return out
133728e barerepo 1mo
85
// isDot reports whether a template was invoked with the dot it was already looking at.
133728e barerepo 1mo
86
func isDot(p *parse.PipeNode) bool {
133728e barerepo 1mo
87
if p == nil || len(p.Cmds) != 1 || len(p.Cmds[0].Args) != 1 {
133728e barerepo 1mo
88
return false
133728e barerepo 1mo
90
_, ok := p.Cmds[0].Args[0].(*parse.DotNode)
133728e barerepo 1mo
91
return ok
133728e barerepo 1mo
94
// hasField looks through embedded structs, which is how every page carries its chrome.
133728e barerepo 1mo
95
func hasField(typ reflect.Type, name string) bool {
133728e barerepo 1mo
96
if typ == nil {
133728e barerepo 1mo
97
return false
133728e barerepo 1mo
99
for typ.Kind() == reflect.Pointer {
133728e barerepo 1mo
100
typ = typ.Elem()
133728e barerepo 1mo
101
}
133728e barerepo 1mo
102
if typ.Kind() != reflect.Struct {
133728e barerepo 1mo
103
return false
133728e barerepo 1mo
104
}
133728e barerepo 1mo
105
if _, ok := typ.FieldByName(name); ok {
133728e barerepo 1mo
106
return true
133728e barerepo 1mo
107
}
133728e barerepo 1mo
108
return false