mirror of
https://github.com/sbrow/thor.git
synced 2026-08-26 11:23:32 -04:00
fix: No longer logs warnings for missing params.* keys. (except spellcheck).
This commit is contained in:
@@ -442,8 +442,9 @@ Rust-style error messages with multi-line source context, caret underlines, and
|
|||||||
|
|
||||||
**Exceptions** (no warning):
|
**Exceptions** (no warning):
|
||||||
- `{{.}}` and dot-prefixed names (current context)
|
- `{{.}}` and dot-prefixed names (current context)
|
||||||
- Paths that cross a map (e.g., `params.*` — user-defined namespace)
|
- Paths that cross a map (e.g., `params.*`) — validated for typos via Levenshtein: close matches warn with a suggestion, genuinely absent keys are suppressed silently
|
||||||
- `Maybe(bool)` fields with nil value (field exists, value is nil — distinguished via `struct_has_field`)
|
- `Maybe(bool)` fields with nil value (field exists, value is nil — distinguished via `struct_has_field`)
|
||||||
|
- Found fields whose value is nil/empty (e.g., nil `json.Value` union) — the field exists, looking up sub-keys is valid "not found" behavior
|
||||||
|
|
||||||
**Block override source tracking**: `Block_Override.source: Template` ensures warnings inside block overrides point at the override's source file (e.g., `page.html`), not the parent template (`base.html`).
|
**Block override source tracking**: `Block_Override.source: Template` ensures warnings inside block overrides point at the override's source file (e.g., `page.html`), not the parent template (`base.html`).
|
||||||
|
|
||||||
|
|||||||
@@ -137,6 +137,22 @@ resolve_name :: proc(name: string, ctx: []any) -> any {
|
|||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
collect_map_keys :: proc(container: any, allocator := context.temp_allocator) -> []string {
|
||||||
|
val, info := base_value(container)
|
||||||
|
if info == nil do return nil
|
||||||
|
if _, ok := info.variant.(runtime.Type_Info_Map); !ok {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
out := make([dynamic]string, 0, 4, allocator)
|
||||||
|
it := 0
|
||||||
|
for {
|
||||||
|
key, _ := reflect.iterate_map(val, &it) or_break
|
||||||
|
key_str := key.(string) or_continue
|
||||||
|
append(&out, key_str)
|
||||||
|
}
|
||||||
|
return out[:]
|
||||||
|
}
|
||||||
|
|
||||||
// is_truthy checks mustache truthiness.
|
// is_truthy checks mustache truthiness.
|
||||||
is_truthy :: proc(a: any) -> bool {
|
is_truthy :: proc(a: any) -> bool {
|
||||||
if a == nil {
|
if a == nil {
|
||||||
@@ -302,3 +318,4 @@ write_value :: proc(b: ^strings.Builder, a: any, escape: bool) {
|
|||||||
strings.write_string(b, s[start:])
|
strings.write_string(b, s[start:])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+10
-1
@@ -121,9 +121,18 @@ validate_key_path :: proc(
|
|||||||
for i in 1 ..< part_count {
|
for i in 1 ..< part_count {
|
||||||
v, info := base_value(current)
|
v, info := base_value(current)
|
||||||
if info == nil {
|
if info == nil {
|
||||||
return false, parts[i], nil
|
return true, "", nil
|
||||||
}
|
}
|
||||||
if _, is_map := info.variant.(runtime.Type_Info_Map); is_map {
|
if _, is_map := info.variant.(runtime.Type_Info_Map); is_map {
|
||||||
|
val, found := lookup_in(current, parts[i])
|
||||||
|
if found {
|
||||||
|
current = val
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
available := collect_map_keys(current, allocator)
|
||||||
|
if suggest_correction(available, parts[i]) != "" {
|
||||||
|
return false, parts[i], available
|
||||||
|
}
|
||||||
return true, "", nil
|
return true, "", nil
|
||||||
}
|
}
|
||||||
if _, is_struct := info.variant.(runtime.Type_Info_Struct); is_struct {
|
if _, is_struct := info.variant.(runtime.Type_Info_Struct); is_struct {
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
#+feature dynamic-literals
|
#+feature dynamic-literals
|
||||||
package mustache
|
package mustache
|
||||||
|
|
||||||
|
import "core:encoding/json"
|
||||||
import "core:fmt"
|
import "core:fmt"
|
||||||
import "core:testing"
|
import "core:testing"
|
||||||
|
|
||||||
@@ -196,3 +197,68 @@ test_warn_no_false_positive_for_valid_keys :: proc(t: ^testing.T) {
|
|||||||
ok, missing, _ := validate_key_path(ctx[:], "name")
|
ok, missing, _ := validate_key_path(ctx[:], "name")
|
||||||
testing.expect_value(t, ok, true)
|
testing.expect_value(t, ok, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// --- json.Value map crossing tests ---
|
||||||
|
|
||||||
|
JSON_Params_Context :: struct {
|
||||||
|
params: json.Value,
|
||||||
|
}
|
||||||
|
|
||||||
|
@(test)
|
||||||
|
test_validate_key_path_crosses_json_value_map :: proc(t: ^testing.T) {
|
||||||
|
params_map := make(json.Object, context.temp_allocator)
|
||||||
|
params_map["author"] = json.String("Tester")
|
||||||
|
|
||||||
|
data := JSON_Params_Context { params = params_map }
|
||||||
|
ctx := make([dynamic]any, 0, 1, context.temp_allocator)
|
||||||
|
append(&ctx, data)
|
||||||
|
|
||||||
|
ok, missing, _ := validate_key_path(ctx[:], "params.starred")
|
||||||
|
testing.expect(t, ok, "path crossing json.Value map should be ok")
|
||||||
|
testing.expect(t, missing == "", "no missing segment for map crossing")
|
||||||
|
}
|
||||||
|
|
||||||
|
@(test)
|
||||||
|
test_validate_key_path_json_value_map_existing_key :: proc(t: ^testing.T) {
|
||||||
|
params_map := make(json.Object, context.temp_allocator)
|
||||||
|
params_map["author"] = json.String("Tester")
|
||||||
|
|
||||||
|
data := JSON_Params_Context { params = params_map }
|
||||||
|
ctx := make([dynamic]any, 0, 1, context.temp_allocator)
|
||||||
|
append(&ctx, data)
|
||||||
|
|
||||||
|
ok, missing, _ := validate_key_path(ctx[:], "params.author")
|
||||||
|
testing.expect(t, ok, "existing key in json.Value map should be ok")
|
||||||
|
}
|
||||||
|
|
||||||
|
@(test)
|
||||||
|
test_validate_key_path_map_typo :: proc(t: ^testing.T) {
|
||||||
|
params_map := make(json.Object, context.temp_allocator)
|
||||||
|
params_map["author"] = json.String("Tester")
|
||||||
|
params_map["social"] = json.String("")
|
||||||
|
|
||||||
|
data := JSON_Params_Context { params = params_map }
|
||||||
|
ctx := make([dynamic]any, 0, 1, context.temp_allocator)
|
||||||
|
append(&ctx, data)
|
||||||
|
|
||||||
|
ok, missing, available := validate_key_path(ctx[:], "params.authr")
|
||||||
|
testing.expect(t, !ok, "typo of map key should not be ok")
|
||||||
|
testing.expect_value(t, missing, "authr")
|
||||||
|
suggestion := suggest_correction(available, "authr")
|
||||||
|
testing.expect_value(t, suggestion, "author")
|
||||||
|
}
|
||||||
|
|
||||||
|
@(test)
|
||||||
|
test_validate_key_path_map_no_close_match :: proc(t: ^testing.T) {
|
||||||
|
params_map := make(json.Object, context.temp_allocator)
|
||||||
|
params_map["author"] = json.String("Tester")
|
||||||
|
params_map["social"] = json.String("")
|
||||||
|
|
||||||
|
data := JSON_Params_Context { params = params_map }
|
||||||
|
ctx := make([dynamic]any, 0, 1, context.temp_allocator)
|
||||||
|
append(&ctx, data)
|
||||||
|
|
||||||
|
ok, missing, _ := validate_key_path(ctx[:], "params.xyz")
|
||||||
|
testing.expect(t, ok, "unknown key with no close match should be ok (suppressed)")
|
||||||
|
testing.expect(t, missing == "", "no missing segment when suppressed")
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user