mirror of
https://github.com/sbrow/thor.git
synced 2026-08-26 11:23:32 -04:00
feat: Added Rust-style error messages for template errors.
This commit is contained in:
+236
-44
@@ -1,28 +1,27 @@
|
||||
package mustache
|
||||
|
||||
import "core:fmt"
|
||||
import "core:log"
|
||||
import "core:strings"
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Error types
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
// TODO: I don't think this is the best way to do our errors.
|
||||
Syntax_Error :: struct {
|
||||
msg: string,
|
||||
pos: int,
|
||||
}
|
||||
|
||||
Data_Error :: struct {
|
||||
msg: string,
|
||||
}
|
||||
Partial_Error :: struct {
|
||||
name: string,
|
||||
msg: string,
|
||||
pos: int,
|
||||
}
|
||||
|
||||
Render_Error :: union {
|
||||
Syntax_Error,
|
||||
Data_Error,
|
||||
Partial_Error,
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -50,6 +49,7 @@ Node :: struct {
|
||||
first_child: int,
|
||||
child_count: int,
|
||||
content: string,
|
||||
pos: int,
|
||||
}
|
||||
|
||||
// node_span returns the number of flat-array entries a node occupies:
|
||||
@@ -67,12 +67,14 @@ node_span :: proc(n: Node) -> int {
|
||||
Template :: struct {
|
||||
nodes: [dynamic]Node,
|
||||
source: string,
|
||||
path: string,
|
||||
}
|
||||
|
||||
Block_Override :: struct {
|
||||
all_nodes: []Node,
|
||||
first: int,
|
||||
count: int,
|
||||
source: Template,
|
||||
}
|
||||
|
||||
delete_template :: proc(tmpl: ^Template) {
|
||||
@@ -94,6 +96,7 @@ delete_partials :: proc(partials: map[string]Template) {
|
||||
|
||||
parse :: proc(
|
||||
source: string,
|
||||
path := "",
|
||||
allocator := context.allocator,
|
||||
tokens_allocator := context.temp_allocator,
|
||||
) -> (
|
||||
@@ -111,6 +114,7 @@ parse :: proc(
|
||||
return {}, err
|
||||
}
|
||||
tmpl.source = source
|
||||
tmpl.path = path
|
||||
deindent_blocks(tmpl.nodes[:], 0, len(tmpl.nodes), allocator)
|
||||
return tmpl, nil
|
||||
}
|
||||
@@ -133,7 +137,7 @@ render :: proc(
|
||||
append(&ctx, data)
|
||||
|
||||
all_nodes := tmpl.nodes[:]
|
||||
err = render_nodes(all_nodes, all_nodes, &ctx, partials, &builder)
|
||||
err = render_nodes(tmpl, all_nodes, all_nodes, &ctx, partials, &builder)
|
||||
if err != nil {
|
||||
return result, err
|
||||
}
|
||||
@@ -158,7 +162,7 @@ parse_tokens :: proc(
|
||||
) {
|
||||
nodes = make([dynamic]Node, 0, len(tokens), allocator)
|
||||
pos := 0
|
||||
err = parse_section(tokens, &pos, &nodes, "", source, allocator)
|
||||
err = parse_section(tokens, &pos, &nodes, "", source, allocator, 0)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -169,22 +173,23 @@ parse_section :: proc(
|
||||
end_tag: string,
|
||||
source: string,
|
||||
allocator := context.allocator,
|
||||
open_pos: int = 0,
|
||||
) -> Render_Error {
|
||||
for pos^ < len(tokens) {
|
||||
tok := tokens[pos^]
|
||||
|
||||
switch tok.kind {
|
||||
case .Text:
|
||||
append(nodes, Node{kind = .Text, text = tok.value, first_child = -1})
|
||||
append(nodes, Node{kind = .Text, text = tok.value, first_child = -1, pos = tok.pos})
|
||||
pos^ += 1
|
||||
|
||||
case .Variable:
|
||||
idx := len(nodes)
|
||||
append(nodes, Node{kind = .Variable, first_child = -1})
|
||||
pipe_key, perr := parse_pipeline(tok.value, &nodes[idx].filters)
|
||||
pipe_key, perr := parse_pipeline(tok.value, &nodes[idx].filters, tok.pos)
|
||||
if perr != nil {
|
||||
return Syntax_Error {
|
||||
msg = fmt.tprintf("pipe parse error in '{{%s}}': %v", tok.value, perr),
|
||||
msg = fmt.tprintf("pipe parse error in '{{{{%s}}}}': %v", tok.value, perr),
|
||||
pos = tok.pos,
|
||||
}
|
||||
}
|
||||
@@ -194,10 +199,10 @@ parse_section :: proc(
|
||||
case .Unescaped:
|
||||
idx := len(nodes)
|
||||
append(nodes, Node{kind = .Unescaped, first_child = -1})
|
||||
pipe_key, perr := parse_pipeline(tok.value, &nodes[idx].filters)
|
||||
pipe_key, perr := parse_pipeline(tok.value, &nodes[idx].filters, tok.pos)
|
||||
if perr != nil {
|
||||
return Syntax_Error {
|
||||
msg = fmt.tprintf("pipe parse error in '{{&%s}}': %v", tok.value, perr),
|
||||
msg = fmt.tprintf("pipe parse error in '{{{{&%s}}}}': %v", tok.value, perr),
|
||||
pos = tok.pos,
|
||||
}
|
||||
}
|
||||
@@ -212,16 +217,16 @@ parse_section :: proc(
|
||||
idx := len(nodes)
|
||||
content_start := 0
|
||||
if pos^ < len(tokens) {content_start = tokens[pos^].pos}
|
||||
append(nodes, Node{kind = .Section, first_child = -1})
|
||||
pipe_key, perr := parse_pipeline(tok.value, &nodes[idx].filters)
|
||||
append(nodes, Node{kind = .Section, first_child = -1, pos = tok.pos})
|
||||
pipe_key, perr := parse_pipeline(tok.value, &nodes[idx].filters, tok.pos)
|
||||
if perr != nil {
|
||||
return Syntax_Error {
|
||||
msg = fmt.tprintf("pipe parse error in '{{#%s}}': %v", tok.value, perr),
|
||||
msg = fmt.tprintf("pipe parse error in '{{{{#%s}}}}': %v", tok.value, perr),
|
||||
pos = tok.pos,
|
||||
}
|
||||
}
|
||||
nodes[idx].key = pipe_key
|
||||
parse_section(tokens, pos, nodes, pipe_key, source, allocator) or_return
|
||||
parse_section(tokens, pos, nodes, pipe_key, source, allocator, tok.pos) or_return
|
||||
close_pos := 0
|
||||
if pos^ - 1 >= 0 && pos^ - 1 < len(tokens) {close_pos = tokens[pos^ - 1].pos}
|
||||
nodes[idx].first_child = idx + 1
|
||||
@@ -233,16 +238,16 @@ parse_section :: proc(
|
||||
idx := len(nodes)
|
||||
content_start := 0
|
||||
if pos^ < len(tokens) {content_start = tokens[pos^].pos}
|
||||
append(nodes, Node{kind = .Inverted, first_child = -1})
|
||||
pipe_key, perr := parse_pipeline(tok.value, &nodes[idx].filters)
|
||||
append(nodes, Node{kind = .Inverted, first_child = -1, pos = tok.pos})
|
||||
pipe_key, perr := parse_pipeline(tok.value, &nodes[idx].filters, tok.pos)
|
||||
if perr != nil {
|
||||
return Syntax_Error {
|
||||
msg = fmt.tprintf("pipe parse error in '{{^%s}}': %v", tok.value, perr),
|
||||
msg = fmt.tprintf("pipe parse error in '{{{{^%s}}}}': %v", tok.value, perr),
|
||||
pos = tok.pos,
|
||||
}
|
||||
}
|
||||
nodes[idx].key = pipe_key
|
||||
parse_section(tokens, pos, nodes, pipe_key, source, allocator) or_return
|
||||
parse_section(tokens, pos, nodes, pipe_key, source, allocator, tok.pos) or_return
|
||||
close_pos := 0
|
||||
if pos^ - 1 >= 0 && pos^ - 1 < len(tokens) {close_pos = tokens[pos^ - 1].pos}
|
||||
nodes[idx].first_child = idx + 1
|
||||
@@ -253,7 +258,7 @@ parse_section :: proc(
|
||||
if strings.contains(tok.value, "|") {
|
||||
return Syntax_Error {
|
||||
msg = fmt.tprintf(
|
||||
"pipe expression not allowed in close tag '{{/%s}}' — use the bare key",
|
||||
"pipe expression not allowed in close tag '{{{{/%s}}}}' — use the bare key",
|
||||
tok.value,
|
||||
),
|
||||
pos = tok.pos,
|
||||
@@ -265,12 +270,12 @@ parse_section :: proc(
|
||||
}
|
||||
if end_tag == "" {
|
||||
return Syntax_Error {
|
||||
msg = fmt.tprintf("unexpected {{/%s}}", tok.value),
|
||||
msg = fmt.tprintf("unexpected {{{{/%s}}}}", tok.value),
|
||||
pos = tok.pos,
|
||||
}
|
||||
}
|
||||
return Syntax_Error {
|
||||
msg = fmt.tprintf("expected {{/%s}}, got {{/%s}}", end_tag, tok.value),
|
||||
msg = fmt.tprintf("expected {{{{/%s}}}}, got {{{{/%s}}}}", end_tag, tok.value),
|
||||
pos = tok.pos,
|
||||
}
|
||||
|
||||
@@ -283,6 +288,7 @@ parse_section :: proc(
|
||||
is_dynamic = tok.is_dynamic,
|
||||
indent = tok.indent,
|
||||
first_child = -1,
|
||||
pos = tok.pos,
|
||||
},
|
||||
)
|
||||
pos^ += 1
|
||||
@@ -292,9 +298,15 @@ parse_section :: proc(
|
||||
idx := len(nodes)
|
||||
append(
|
||||
nodes,
|
||||
Node{kind = .Parent, key = tok.value, indent = tok.indent, first_child = -1},
|
||||
Node {
|
||||
kind = .Parent,
|
||||
key = tok.value,
|
||||
indent = tok.indent,
|
||||
first_child = -1,
|
||||
pos = tok.pos,
|
||||
},
|
||||
)
|
||||
parse_section(tokens, pos, nodes, tok.value, source, allocator) or_return
|
||||
parse_section(tokens, pos, nodes, tok.value, source, allocator, tok.pos) or_return
|
||||
nodes[idx].first_child = idx + 1
|
||||
nodes[idx].child_count = len(nodes) - idx - 1
|
||||
|
||||
@@ -303,16 +315,25 @@ parse_section :: proc(
|
||||
idx := len(nodes)
|
||||
append(
|
||||
nodes,
|
||||
Node{kind = .Block, key = tok.value, indent = tok.indent, first_child = -1},
|
||||
Node {
|
||||
kind = .Block,
|
||||
key = tok.value,
|
||||
indent = tok.indent,
|
||||
first_child = -1,
|
||||
pos = tok.pos,
|
||||
},
|
||||
)
|
||||
parse_section(tokens, pos, nodes, tok.value, source, allocator) or_return
|
||||
parse_section(tokens, pos, nodes, tok.value, source, allocator, tok.pos) or_return
|
||||
nodes[idx].first_child = idx + 1
|
||||
nodes[idx].child_count = len(nodes) - idx - 1
|
||||
}
|
||||
}
|
||||
|
||||
if end_tag != "" {
|
||||
return Syntax_Error{msg = fmt.tprintf("unclosed section '{{#%s}}'", end_tag)}
|
||||
return Syntax_Error {
|
||||
msg = fmt.tprintf("unclosed section '{{{{#%s}}}}'", end_tag),
|
||||
pos = open_pos,
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -476,11 +497,20 @@ render_template :: proc(
|
||||
indent: string,
|
||||
) -> Render_Error {
|
||||
if len(indent) > 0 && len(pt.source) > 0 {
|
||||
// Per Mustache spec: the partial's source is indented before rendering,
|
||||
// not its output. This is necessary so that data-injected newlines
|
||||
// (e.g. from `{{{content}}}` where content contains `\n`) do NOT pick
|
||||
// up the indent — only source-level line breaks do.
|
||||
indented := indent_lines(pt.source, indent)
|
||||
reparse := parse(indented, context.temp_allocator, context.temp_allocator) or_return
|
||||
return render_nodes(reparse.nodes[:], reparse.nodes[:], ctx, partials, b, blocks)
|
||||
reparse := parse(
|
||||
indented,
|
||||
pt.path,
|
||||
context.temp_allocator,
|
||||
context.temp_allocator,
|
||||
) or_return
|
||||
return render_nodes(reparse, reparse.nodes[:], reparse.nodes[:], ctx, partials, b, blocks)
|
||||
}
|
||||
return render_nodes(pt.nodes[:], pt.nodes[:], ctx, partials, b, blocks)
|
||||
return render_nodes(pt, pt.nodes[:], pt.nodes[:], ctx, partials, b, blocks)
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -488,6 +518,7 @@ render_template :: proc(
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
render_nodes :: proc(
|
||||
current: Template,
|
||||
all_nodes: []Node,
|
||||
nodes: []Node,
|
||||
ctx: ^[dynamic]any,
|
||||
@@ -505,19 +536,28 @@ render_nodes :: proc(
|
||||
|
||||
case .Variable:
|
||||
val := resolve_name(node.key, ctx[:])
|
||||
if val == nil {
|
||||
warn_unknown_key(current, ctx[:], node)
|
||||
}
|
||||
if len(node.filters) > 0 {
|
||||
transformed, perr := apply_pipeline(val, node.filters[:])
|
||||
transformed, perr := apply_pipeline(val, node.filters[:], node.pos)
|
||||
if perr != nil {
|
||||
return perr
|
||||
}
|
||||
val = transformed
|
||||
}
|
||||
if result_str, ok := call_interp_lambda(val); ok {
|
||||
sub_tpl, perr := parse(result_str, context.temp_allocator, context.temp_allocator)
|
||||
sub_tpl, perr := parse(
|
||||
result_str,
|
||||
fmt.tprintf("<lambda output from '%s'>", node.key),
|
||||
context.temp_allocator,
|
||||
context.temp_allocator,
|
||||
)
|
||||
if perr == nil {
|
||||
temp: strings.Builder
|
||||
strings.builder_init(&temp, context.temp_allocator)
|
||||
render_nodes(
|
||||
sub_tpl,
|
||||
sub_tpl.nodes[:],
|
||||
sub_tpl.nodes[:],
|
||||
ctx,
|
||||
@@ -534,19 +574,28 @@ render_nodes :: proc(
|
||||
|
||||
case .Unescaped:
|
||||
val := resolve_name(node.key, ctx[:])
|
||||
if val == nil {
|
||||
warn_unknown_key(current, ctx[:], node)
|
||||
}
|
||||
if len(node.filters) > 0 {
|
||||
transformed, perr := apply_pipeline(val, node.filters[:])
|
||||
transformed, perr := apply_pipeline(val, node.filters[:], node.pos)
|
||||
if perr != nil {
|
||||
return perr
|
||||
}
|
||||
val = transformed
|
||||
}
|
||||
if result_str, ok := call_interp_lambda(val); ok {
|
||||
sub_tpl, perr := parse(result_str, context.temp_allocator, context.temp_allocator)
|
||||
sub_tpl, perr := parse(
|
||||
result_str,
|
||||
fmt.tprintf("<lambda output from '%s'>", node.key),
|
||||
context.temp_allocator,
|
||||
context.temp_allocator,
|
||||
)
|
||||
if perr == nil {
|
||||
temp: strings.Builder
|
||||
strings.builder_init(&temp, context.temp_allocator)
|
||||
render_nodes(
|
||||
sub_tpl,
|
||||
sub_tpl.nodes[:],
|
||||
sub_tpl.nodes[:],
|
||||
ctx,
|
||||
@@ -563,17 +612,26 @@ render_nodes :: proc(
|
||||
|
||||
case .Section:
|
||||
val := resolve_name(node.key, ctx[:])
|
||||
if val == nil {
|
||||
warn_unknown_key(current, ctx[:], node)
|
||||
}
|
||||
if len(node.filters) > 0 {
|
||||
transformed, perr := apply_pipeline(val, node.filters[:])
|
||||
transformed, perr := apply_pipeline(val, node.filters[:], node.pos)
|
||||
if perr != nil {
|
||||
return perr
|
||||
}
|
||||
val = transformed
|
||||
}
|
||||
if result_str, ok := call_section_lambda(val, node.content); ok {
|
||||
sub_tpl, perr := parse(result_str, context.temp_allocator, context.temp_allocator)
|
||||
sub_tpl, perr := parse(
|
||||
result_str,
|
||||
fmt.tprintf("<lambda output from '%s'>", node.key),
|
||||
context.temp_allocator,
|
||||
context.temp_allocator,
|
||||
)
|
||||
if perr == nil {
|
||||
render_nodes(
|
||||
sub_tpl,
|
||||
sub_tpl.nodes[:],
|
||||
sub_tpl.nodes[:],
|
||||
ctx,
|
||||
@@ -590,20 +648,31 @@ render_nodes :: proc(
|
||||
elem := extract_list_element(elem_info, data, j)
|
||||
append(ctx, elem)
|
||||
defer pop(ctx)
|
||||
render_nodes(all_nodes, children, ctx, partials, b, blocks) or_return
|
||||
render_nodes(
|
||||
current,
|
||||
all_nodes,
|
||||
children,
|
||||
ctx,
|
||||
partials,
|
||||
b,
|
||||
blocks,
|
||||
) or_return
|
||||
}
|
||||
} else {
|
||||
append(ctx, val)
|
||||
defer pop(ctx)
|
||||
render_nodes(all_nodes, children, ctx, partials, b, blocks) or_return
|
||||
render_nodes(current, all_nodes, children, ctx, partials, b, blocks) or_return
|
||||
}
|
||||
}
|
||||
i += 1 + node.child_count
|
||||
|
||||
case .Inverted:
|
||||
val := resolve_name(node.key, ctx[:])
|
||||
if val == nil {
|
||||
warn_unknown_key(current, ctx[:], node)
|
||||
}
|
||||
if len(node.filters) > 0 {
|
||||
transformed, perr := apply_pipeline(val, node.filters[:])
|
||||
transformed, perr := apply_pipeline(val, node.filters[:], node.pos)
|
||||
if perr != nil {
|
||||
return perr
|
||||
}
|
||||
@@ -611,7 +680,7 @@ render_nodes :: proc(
|
||||
}
|
||||
if !is_truthy(val) {
|
||||
children := all_nodes[node.first_child:node.first_child + node.child_count]
|
||||
render_nodes(all_nodes, children, ctx, partials, b, blocks) or_return
|
||||
render_nodes(current, all_nodes, children, ctx, partials, b, blocks) or_return
|
||||
}
|
||||
i += 1 + node.child_count
|
||||
|
||||
@@ -622,7 +691,9 @@ render_nodes :: proc(
|
||||
name = any_to_string(val)
|
||||
}
|
||||
pt, found := partials[name]
|
||||
if found {
|
||||
if !found {
|
||||
warn_missing_partial(current, partials, node, name)
|
||||
} else {
|
||||
render_template(pt, ctx, partials, b, nil, node.indent) or_return
|
||||
}
|
||||
i += 1
|
||||
@@ -631,6 +702,7 @@ render_nodes :: proc(
|
||||
content_nodes: []Node
|
||||
content_pool: []Node
|
||||
content_blocks := blocks
|
||||
render_current := current
|
||||
|
||||
found_override := false
|
||||
if blocks != nil {
|
||||
@@ -638,6 +710,7 @@ render_nodes :: proc(
|
||||
content_nodes = o.all_nodes[o.first:o.first + o.count]
|
||||
content_pool = o.all_nodes
|
||||
found_override = true
|
||||
render_current = o.source
|
||||
}
|
||||
}
|
||||
if !found_override {
|
||||
@@ -649,6 +722,7 @@ render_nodes :: proc(
|
||||
temp: strings.Builder
|
||||
strings.builder_init(&temp, context.temp_allocator)
|
||||
render_nodes(
|
||||
render_current,
|
||||
content_pool,
|
||||
content_nodes,
|
||||
ctx,
|
||||
@@ -659,6 +733,7 @@ render_nodes :: proc(
|
||||
write_indented(b, node.indent, strings.to_string(temp))
|
||||
} else {
|
||||
render_nodes(
|
||||
render_current,
|
||||
content_pool,
|
||||
content_nodes,
|
||||
ctx,
|
||||
@@ -671,9 +746,12 @@ render_nodes :: proc(
|
||||
|
||||
case .Parent:
|
||||
parent_children := all_nodes[node.first_child:node.first_child + node.child_count]
|
||||
merged := merge_block_overrides(parent_children, all_nodes, blocks)
|
||||
merged := merge_block_overrides(parent_children, all_nodes, blocks, current)
|
||||
pt, found := partials[node.key]
|
||||
if found {
|
||||
if !found {
|
||||
warn_missing_partial(current, partials, node, node.key)
|
||||
} else {
|
||||
warn_unmatched_block_overrides(current, pt, parent_children)
|
||||
render_template(pt, ctx, partials, b, merged, node.indent) or_return
|
||||
}
|
||||
i += 1 + node.child_count
|
||||
@@ -686,6 +764,7 @@ merge_block_overrides :: proc(
|
||||
children: []Node,
|
||||
all_nodes: []Node,
|
||||
existing: map[string]Block_Override,
|
||||
source: Template,
|
||||
) -> map[string]Block_Override {
|
||||
result := make(map[string]Block_Override, context.temp_allocator)
|
||||
|
||||
@@ -702,6 +781,7 @@ merge_block_overrides :: proc(
|
||||
all_nodes = all_nodes,
|
||||
first = child.first_child,
|
||||
count = child.child_count,
|
||||
source = source,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -711,3 +791,115 @@ merge_block_overrides :: proc(
|
||||
return result
|
||||
}
|
||||
|
||||
// warn_unknown_key checks whether the missing key is a genuine typo (vs. a
|
||||
// legitimate path through a user-defined map) and, if so, emits a diagnostic
|
||||
// warning with the closest field-name suggestion via Levenshtein.
|
||||
warn_unknown_key :: proc(current: Template, ctx: []any, node: Node) {
|
||||
// `{{.}}` and dot-prefixed names refer to the current context — always valid.
|
||||
if node.key == "." || (len(node.key) > 0 && node.key[0] == '.') {
|
||||
return
|
||||
}
|
||||
|
||||
path_ok, missing, available := validate_key_path(ctx, node.key)
|
||||
if path_ok {
|
||||
return
|
||||
}
|
||||
|
||||
hint := ""
|
||||
if len(available) > 0 {
|
||||
suggestion := suggest_correction(available, missing)
|
||||
if suggestion != "" {
|
||||
hint = fmt.tprintf("did you mean '%s'?", suggestion)
|
||||
}
|
||||
}
|
||||
msg := fmt.tprintf("unknown key '%s'", node.key)
|
||||
path := current.path
|
||||
if path == "" {
|
||||
path = "<input>"
|
||||
}
|
||||
diag := format_error(path, current.source, node.pos, msg, hint, colorize = should_colorize())
|
||||
log.warnf("%s", diag)
|
||||
}
|
||||
|
||||
// warn_missing_partial emits a warning when a `{{> name}}` or `{{<name}}` tag
|
||||
// references a partial that isn't in the partials map.
|
||||
warn_missing_partial :: proc(
|
||||
current: Template,
|
||||
partials: map[string]Template,
|
||||
node: Node,
|
||||
name: string,
|
||||
) {
|
||||
hint := ""
|
||||
available := collect_partial_names(partials)
|
||||
defer delete(available)
|
||||
suggestion := suggest_correction(available, name)
|
||||
if suggestion != "" {
|
||||
hint = fmt.tprintf("did you mean '%s'?", suggestion)
|
||||
}
|
||||
msg := fmt.tprintf("partial '%s' not found", name)
|
||||
path := current.path
|
||||
if path == "" {
|
||||
path = "<input>"
|
||||
}
|
||||
diag := format_error(path, current.source, node.pos, msg, hint, colorize = should_colorize())
|
||||
log.warnf("%s", diag)
|
||||
}
|
||||
|
||||
// warn_unmatched_block_overrides checks each `{{$name}}...{{/name}}` block
|
||||
// defined inside a `{{<parent}}` tag and warns when the name doesn't match
|
||||
// any block in the parent template.
|
||||
warn_unmatched_block_overrides :: proc(
|
||||
current: Template,
|
||||
parent: Template,
|
||||
parent_children: []Node,
|
||||
) {
|
||||
if len(parent_children) == 0 {
|
||||
return
|
||||
}
|
||||
available := collect_block_names(parent)
|
||||
defer delete(available)
|
||||
parent_path := parent.path
|
||||
if parent_path == "" {
|
||||
parent_path = "<input>"
|
||||
}
|
||||
for child in parent_children {
|
||||
if child.kind != .Block {
|
||||
continue
|
||||
}
|
||||
matched := false
|
||||
for name in available {
|
||||
if name == child.key {
|
||||
matched = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if matched {
|
||||
continue
|
||||
}
|
||||
|
||||
hint := ""
|
||||
suggestion := suggest_correction(available, child.key)
|
||||
if suggestion != "" {
|
||||
hint = fmt.tprintf("did you mean '%s'?", suggestion)
|
||||
}
|
||||
msg := fmt.tprintf(
|
||||
"block override '%s' has no match in parent template '%s'",
|
||||
child.key,
|
||||
parent_path,
|
||||
)
|
||||
path := current.path
|
||||
if path == "" {
|
||||
path = "<input>"
|
||||
}
|
||||
diag := format_error(
|
||||
path,
|
||||
current.source,
|
||||
child.pos,
|
||||
msg,
|
||||
hint,
|
||||
colorize = should_colorize(),
|
||||
)
|
||||
log.warnf("%s", diag)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user