1+ --- @class render.md.debug.Spec
2+ --- @field message string
3+ --- @field validation fun ( value : any ): boolean , string ?
4+
15--- @class render.md.debug.ValidatorSpec
26--- @field private validator render.md.debug.Validator
37--- @field private config ? table<string , any>
48--- @field private nilable boolean
59--- @field private path string
6- --- @field private opts table<string, vim.validate .Spec>
10+ --- @field private specs table<string, render.md.debug .Spec>
711local Spec = {}
812Spec .__index = Spec
913
1014--- @param validator render.md.debug.Validator
11- --- @param config table<string , any>
15+ --- @param config ? table<string , any>
1216--- @param nilable boolean
1317--- @param key ? string | string[]
1418--- @param path ? string
@@ -19,65 +23,68 @@ function Spec.new(validator, config, nilable, key, path)
1923 self .config = config
2024 self .nilable = nilable
2125 self .path = path or ' '
22- self .opts = {}
23- if key ~= nil then
26+ self .specs = {}
27+ if self . config ~= nil and key ~= nil then
2428 key = type (key ) == ' table' and key or { key }
2529 self .path = self .path .. ' .' .. table.concat (key , ' .' )
2630 self .config = vim .tbl_get (self .config , unpack (key ))
27- assert (self .config ~= nil or self .nilable )
31+ self . config = type (self .config ) == ' table ' and self .config or nil
2832 end
2933 return self
3034end
3135
32- --- @return string
33- function Spec :get_path ()
34- return self .path
35- end
36-
37- --- @return table<string , any>
38- function Spec :get_config ()
39- return self .config
40- end
41-
42- --- @param nilable boolean
36+ --- @param keys ' ALL' | string | string[]
4337--- @param f fun ( spec : render.md.debug.ValidatorSpec )
44- function Spec :for_each (nilable , f )
45- for name in pairs (self .config or {}) do
46- local spec = Spec .new (self .validator , self .config , nilable , name , self .path )
47- f (spec )
48- spec :check ()
38+ --- @param nilable ? boolean
39+ --- @return render.md.debug.ValidatorSpec
40+ function Spec :nested (keys , f , nilable )
41+ if keys == ' ALL' then
42+ keys = self .config ~= nil and vim .tbl_keys (self .config ) or {}
43+ else
44+ keys = type (keys ) == ' table' and keys or { keys }
4945 end
46+ if nilable == nil then
47+ nilable = self .nilable
48+ end
49+ for _ , key in ipairs (keys ) do
50+ self :type (key , ' table' )
51+ f (Spec .new (self .validator , self .config , nilable , key , self .path ))
52+ end
53+ return self
5054end
5155
5256--- @param keys string | string[]
53- --- @param types type | type[]
57+ --- @param input_types type | type[]
5458--- @return render.md.debug.ValidatorSpec
55- function Spec :type (keys , types )
56- return self :add (keys , types , nil )
59+ function Spec :type (keys , input_types )
60+ local types , message = self :handle_types (input_types , ' ' )
61+ return self :add (keys , message , function (value )
62+ return vim .tbl_contains (types , type (value ))
63+ end )
5764end
5865
5966--- @param keys string | string[]
6067--- @param values string[]
6168--- @param input_types ? type | type[]
6269--- @return render.md.debug.ValidatorSpec
6370function Spec :one_of (keys , values , input_types )
64- local types , suffix = self :handle_types (input_types )
65- return self :add (keys , function (v )
66- return vim .tbl_contains (values , v ) or vim .tbl_contains (types , type (v ))
67- end , ' one of ' .. vim . inspect ( values ) .. suffix )
71+ local types , message = self :handle_types (input_types , ' one of ' .. vim . inspect ( values ) )
72+ return self :add (keys , message , function (value )
73+ return vim .tbl_contains (values , value ) or vim .tbl_contains (types , type (value ))
74+ end )
6875end
6976
7077--- @param keys string | string[]
7178--- @param list_type type
7279--- @param input_types ? type | type[]
7380--- @return render.md.debug.ValidatorSpec
7481function Spec :list (keys , list_type , input_types )
75- local types , suffix = self :handle_types (input_types )
76- return self :add (keys , function (v )
77- if vim .tbl_contains (types , type (v )) then
82+ local types , message = self :handle_types (input_types , list_type .. ' list ' )
83+ return self :add (keys , message , function (value )
84+ if vim .tbl_contains (types , type (value )) then
7885 return true
79- elseif type (v ) == ' table' then
80- for i , item in ipairs (v ) do
86+ elseif type (value ) == ' table' then
87+ for i , item in ipairs (value ) do
8188 if type (item ) ~= list_type then
8289 return false , string.format (' [%d] is %s' , i , type (item ))
8390 end
@@ -86,20 +93,20 @@ function Spec:list(keys, list_type, input_types)
8693 else
8794 return false
8895 end
89- end , list_type .. ' list ' .. suffix )
96+ end )
9097end
9198
9299--- @param keys string | string[]
93100--- @param list_type type
94101--- @param input_types ? type | type[]
95102--- @return render.md.debug.ValidatorSpec
96103function Spec :list_or_list_of_list (keys , list_type , input_types )
97- local types , suffix = self :handle_types (input_types )
98- return self :add (keys , function (v )
99- if vim .tbl_contains (types , type (v )) then
104+ local types , message = self :handle_types (input_types , list_type .. ' list or list of list ' )
105+ return self :add (keys , message , function (value )
106+ if vim .tbl_contains (types , type (value )) then
100107 return true
101- elseif type (v ) == ' table' then
102- for i , item in ipairs (v ) do
108+ elseif type (value ) == ' table' then
109+ for i , item in ipairs (value ) do
103110 if type (item ) == ' table' then
104111 for j , nested in ipairs (item ) do
105112 if type (nested ) ~= list_type then
@@ -114,22 +121,22 @@ function Spec:list_or_list_of_list(keys, list_type, input_types)
114121 else
115122 return false
116123 end
117- end , list_type .. ' list or list of list ' .. suffix )
124+ end )
118125end
119126
120127--- @param keys string | string[]
121128--- @param values string[]
122129--- @param input_types ? type | type[]
123130--- @return render.md.debug.ValidatorSpec
124131function Spec :one_or_list_of (keys , values , input_types )
125- local types , suffix = self :handle_types (input_types )
126- return self :add (keys , function (v )
127- if vim .tbl_contains (types , type (v )) then
132+ local types , message = self :handle_types (input_types , ' one or list of ' .. vim . inspect ( values ) )
133+ return self :add (keys , message , function (value )
134+ if vim .tbl_contains (types , type (value )) then
128135 return true
129- elseif type (v ) == ' string' then
130- return vim .tbl_contains (values , v )
131- elseif type (v ) == ' table' then
132- for i , item in ipairs (v ) do
136+ elseif type (value ) == ' string' then
137+ return vim .tbl_contains (values , value )
138+ elseif type (value ) == ' table' then
139+ for i , item in ipairs (value ) do
133140 if not vim .tbl_contains (values , item ) then
134141 return false , string.format (' [%d] is %s' , i , item )
135142 end
@@ -138,13 +145,14 @@ function Spec:one_or_list_of(keys, values, input_types)
138145 else
139146 return false
140147 end
141- end , ' one or list of ' .. vim . inspect ( values ) .. suffix )
148+ end )
142149end
143150
144151--- @private
145152--- @param input_types ? type | type[]
153+ --- @param prefix string
146154--- @return type[] , string
147- function Spec :handle_types (input_types )
155+ function Spec :handle_types (input_types , prefix )
148156 local types = nil
149157 if input_types == nil then
150158 types = {}
@@ -156,33 +164,36 @@ function Spec:handle_types(input_types)
156164 if self .nilable and not vim .tbl_contains (types , ' nil' ) then
157165 table.insert (types , ' nil' )
158166 end
159- return types , # types == 0 and ' ' or (' or type ' .. vim .inspect (types ))
167+ local message = prefix
168+ if # types > 0 then
169+ if # message > 0 then
170+ message = message .. ' or '
171+ end
172+ message = message .. ' type ' .. table.concat (types , ' or ' )
173+ end
174+ return types , message
160175end
161176
162177--- @private
163178--- @param keys string | string[]
164- --- @param logic type | type[] | fun ( v : any ): boolean , any ?
165- --- @param message string ?
179+ --- @param message string
180+ --- @param validation fun ( v : any ): boolean , string ?
166181--- @return render.md.debug.ValidatorSpec
167- function Spec :add (keys , logic , message )
182+ function Spec :add (keys , message , validation )
168183 if self .config ~= nil then
169184 keys = type (keys ) == ' table' and keys or { keys }
170185 for _ , key in ipairs (keys ) do
171- --- @diagnostic disable-next-line : assign-type-mismatch
172- self .opts [key ] = { self .config [key ], logic , message or self .nilable }
186+ self .specs [key ] = { message = message , validation = validation }
173187 end
174188 end
175189 return self
176190end
177191
178192function Spec :check ()
179- if self .config == nil then
193+ if self .config == nil or vim . tbl_count ( self . specs ) == 0 then
180194 return
181195 end
182- if vim .tbl_count (self .opts ) == 0 then
183- return
184- end
185- self .validator :check (self .path , self .config , self .opts )
196+ self .validator :check (self .path , self .config , self .specs )
186197end
187198
188199--- @class render.md.debug.Validator
@@ -201,24 +212,33 @@ end
201212
202213Validator .spec = Spec .new
203214
204- --- @param suffix string
215+ --- @param path string
205216--- @param config table<string , any>
206- --- @param opts table<string , vim.validate.Spec>
207- function Validator :check (suffix , config , opts )
208- local path = self .prefix .. suffix
209- local ok , err = pcall (vim .validate , opts )
210- if not ok then
211- table.insert (self .errors , path .. ' .' .. err )
217+ --- @param specs table<string , render.md.debug.Spec>
218+ function Validator :check (path , config , specs )
219+ path = self .prefix .. path
220+ for key , spec in pairs (specs ) do
221+ local value = config [key ]
222+ local ok , info = spec .validation (value )
223+ if not ok then
224+ local message = string.format (' %s.%s: expected %s, got %s' , path , key , spec .message , type (value ))
225+ if info ~= nil then
226+ message = message .. string.format (' , info: %s' , info )
227+ end
228+ table.insert (self .errors , message )
229+ end
212230 end
213231 for key , _ in pairs (config ) do
214- if opts [key ] == nil then
215- table.insert (self .errors , string.format (' %s.%s: is not a valid key' , path , key ))
232+ if specs [key ] == nil then
233+ local message = string.format (' %s.%s: is not a valid key' , path , key )
234+ table.insert (self .errors , message )
216235 end
217236 end
218237end
219238
220239--- @return string[]
221240function Validator :get_errors ()
241+ table.sort (self .errors )
222242 return self .errors
223243end
224244
0 commit comments