diff --git a/data.go b/data.go index ed49ca5..a41bd92 100644 --- a/data.go +++ b/data.go @@ -67,6 +67,10 @@ func loadDir(dir string) (*group, error) { if !strings.HasSuffix(e.Name(), ".json") { continue } + name := strings.TrimSuffix(e.Name(), ".json") + if isRef(name) { + return nil, fmt.Errorf("%s: category %q starts with %q, which is reserved for {..path} bindings", full, name, refPrefix) + } b, err := os.ReadFile(full) if err != nil { return nil, err @@ -79,7 +83,7 @@ func loadDir(dir string) (*group, error) { if err != nil { return nil, fmt.Errorf("%s: %w", full, err) } - g.children[strings.TrimSuffix(e.Name(), ".json")] = n + g.children[name] = n } return g, nil } diff --git a/node.go b/node.go index 6abcead..7f0f573 100644 --- a/node.go +++ b/node.go @@ -3,6 +3,7 @@ package fakes import ( "fmt" "math" + "sort" ) // node is a compiled template element: literal, choice, or template. Compiling @@ -106,11 +107,19 @@ func compileTemplate(m map[string]any) (node, error) { } } t := &template{format: format, fields: make(map[string]node, len(m)), repeat: repeat, separator: sep} - for k, v := range m { + keys := make([]string, 0, len(m)) + for k := range m { + keys = append(keys, k) + } + sort.Strings(keys) // so which of several bad fields is reported does not vary + for _, k := range keys { if k == "format" || k == "weight" || k == "repeat" || k == "separator" { continue } - n, err := compile(v) + if isRef(k) { + return nil, fmt.Errorf("field %q starts with %q, which is reserved for {..path} bindings", k, refPrefix) + } + n, err := compile(m[k]) if err != nil { return nil, fmt.Errorf("field %q: %w", k, err) } diff --git a/reference.go b/reference.go index f3bd7fe..89c8b7b 100644 --- a/reference.go +++ b/reference.go @@ -38,8 +38,7 @@ func linkRefs(root map[string]node) error { // walkNodes calls fn once per contained node, passing the dot path that reaches it, // visiting keys in sorted order so which of several broken nodes gets reported does -// not depend on map iteration. fn runs before a node's children, so a binding it -// adds to a template's fields is walked too. +// not depend on map iteration. func walkNodes(root map[string]node, fn func(path string, n node) error) error { seen := map[node]bool{} var visit func(string, node) error