1
+ --- @class render.md.debug.Spec
2
+ --- @field message string
3
+ --- @field validation fun ( value : any ): boolean , string ?
4
+
1
5
--- @class render.md.debug.ValidatorSpec
2
6
--- @field private validator render.md.debug.Validator
3
7
--- @field private config ? table<string , any>
4
8
--- @field private nilable boolean
5
9
--- @field private path string
6
- --- @field private opts table<string, vim.validate .Spec>
10
+ --- @field private specs table<string, render.md.debug .Spec>
7
11
local Spec = {}
8
12
Spec .__index = Spec
9
13
10
14
--- @param validator render.md.debug.Validator
11
- --- @param config table<string , any>
15
+ --- @param config ? table<string , any>
12
16
--- @param nilable boolean
13
17
--- @param key ? string | string[]
14
18
--- @param path ? string
@@ -19,65 +23,68 @@ function Spec.new(validator, config, nilable, key, path)
19
23
self .config = config
20
24
self .nilable = nilable
21
25
self .path = path or ' '
22
- self .opts = {}
23
- if key ~= nil then
26
+ self .specs = {}
27
+ if self . config ~= nil and key ~= nil then
24
28
key = type (key ) == ' table' and key or { key }
25
29
self .path = self .path .. ' .' .. table.concat (key , ' .' )
26
30
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
28
32
end
29
33
return self
30
34
end
31
35
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[]
43
37
--- @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 }
49
45
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
50
54
end
51
55
52
56
--- @param keys string | string[]
53
- --- @param types type | type[]
57
+ --- @param input_types type | type[]
54
58
--- @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 )
57
64
end
58
65
59
66
--- @param keys string | string[]
60
67
--- @param values string[]
61
68
--- @param input_types ? type | type[]
62
69
--- @return render.md.debug.ValidatorSpec
63
70
function 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 )
68
75
end
69
76
70
77
--- @param keys string | string[]
71
78
--- @param list_type type
72
79
--- @param input_types ? type | type[]
73
80
--- @return render.md.debug.ValidatorSpec
74
81
function 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
78
85
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
81
88
if type (item ) ~= list_type then
82
89
return false , string.format (' [%d] is %s' , i , type (item ))
83
90
end
@@ -86,20 +93,20 @@ function Spec:list(keys, list_type, input_types)
86
93
else
87
94
return false
88
95
end
89
- end , list_type .. ' list ' .. suffix )
96
+ end )
90
97
end
91
98
92
99
--- @param keys string | string[]
93
100
--- @param list_type type
94
101
--- @param input_types ? type | type[]
95
102
--- @return render.md.debug.ValidatorSpec
96
103
function 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
100
107
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
103
110
if type (item ) == ' table' then
104
111
for j , nested in ipairs (item ) do
105
112
if type (nested ) ~= list_type then
@@ -114,22 +121,22 @@ function Spec:list_or_list_of_list(keys, list_type, input_types)
114
121
else
115
122
return false
116
123
end
117
- end , list_type .. ' list or list of list ' .. suffix )
124
+ end )
118
125
end
119
126
120
127
--- @param keys string | string[]
121
128
--- @param values string[]
122
129
--- @param input_types ? type | type[]
123
130
--- @return render.md.debug.ValidatorSpec
124
131
function 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
128
135
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
133
140
if not vim .tbl_contains (values , item ) then
134
141
return false , string.format (' [%d] is %s' , i , item )
135
142
end
@@ -138,13 +145,14 @@ function Spec:one_or_list_of(keys, values, input_types)
138
145
else
139
146
return false
140
147
end
141
- end , ' one or list of ' .. vim . inspect ( values ) .. suffix )
148
+ end )
142
149
end
143
150
144
151
--- @private
145
152
--- @param input_types ? type | type[]
153
+ --- @param prefix string
146
154
--- @return type[] , string
147
- function Spec :handle_types (input_types )
155
+ function Spec :handle_types (input_types , prefix )
148
156
local types = nil
149
157
if input_types == nil then
150
158
types = {}
@@ -156,33 +164,36 @@ function Spec:handle_types(input_types)
156
164
if self .nilable and not vim .tbl_contains (types , ' nil' ) then
157
165
table.insert (types , ' nil' )
158
166
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
160
175
end
161
176
162
177
--- @private
163
178
--- @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 ?
166
181
--- @return render.md.debug.ValidatorSpec
167
- function Spec :add (keys , logic , message )
182
+ function Spec :add (keys , message , validation )
168
183
if self .config ~= nil then
169
184
keys = type (keys ) == ' table' and keys or { keys }
170
185
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 }
173
187
end
174
188
end
175
189
return self
176
190
end
177
191
178
192
function Spec :check ()
179
- if self .config == nil then
193
+ if self .config == nil or vim . tbl_count ( self . specs ) == 0 then
180
194
return
181
195
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 )
186
197
end
187
198
188
199
--- @class render.md.debug.Validator
@@ -201,24 +212,33 @@ end
201
212
202
213
Validator .spec = Spec .new
203
214
204
- --- @param suffix string
215
+ --- @param path string
205
216
--- @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
212
230
end
213
231
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 )
216
235
end
217
236
end
218
237
end
219
238
220
239
--- @return string[]
221
240
function Validator :get_errors ()
241
+ table.sort (self .errors )
222
242
return self .errors
223
243
end
224
244
0 commit comments