Skip to content

Commit 625790b

Browse files
committed
fix(config): validate missing boolean fields and add negative tests
Add validation rules for previously unchecked config fields: - providers.{lsp,diagnostics,file}.enable - providers.marks.enable - persist.enabled - ui.popup.auto_close.{enabled,idle_ms,check_interval_ms,ignore_pinned} - ui.feedback.highlight_origin_on_close - ui.promote.close_popup - ui.title.{enabled,format} - ui.title.context.{enabled,max_depth,separator} Invalid values (e.g. string "false" for booleans) now warn and fall back to defaults instead of silently being treated as truthy.
1 parent 06f6252 commit 625790b

4 files changed

Lines changed: 286 additions & 21 deletions

File tree

lua/peekstack/config/validate/rules/persist.lua

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ local M = {}
44

55
---@type PeekstackConfigFieldRule[]
66
local PERSIST_RULES = {
7+
{ key = "enabled", validate = shared.field_type("boolean") },
78
{ key = "max_items", validate = shared.field_number_range({ min = 1 }) },
89
}
910

@@ -34,9 +35,11 @@ function M.validate(cfg, defaults)
3435

3536
shared.apply_rules(persist, "persist", defaults.persist, PERSIST_RULES)
3637

37-
local session = shared.as_table(persist.session)
38-
if session then
39-
shared.apply_rules(session, "persist.session", defaults.persist.session, PERSIST_SESSION_RULES)
38+
if persist.session ~= nil then
39+
local session = shared.ensure_table_field(persist, "session", "persist.session", defaults.persist.session)
40+
if session then
41+
shared.apply_rules(session, "persist.session", defaults.persist.session, PERSIST_SESSION_RULES)
42+
end
4043
end
4144

4245
if persist.auto ~= nil then

lua/peekstack/config/validate/rules/providers.lua

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,14 @@ local shared = require("peekstack.config.validate.shared")
22

33
local M = {}
44

5+
---@type PeekstackConfigFieldRule[]
6+
local PROVIDER_ENABLE_RULES = {
7+
{ key = "enable", validate = shared.field_type("boolean") },
8+
}
9+
510
---@type PeekstackConfigFieldRule[]
611
local MARKS_RULES = {
12+
{ key = "enable", validate = shared.field_type("boolean") },
713
{ key = "include", validate = shared.field_type("string") },
814
{ key = "include_special", validate = shared.field_type("boolean") },
915
}
@@ -16,9 +22,20 @@ function M.validate(cfg, defaults)
1622
return
1723
end
1824

19-
local marks = shared.as_table(providers.marks)
20-
if marks then
21-
shared.apply_rules(marks, "providers.marks", defaults.providers.marks, MARKS_RULES)
25+
for _, name in ipairs({ "lsp", "diagnostics", "file" }) do
26+
if providers[name] ~= nil then
27+
local provider = shared.ensure_table_field(providers, name, "providers." .. name, defaults.providers[name])
28+
if provider then
29+
shared.apply_rules(provider, "providers." .. name, defaults.providers[name], PROVIDER_ENABLE_RULES)
30+
end
31+
end
32+
end
33+
34+
if providers.marks ~= nil then
35+
local marks = shared.ensure_table_field(providers, "marks", "providers.marks", defaults.providers.marks)
36+
if marks then
37+
shared.apply_rules(marks, "providers.marks", defaults.providers.marks, MARKS_RULES)
38+
end
2239
end
2340
end
2441

lua/peekstack/config/validate/rules/ui.lua

Lines changed: 93 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,37 @@ local QUICK_PEEK_RULES = {
6060
{ key = "close_events", validate = shared.field_event_list() },
6161
}
6262

63+
---@type PeekstackConfigFieldRule[]
64+
local POPUP_AUTO_CLOSE_RULES = {
65+
{ key = "enabled", validate = shared.field_type("boolean") },
66+
{ key = "idle_ms", validate = shared.field_number_range({ min = 1 }) },
67+
{ key = "check_interval_ms", validate = shared.field_number_range({ min = 1 }) },
68+
{ key = "ignore_pinned", validate = shared.field_type("boolean") },
69+
}
70+
71+
---@type PeekstackConfigFieldRule[]
72+
local FEEDBACK_RULES = {
73+
{ key = "highlight_origin_on_close", validate = shared.field_type("boolean") },
74+
}
75+
76+
---@type PeekstackConfigFieldRule[]
77+
local PROMOTE_RULES = {
78+
{ key = "close_popup", validate = shared.field_type("boolean") },
79+
}
80+
81+
---@type PeekstackConfigFieldRule[]
82+
local TITLE_RULES = {
83+
{ key = "enabled", validate = shared.field_type("boolean") },
84+
{ key = "format", validate = shared.field_type("string") },
85+
}
86+
87+
---@type PeekstackConfigFieldRule[]
88+
local TITLE_CONTEXT_RULES = {
89+
{ key = "enabled", validate = shared.field_type("boolean") },
90+
{ key = "max_depth", validate = shared.field_number_range({ min = 1 }) },
91+
{ key = "separator", validate = shared.field_type("string") },
92+
}
93+
6394
---@type PeekstackConfigFieldRule[]
6495
local TITLE_ICON_RULES = {
6596
{ key = "enabled", validate = shared.field_type("boolean") },
@@ -119,28 +150,45 @@ end
119150
---@param ui table
120151
---@param defaults PeekstackConfigUI
121152
local function validate_popup(ui, defaults)
122-
local popup = shared.as_table(ui.popup)
153+
if ui.popup == nil then
154+
return
155+
end
156+
local popup = shared.ensure_table_field(ui, "popup", "ui.popup", defaults.popup)
123157
if not popup then
124158
return
125159
end
126160

127161
shared.apply_rules(popup, "ui.popup", defaults.popup, POPUP_RULES)
128162

129-
local source = shared.as_table(popup.source)
130-
if source then
131-
shared.apply_rules(source, "ui.popup.source", defaults.popup.source, POPUP_SOURCE_RULES)
163+
if popup.source ~= nil then
164+
local source = shared.ensure_table_field(popup, "source", "ui.popup.source", defaults.popup.source)
165+
if source then
166+
shared.apply_rules(source, "ui.popup.source", defaults.popup.source, POPUP_SOURCE_RULES)
167+
end
132168
end
133169

134-
local history = shared.as_table(popup.history)
135-
if history then
136-
shared.apply_rules(history, "ui.popup.history", defaults.popup.history, POPUP_HISTORY_RULES)
170+
if popup.history ~= nil then
171+
local history = shared.ensure_table_field(popup, "history", "ui.popup.history", defaults.popup.history)
172+
if history then
173+
shared.apply_rules(history, "ui.popup.history", defaults.popup.history, POPUP_HISTORY_RULES)
174+
end
175+
end
176+
177+
if popup.auto_close ~= nil then
178+
local auto_close = shared.ensure_table_field(popup, "auto_close", "ui.popup.auto_close", defaults.popup.auto_close)
179+
if auto_close then
180+
shared.apply_rules(auto_close, "ui.popup.auto_close", defaults.popup.auto_close, POPUP_AUTO_CLOSE_RULES)
181+
end
137182
end
138183
end
139184

140185
---@param ui table
141186
---@param defaults PeekstackConfigUI
142187
local function validate_path(ui, defaults)
143-
local path = shared.as_table(ui.path)
188+
if ui.path == nil then
189+
return
190+
end
191+
local path = shared.ensure_table_field(ui, "path", "ui.path", defaults.path)
144192
if path then
145193
shared.apply_rules(path, "ui.path", defaults.path, UI_PATH_RULES)
146194
end
@@ -162,25 +210,34 @@ end
162210
---@param ui table
163211
---@param defaults PeekstackConfigUI
164212
local function validate_preview(ui, defaults)
165-
local inline_preview = shared.as_table(ui.inline_preview)
166-
if inline_preview then
167-
shared.apply_rules(inline_preview, "ui.inline_preview", defaults.inline_preview, INLINE_PREVIEW_RULES)
213+
if ui.inline_preview ~= nil then
214+
local inline_preview = shared.ensure_table_field(ui, "inline_preview", "ui.inline_preview", defaults.inline_preview)
215+
if inline_preview then
216+
shared.apply_rules(inline_preview, "ui.inline_preview", defaults.inline_preview, INLINE_PREVIEW_RULES)
217+
end
168218
end
169219

170-
local quick_peek = shared.as_table(ui.quick_peek)
171-
if quick_peek then
172-
shared.apply_rules(quick_peek, "ui.quick_peek", defaults.quick_peek, QUICK_PEEK_RULES)
220+
if ui.quick_peek ~= nil then
221+
local quick_peek = shared.ensure_table_field(ui, "quick_peek", "ui.quick_peek", defaults.quick_peek)
222+
if quick_peek then
223+
shared.apply_rules(quick_peek, "ui.quick_peek", defaults.quick_peek, QUICK_PEEK_RULES)
224+
end
173225
end
174226
end
175227

176228
---@param ui table
177229
---@param defaults PeekstackConfigTitle
178230
local function validate_title(ui, defaults)
179-
local title = shared.as_table(ui.title)
231+
if ui.title == nil then
232+
return
233+
end
234+
local title = shared.ensure_table_field(ui, "title", "ui.title", defaults)
180235
if not title then
181236
return
182237
end
183238

239+
shared.apply_rules(title, "ui.title", defaults, TITLE_RULES)
240+
184241
if title.icons ~= nil and type(title.icons) ~= "table" then
185242
notify.warn("ui.title.icons must be a table, got " .. type(title.icons) .. ". Falling back to defaults")
186243
title.icons = vim.deepcopy(defaults.icons)
@@ -195,6 +252,13 @@ local function validate_title(ui, defaults)
195252
icons.map = vim.deepcopy(defaults.icons.map)
196253
end
197254
end
255+
256+
if title.context ~= nil then
257+
local context = shared.ensure_table_field(title, "context", "ui.title.context", defaults.context)
258+
if context then
259+
shared.apply_rules(context, "ui.title.context", defaults.context, TITLE_CONTEXT_RULES)
260+
end
261+
end
198262
end
199263

200264
---@param ui table
@@ -248,6 +312,20 @@ function M.validate(cfg, defaults)
248312
validate_preview(ui, defaults.ui)
249313
validate_title(ui, defaults.ui.title)
250314
validate_layout(ui, defaults.ui)
315+
316+
if ui.feedback ~= nil then
317+
local feedback = shared.ensure_table_field(ui, "feedback", "ui.feedback", defaults.ui.feedback)
318+
if feedback then
319+
shared.apply_rules(feedback, "ui.feedback", defaults.ui.feedback, FEEDBACK_RULES)
320+
end
321+
end
322+
323+
if ui.promote ~= nil then
324+
local promote = shared.ensure_table_field(ui, "promote", "ui.promote", defaults.ui.promote)
325+
if promote then
326+
shared.apply_rules(promote, "ui.promote", defaults.ui.promote, PROMOTE_RULES)
327+
end
328+
end
251329
end
252330

253331
return M

0 commit comments

Comments
 (0)