xdbc 1.0.217 → 1.0.219
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.gitattributes +16 -8
- package/.vscode/settings.json +3 -3
- package/.vscode/tasks.json +23 -23
- package/ASSESSMENT.md +249 -0
- package/README.md +538 -408
- package/__tests__/DBC/AE.test.ts +62 -62
- package/__tests__/DBC/ARRAY.test.ts +91 -91
- package/__tests__/DBC/DEFINED.test.ts +53 -53
- package/__tests__/DBC/DOM.test.ts +786 -0
- package/__tests__/DBC/Decorators.test.ts +367 -367
- package/__tests__/DBC/EQ.test.ts +13 -13
- package/__tests__/DBC/GREATER.test.ts +31 -31
- package/__tests__/DBC/HasAttribute.test.ts +60 -60
- package/__tests__/DBC/IF.test.ts +62 -62
- package/__tests__/DBC/INSTANCE.test.ts +13 -13
- package/__tests__/DBC/JSON.OP.test.ts +47 -47
- package/__tests__/DBC/JSON.Parse.test.ts +17 -17
- package/__tests__/DBC/OR.test.ts +14 -14
- package/__tests__/DBC/PLAIN_OBJECT.test.ts +109 -109
- package/__tests__/DBC/REGEX.test.ts +17 -17
- package/__tests__/DBC/TYPE.test.ts +13 -13
- package/__tests__/DBC/UNDEFINED.test.ts +45 -45
- package/__tests__/DBC/ZOD.test.ts +54 -54
- package/__tests__/DBC/onInfringement.test.ts +262 -0
- package/biome.json +45 -40
- package/dist/DBC/AE.js +172 -0
- package/dist/DBC/ARR/PLAIN_OBJECT.d.ts +0 -3
- package/dist/DBC/ARR/PLAIN_OBJECT.js +95 -0
- package/dist/DBC/ARRAY.d.ts +0 -3
- package/dist/DBC/ARRAY.js +90 -0
- package/dist/DBC/COMPARISON/GREATER.js +21 -0
- package/dist/DBC/COMPARISON/GREATER_OR_EQUAL.js +21 -0
- package/dist/DBC/COMPARISON/LESS.js +21 -0
- package/dist/DBC/COMPARISON/LESS_OR_EQUAL.js +21 -0
- package/dist/DBC/COMPARISON.js +98 -0
- package/dist/DBC/DEFINED.js +87 -0
- package/dist/DBC/DOM.d.ts +123 -0
- package/dist/DBC/DOM.js +362 -0
- package/dist/DBC/EQ/DIFFERENT.js +34 -0
- package/dist/DBC/EQ.js +101 -0
- package/dist/DBC/GREATER.js +99 -0
- package/dist/DBC/HasAttribute.js +101 -0
- package/dist/DBC/IF.js +96 -0
- package/dist/DBC/INSTANCE.js +122 -0
- package/dist/DBC/JSON.OP.js +120 -0
- package/dist/DBC/JSON.Parse.js +104 -0
- package/dist/DBC/OR.js +125 -0
- package/dist/DBC/REGEX.js +136 -0
- package/dist/DBC/TYPE.js +112 -0
- package/dist/DBC/UNDEFINED.js +87 -0
- package/dist/DBC/ZOD.js +99 -0
- package/dist/DBC.d.ts +18 -4
- package/dist/DBC.js +645 -0
- package/dist/Demo.d.ts +10 -0
- package/dist/Demo.js +713 -0
- package/dist/Test.html +18 -0
- package/dist/bundle.js +6140 -405
- package/dist/index.d.ts +22 -0
- package/dist/index.html +18 -0
- package/dist/index.js +22 -0
- package/docs/assets/highlight.css +22 -22
- package/docs/assets/icons.js +17 -17
- package/docs/assets/main.js +60 -60
- package/docs/assets/style.css +1640 -1640
- package/docs/classes/DBC.DBC.html +98 -98
- package/docs/classes/DBC_AE.AE.html +160 -160
- package/docs/classes/DBC_EQ.EQ.html +131 -131
- package/docs/classes/DBC_GREATER.GREATER.html +139 -139
- package/docs/classes/DBC_INSTANCE.INSTANCE.html +130 -130
- package/docs/classes/DBC_JSON.OP.JSON_OP.html +138 -138
- package/docs/classes/DBC_JSON.Parse.JSON_Parse.html +129 -129
- package/docs/classes/DBC_OR.OR.html +137 -137
- package/docs/classes/DBC_REGEX.REGEX.html +136 -136
- package/docs/classes/DBC_TYPE.TYPE.html +130 -130
- package/docs/classes/Demo.Demo.html +14 -14
- package/docs/hierarchy.html +1 -1
- package/docs/index.html +1 -1
- package/docs/modules/DBC.html +1 -1
- package/docs/modules/DBC_AE.html +1 -1
- package/docs/modules/DBC_EQ.html +1 -1
- package/docs/modules/DBC_GREATER.html +1 -1
- package/docs/modules/DBC_INSTANCE.html +1 -1
- package/docs/modules/DBC_JSON.OP.html +1 -1
- package/docs/modules/DBC_JSON.Parse.html +1 -1
- package/docs/modules/DBC_OR.html +1 -1
- package/docs/modules/DBC_REGEX.html +1 -1
- package/docs/modules/DBC_TYPE.html +1 -1
- package/docs/modules/Demo.html +1 -1
- package/jest.config.js +32 -32
- package/package.json +71 -55
- package/src/DBC/AE.ts +269 -288
- package/src/DBC/ARR/PLAIN_OBJECT.ts +122 -133
- package/src/DBC/ARRAY.ts +117 -127
- package/src/DBC/COMPARISON/GREATER.ts +41 -46
- package/src/DBC/COMPARISON/GREATER_OR_EQUAL.ts +41 -45
- package/src/DBC/COMPARISON/LESS.ts +41 -45
- package/src/DBC/COMPARISON/LESS_OR_EQUAL.ts +41 -45
- package/src/DBC/COMPARISON.ts +149 -159
- package/src/DBC/DEFINED.ts +117 -122
- package/src/DBC/DOM.ts +453 -0
- package/src/DBC/EQ/DIFFERENT.ts +51 -57
- package/src/DBC/EQ.ts +154 -163
- package/src/DBC/HasAttribute.ts +149 -154
- package/src/DBC/IF.ts +173 -179
- package/src/DBC/INSTANCE.ts +168 -171
- package/src/DBC/JSON.OP.ts +178 -186
- package/src/DBC/JSON.Parse.ts +150 -157
- package/src/DBC/OR.ts +183 -187
- package/src/DBC/REGEX.ts +195 -196
- package/src/DBC/TYPE.ts +142 -149
- package/src/DBC/UNDEFINED.ts +115 -117
- package/src/DBC/ZOD.ts +130 -135
- package/src/DBC.ts +902 -904
- package/src/Demo.ts +537 -404
- package/src/index.ts +22 -0
- package/tsconfig.json +18 -18
- package/tsconfig.test.json +7 -7
- package/typedoc.json +16 -16
- package/webpack.config.js +27 -27
- package/Assessment.md +0 -507
package/src/DBC.ts
CHANGED
|
@@ -1,904 +1,902 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Provides a **D**esign **B**y **C**ontract Framework using decorators.
|
|
3
|
-
*
|
|
4
|
-
* @remarks
|
|
5
|
-
* Maintainer: Callari, Salvatore (XDBC@WaXCode.net) */
|
|
6
|
-
export class DBC {
|
|
7
|
-
// #region Internal caches.
|
|
8
|
-
private static readonly MAX_CACHE_SIZE = 1000;
|
|
9
|
-
private static dbcCache: Map<string, DBC> = new Map();
|
|
10
|
-
private static pathTokenCache: Map<string, string[]> = new Map();
|
|
11
|
-
/** Evicts the oldest entry if the cache exceeds the maximum size. */
|
|
12
|
-
private static evictIfNeeded<K, V>(cache: Map<K, V>): void {
|
|
13
|
-
if (cache.size > DBC.MAX_CACHE_SIZE) {
|
|
14
|
-
const oldest = cache.keys().next().value;
|
|
15
|
-
if (oldest !== undefined) cache.delete(oldest);
|
|
16
|
-
}
|
|
17
|
-
}
|
|
18
|
-
private static getHost(): unknown {
|
|
19
|
-
return typeof window !== "undefined" ? window : globalThis;
|
|
20
|
-
}
|
|
21
|
-
private static getDBC(dbc: string | DBC | undefined): DBC {
|
|
22
|
-
if (dbc instanceof DBC) return dbc;
|
|
23
|
-
const path = dbc ?? "WaXCode.DBC";
|
|
24
|
-
if (DBC.dbcCache.has(path)) {
|
|
25
|
-
return DBC.dbcCache.get(path) as DBC;
|
|
26
|
-
}
|
|
27
|
-
const resolved = DBC.resolveDBCPath(DBC.getHost(), path);
|
|
28
|
-
if (resolved) {
|
|
29
|
-
DBC.evictIfNeeded(DBC.dbcCache);
|
|
30
|
-
DBC.dbcCache.set(path, resolved);
|
|
31
|
-
return resolved;
|
|
32
|
-
}
|
|
33
|
-
throw new Error(
|
|
34
|
-
`[XDBC] DBC instance not found at path "${path}". Ensure a DBC instance is registered there.`,
|
|
35
|
-
);
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
*
|
|
68
|
-
*
|
|
69
|
-
*
|
|
70
|
-
*
|
|
71
|
-
*
|
|
72
|
-
* @param
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
return undefined;
|
|
99
|
-
}
|
|
100
|
-
/**
|
|
101
|
-
* A decorator usable on both **methods** (including setters) and **classes**.
|
|
102
|
-
*
|
|
103
|
-
* - **On a method**: wraps the method so that any {@link DBC } preconditions registered on its parameters
|
|
104
|
-
* via XDBC parameter-decorator factories are dispatched with the actual argument values on each call.
|
|
105
|
-
* - **On a class**: wraps the constructor so that preconditions registered on constructor parameters are
|
|
106
|
-
* dispatched **before** the original constructor body runs, upholding true precondition semantics.
|
|
107
|
-
*
|
|
108
|
-
* In both cases the "receptor" callbacks enlisted in {@link paramValueRequests } by {@link requestParamValue }
|
|
109
|
-
* are invoked with the live argument values.
|
|
110
|
-
*
|
|
111
|
-
* @param targetOrConstructor When used as a **method** decorator: the {@link object } hosting the tagged method.
|
|
112
|
-
* When used as a **class** decorator: the class constructor.
|
|
113
|
-
* @param propertyKey *(method decorator only)* The tagged method's name as provided by the runtime.
|
|
114
|
-
* @param descriptor *(method decorator only)* The {@link PropertyDescriptor } as provided by the runtime.
|
|
115
|
-
*
|
|
116
|
-
* @returns When used as a **method** decorator: the (modified) {@link PropertyDescriptor }.
|
|
117
|
-
* When used as a **class** decorator: a replacement constructor that performs precondition checks. */
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
public static ParamvalueProvider<T extends
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
const
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
if
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
*
|
|
191
|
-
*
|
|
192
|
-
*
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
const realValue = path ? DBC.resolve(this, path) : this;
|
|
245
|
-
// #region Check if all "contracts" are fulfilled.
|
|
246
|
-
for (const contract of contracts) {
|
|
247
|
-
const result = contract.check(realValue);
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
// #
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
*
|
|
272
|
-
*
|
|
273
|
-
*
|
|
274
|
-
*
|
|
275
|
-
*
|
|
276
|
-
*
|
|
277
|
-
*
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
if (!dbcInstance)
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
//
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
*
|
|
331
|
-
*
|
|
332
|
-
* @
|
|
333
|
-
*
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
if (
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
}
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
return
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
const
|
|
604
|
-
const
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
*
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
}
|
|
675
|
-
|
|
676
|
-
*
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
*
|
|
702
|
-
*
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
}
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
// #region
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
*
|
|
734
|
-
*
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
// #endregion
|
|
741
|
-
// #endregion
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
*
|
|
745
|
-
*
|
|
746
|
-
* @param
|
|
747
|
-
*
|
|
748
|
-
*
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
static resolveDBCPath = (obj: any, path: string): DBC =>
|
|
752
|
-
path
|
|
753
|
-
?.split(".")
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
*
|
|
758
|
-
*
|
|
759
|
-
*
|
|
760
|
-
* @param
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
*
|
|
827
|
-
*
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
if (methodMatch) {
|
|
865
|
-
const methodName = methodMatch[1];
|
|
866
|
-
const argsStr = methodMatch[2];
|
|
867
|
-
const args = argsStr.split(",").map((arg) => arg.trim());
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
typeof
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
typeof
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
// Register the default instance with standard settings.
|
|
904
|
-
DBC.register(new DBC());
|
|
1
|
+
/**
|
|
2
|
+
* Provides a **D**esign **B**y **C**ontract Framework using decorators.
|
|
3
|
+
*
|
|
4
|
+
* @remarks
|
|
5
|
+
* Maintainer: Callari, Salvatore (XDBC@WaXCode.net) */
|
|
6
|
+
export class DBC {
|
|
7
|
+
// #region Internal caches.
|
|
8
|
+
private static readonly MAX_CACHE_SIZE = 1000;
|
|
9
|
+
private static dbcCache: Map<string, DBC> = new Map();
|
|
10
|
+
private static pathTokenCache: Map<string, string[]> = new Map();
|
|
11
|
+
/** Evicts the oldest entry if the cache exceeds the maximum size. */
|
|
12
|
+
private static evictIfNeeded<K, V>(cache: Map<K, V>): void {
|
|
13
|
+
if (cache.size > DBC.MAX_CACHE_SIZE) {
|
|
14
|
+
const oldest = cache.keys().next().value;
|
|
15
|
+
if (oldest !== undefined) cache.delete(oldest);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
private static getHost(): unknown {
|
|
19
|
+
return typeof window !== "undefined" ? window : globalThis;
|
|
20
|
+
}
|
|
21
|
+
private static getDBC(dbc: string | DBC | undefined): DBC {
|
|
22
|
+
if (dbc instanceof DBC) return dbc;
|
|
23
|
+
const path = dbc ?? "WaXCode.DBC";
|
|
24
|
+
if (DBC.dbcCache.has(path)) {
|
|
25
|
+
return DBC.dbcCache.get(path) as DBC;
|
|
26
|
+
}
|
|
27
|
+
const resolved = DBC.resolveDBCPath(DBC.getHost(), path);
|
|
28
|
+
if (resolved) {
|
|
29
|
+
DBC.evictIfNeeded(DBC.dbcCache);
|
|
30
|
+
DBC.dbcCache.set(path, resolved);
|
|
31
|
+
return resolved;
|
|
32
|
+
}
|
|
33
|
+
throw new Error(
|
|
34
|
+
`[XDBC] DBC instance not found at path "${path}". Ensure a DBC instance is registered there.`,
|
|
35
|
+
);
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Returns the registered {@link DBC } instance at the specified path.
|
|
39
|
+
* Throws if no instance is registered there.
|
|
40
|
+
*
|
|
41
|
+
* @param path The dotted path to look up (default: `"WaXCode.DBC"`). */
|
|
42
|
+
public static getRegistered(path?: string): DBC {
|
|
43
|
+
return DBC.getDBC(path);
|
|
44
|
+
}
|
|
45
|
+
// #endregion Internal caches.
|
|
46
|
+
// #region Parameter-value requests.
|
|
47
|
+
/** Stores all request for parameter values registered by {@link decPrecondition }. */
|
|
48
|
+
static paramValueRequests: Map<
|
|
49
|
+
string,
|
|
50
|
+
Map<number, Array<(value: any) => undefined>>
|
|
51
|
+
> = new Map<string, Map<number, Array<(value: any) => undefined>>>();
|
|
52
|
+
/**
|
|
53
|
+
* Generate a unique key for storing parameter value requests.
|
|
54
|
+
* Format: "ClassName:methodName"
|
|
55
|
+
*/
|
|
56
|
+
private static getRequestKey(
|
|
57
|
+
target: object,
|
|
58
|
+
methodName: string | symbol | undefined,
|
|
59
|
+
): string {
|
|
60
|
+
const className =
|
|
61
|
+
typeof target === "function"
|
|
62
|
+
? target.name
|
|
63
|
+
: target.constructor?.name || "Unknown";
|
|
64
|
+
return `${className}:${String(methodName)}`;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Make a request to get the value of a certain parameter of specific method in a specific {@link object }.
|
|
68
|
+
* That request gets enlisted in {@link paramValueRequests } which is used by {@link ParamvalueProvider} to invoke the
|
|
69
|
+
* given "receptor" with the parameter value stored in there. Thus a parameter decorator using this method will
|
|
70
|
+
* not receive any value of the top method is not tagged with {@link ParamvalueProvider}.
|
|
71
|
+
*
|
|
72
|
+
* @param target The {@link object } containing the method with the parameter which's value is requested.
|
|
73
|
+
* @param methodName The name of the method with the parameter which's value is requested.
|
|
74
|
+
* @param index The index of the parameter which's value is requested.
|
|
75
|
+
* @param receptor The method the requested parameter-value shall be passed to when it becomes available. */
|
|
76
|
+
protected static requestParamValue(
|
|
77
|
+
target: object,
|
|
78
|
+
methodName: string | symbol | undefined,
|
|
79
|
+
index: number,
|
|
80
|
+
receptor: (value: any) => undefined,
|
|
81
|
+
): undefined {
|
|
82
|
+
const key = DBC.getRequestKey(target, methodName);
|
|
83
|
+
if (DBC.paramValueRequests.has(key)) {
|
|
84
|
+
const paramMap = DBC.paramValueRequests.get(key)!;
|
|
85
|
+
if (paramMap.has(index)) {
|
|
86
|
+
paramMap.get(index)?.push(receptor);
|
|
87
|
+
} else {
|
|
88
|
+
paramMap.set(index, new Array<(value: unknown) => undefined>(receptor));
|
|
89
|
+
}
|
|
90
|
+
} else {
|
|
91
|
+
DBC.paramValueRequests.set(
|
|
92
|
+
key,
|
|
93
|
+
new Map<number, Array<(value: unknown) => undefined>>([
|
|
94
|
+
[index, new Array<(value: unknown) => undefined>(receptor)],
|
|
95
|
+
]),
|
|
96
|
+
);
|
|
97
|
+
}
|
|
98
|
+
return undefined;
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* A decorator usable on both **methods** (including setters) and **classes**.
|
|
102
|
+
*
|
|
103
|
+
* - **On a method**: wraps the method so that any {@link DBC } preconditions registered on its parameters
|
|
104
|
+
* via XDBC parameter-decorator factories are dispatched with the actual argument values on each call.
|
|
105
|
+
* - **On a class**: wraps the constructor so that preconditions registered on constructor parameters are
|
|
106
|
+
* dispatched **before** the original constructor body runs, upholding true precondition semantics.
|
|
107
|
+
*
|
|
108
|
+
* In both cases the "receptor" callbacks enlisted in {@link paramValueRequests } by {@link requestParamValue }
|
|
109
|
+
* are invoked with the live argument values.
|
|
110
|
+
*
|
|
111
|
+
* @param targetOrConstructor When used as a **method** decorator: the {@link object } hosting the tagged method.
|
|
112
|
+
* When used as a **class** decorator: the class constructor.
|
|
113
|
+
* @param propertyKey *(method decorator only)* The tagged method's name as provided by the runtime.
|
|
114
|
+
* @param descriptor *(method decorator only)* The {@link PropertyDescriptor } as provided by the runtime.
|
|
115
|
+
*
|
|
116
|
+
* @returns When used as a **method** decorator: the (modified) {@link PropertyDescriptor }.
|
|
117
|
+
* When used as a **class** decorator: a replacement constructor that performs precondition checks. */
|
|
118
|
+
public static ParamvalueProvider(
|
|
119
|
+
target: object,
|
|
120
|
+
propertyKey: string,
|
|
121
|
+
descriptor: PropertyDescriptor,
|
|
122
|
+
): PropertyDescriptor;
|
|
123
|
+
public static ParamvalueProvider<T extends new (...args: any[]) => any>(
|
|
124
|
+
ctor: T,
|
|
125
|
+
): T;
|
|
126
|
+
public static ParamvalueProvider<
|
|
127
|
+
T extends abstract new (
|
|
128
|
+
...args: any[]
|
|
129
|
+
) => any,
|
|
130
|
+
>(ctor: T): T;
|
|
131
|
+
public static ParamvalueProvider(...args: any[]): any {
|
|
132
|
+
if (args.length === 1 && typeof args[0] === "function") {
|
|
133
|
+
// #region Class decorator path
|
|
134
|
+
const ctor = args[0] as new (...args: any[]) => any;
|
|
135
|
+
const key = `${ctor.name}:undefined`;
|
|
136
|
+
const WrappedClass = class extends ctor {
|
|
137
|
+
constructor(...ctorArgs: any[]) {
|
|
138
|
+
if (DBC.paramValueRequests.has(key)) {
|
|
139
|
+
const paramMap = DBC.paramValueRequests.get(key)!;
|
|
140
|
+
for (const index of paramMap.keys()) {
|
|
141
|
+
if (index < ctorArgs.length) {
|
|
142
|
+
for (const receptor of paramMap.get(index)!) {
|
|
143
|
+
receptor(ctorArgs[index]);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
super(...ctorArgs);
|
|
149
|
+
}
|
|
150
|
+
} as typeof ctor;
|
|
151
|
+
Object.defineProperty(WrappedClass, "name", { value: ctor.name });
|
|
152
|
+
return WrappedClass;
|
|
153
|
+
// #endregion Class decorator path
|
|
154
|
+
}
|
|
155
|
+
// #region Method decorator path
|
|
156
|
+
const [target, propertyKey, descriptor] = args as [
|
|
157
|
+
object,
|
|
158
|
+
string,
|
|
159
|
+
PropertyDescriptor,
|
|
160
|
+
];
|
|
161
|
+
const originalMethod = descriptor.value;
|
|
162
|
+
const isStatic = typeof target === "function";
|
|
163
|
+
descriptor.value = function (...methodArgs: any[]) {
|
|
164
|
+
// #region Check if a value of one of the method's parameter has been requested and pass it to the
|
|
165
|
+
// receptor, if so.
|
|
166
|
+
const actualTarget = isStatic ? this : (this as any).constructor;
|
|
167
|
+
const key = DBC.getRequestKey(actualTarget, propertyKey);
|
|
168
|
+
if (DBC.paramValueRequests.has(key)) {
|
|
169
|
+
const paramMap = DBC.paramValueRequests.get(key)!;
|
|
170
|
+
for (const index of paramMap.keys()) {
|
|
171
|
+
if (index < methodArgs.length) {
|
|
172
|
+
for (const receptor of paramMap.get(index)!) {
|
|
173
|
+
receptor(methodArgs[index]);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
} else {
|
|
178
|
+
console.warn("No parameter value requests found for key:", key);
|
|
179
|
+
}
|
|
180
|
+
// #endregion Check if a value of one of the method's parameter has been requested and pass it to the
|
|
181
|
+
// receptor, if so.
|
|
182
|
+
return originalMethod.apply(this, methodArgs);
|
|
183
|
+
};
|
|
184
|
+
return descriptor;
|
|
185
|
+
// #endregion Method decorator path
|
|
186
|
+
}
|
|
187
|
+
// #endregion Parameter-value requests.
|
|
188
|
+
// #region Class
|
|
189
|
+
/**
|
|
190
|
+
* A property-decorator factory serving as a **D**esign **B**y **C**ontract Invariant.
|
|
191
|
+
* This invariant aims to check the instance of the class not the value to be get or set.
|
|
192
|
+
*
|
|
193
|
+
* @param contracts The {@link DBC }-Contracts the value shall uphold.
|
|
194
|
+
*
|
|
195
|
+
* @throws A {@link DBC.Infringement } whenever the property is tried to be get or set without the instance of it's class
|
|
196
|
+
* fulfilling the specified **contracts**. */
|
|
197
|
+
public static decClassInvariant(
|
|
198
|
+
contracts: Array<{
|
|
199
|
+
check: (toCheck: unknown | null | undefined) => boolean | string;
|
|
200
|
+
}>,
|
|
201
|
+
path: string | undefined = undefined,
|
|
202
|
+
dbc = "WaXCode.DBC",
|
|
203
|
+
) {
|
|
204
|
+
let dbcInstance: DBC | undefined;
|
|
205
|
+
return (
|
|
206
|
+
target: unknown,
|
|
207
|
+
propertyKey: string | symbol,
|
|
208
|
+
descriptor: PropertyDescriptor,
|
|
209
|
+
) => {
|
|
210
|
+
if (!dbcInstance) dbcInstance = DBC.getDBC(dbc);
|
|
211
|
+
if (!dbcInstance.executionSettings.checkInvariants) {
|
|
212
|
+
return;
|
|
213
|
+
}
|
|
214
|
+
const originalSetter = descriptor.set;
|
|
215
|
+
const originalGetter = descriptor.get;
|
|
216
|
+
let value: any;
|
|
217
|
+
// #region Replace original property.
|
|
218
|
+
Object.defineProperty(target, propertyKey, {
|
|
219
|
+
get() {
|
|
220
|
+
if (!dbcInstance?.executionSettings.checkInvariants) {
|
|
221
|
+
return;
|
|
222
|
+
}
|
|
223
|
+
const realValue = path ? DBC.resolve(this, path) : this;
|
|
224
|
+
// #region Check if all "contracts" are fulfilled.
|
|
225
|
+
for (const contract of contracts) {
|
|
226
|
+
const result = contract.check(realValue);
|
|
227
|
+
if (typeof result === "string") {
|
|
228
|
+
dbcInstance?.reportFieldInfringement(
|
|
229
|
+
result,
|
|
230
|
+
target as object,
|
|
231
|
+
path,
|
|
232
|
+
propertyKey as string,
|
|
233
|
+
realValue,
|
|
234
|
+
);
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
// #endregion Check if all "contracts" are fulfilled.
|
|
238
|
+
return originalGetter ? (originalGetter as any)[propertyKey] : value;
|
|
239
|
+
},
|
|
240
|
+
set(newValue) {
|
|
241
|
+
if (!dbcInstance?.executionSettings.checkInvariants) {
|
|
242
|
+
return;
|
|
243
|
+
}
|
|
244
|
+
const realValue = path ? DBC.resolve(this, path) : this;
|
|
245
|
+
// #region Check if all "contracts" are fulfilled.
|
|
246
|
+
for (const contract of contracts) {
|
|
247
|
+
const result = contract.check(realValue);
|
|
248
|
+
if (typeof result === "string") {
|
|
249
|
+
dbcInstance?.reportFieldInfringement(
|
|
250
|
+
result,
|
|
251
|
+
target as object,
|
|
252
|
+
path,
|
|
253
|
+
propertyKey as string,
|
|
254
|
+
realValue,
|
|
255
|
+
);
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
// #endregion Check if all "contracts" are fulfilled.
|
|
259
|
+
value = newValue;
|
|
260
|
+
},
|
|
261
|
+
enumerable: true,
|
|
262
|
+
configurable: true,
|
|
263
|
+
});
|
|
264
|
+
// #endregion Replace original property.
|
|
265
|
+
};
|
|
266
|
+
}
|
|
267
|
+
// #endregion Class
|
|
268
|
+
// #region Invariant
|
|
269
|
+
/**
|
|
270
|
+
* A property-decorator factory serving as a **D**esign **B**y **C**ontract Invariant.
|
|
271
|
+
* Since the value must be initialized or set according to the specified **contracts** the value will only be checked
|
|
272
|
+
* when assigning it.
|
|
273
|
+
*
|
|
274
|
+
* @param contracts The {@link DBC }-Contracts the value shall uphold.
|
|
275
|
+
*
|
|
276
|
+
* @throws A {@link DBC.Infringement } whenever the property is tried to be set to a value that does not comply to the
|
|
277
|
+
* specified **contracts**, by the returned method.*/
|
|
278
|
+
public static decInvariant(
|
|
279
|
+
contracts: Array<{
|
|
280
|
+
check: (toCheck: unknown | null | undefined) => boolean | string;
|
|
281
|
+
}>,
|
|
282
|
+
path: string | undefined = undefined,
|
|
283
|
+
dbc: string | undefined = undefined,
|
|
284
|
+
hint: string | undefined = undefined,
|
|
285
|
+
) {
|
|
286
|
+
let dbcInstance: DBC | undefined;
|
|
287
|
+
return (target: unknown, propertyKey: string | symbol) => {
|
|
288
|
+
if (!dbcInstance) dbcInstance = DBC.getDBC(dbc);
|
|
289
|
+
if (!dbcInstance.executionSettings.checkInvariants) {
|
|
290
|
+
return;
|
|
291
|
+
}
|
|
292
|
+
let value: any;
|
|
293
|
+
// #region Replace original property.
|
|
294
|
+
Object.defineProperty(target, propertyKey, {
|
|
295
|
+
set(newValue) {
|
|
296
|
+
if (!dbcInstance?.executionSettings.checkInvariants) {
|
|
297
|
+
return;
|
|
298
|
+
}
|
|
299
|
+
const realValue = path ? DBC.resolve(newValue, path) : newValue;
|
|
300
|
+
// #region Check if all "contracts" are fulfilled.
|
|
301
|
+
for (const contract of contracts) {
|
|
302
|
+
const result = contract.check(realValue);
|
|
303
|
+
if (typeof result === "string") {
|
|
304
|
+
dbcInstance?.reportFieldInfringement(
|
|
305
|
+
result,
|
|
306
|
+
target as object,
|
|
307
|
+
path,
|
|
308
|
+
propertyKey as string,
|
|
309
|
+
realValue,
|
|
310
|
+
hint,
|
|
311
|
+
);
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
// #endregion Check if all "contracts" are fulfilled.
|
|
315
|
+
value = newValue;
|
|
316
|
+
},
|
|
317
|
+
enumerable: true,
|
|
318
|
+
configurable: true,
|
|
319
|
+
});
|
|
320
|
+
// #endregion Replace original property.
|
|
321
|
+
};
|
|
322
|
+
}
|
|
323
|
+
// #endregion Invariant
|
|
324
|
+
// #region Postcondition
|
|
325
|
+
/**
|
|
326
|
+
* A method decorator factory checking the result of a method whenever it is invoked thus also usable on getters.
|
|
327
|
+
*
|
|
328
|
+
* @param check The **(toCheck: any, object, string) => boolean | string** to use for checking.
|
|
329
|
+
* @param dbc See {@link DBC.resolveDBCPath }.
|
|
330
|
+
* @param path The dotted path referring to the actual value to check, starting form the specified one.
|
|
331
|
+
*
|
|
332
|
+
* @returns The **( target : object, propertyKey : string, descriptor : PropertyDescriptor ) : PropertyDescriptor**
|
|
333
|
+
* invoked by Typescript.
|
|
334
|
+
*/
|
|
335
|
+
public static decPostcondition(
|
|
336
|
+
check: (
|
|
337
|
+
toCheck: any,
|
|
338
|
+
target: object,
|
|
339
|
+
propertyKey: string,
|
|
340
|
+
) => boolean | string,
|
|
341
|
+
dbc: string | undefined = undefined,
|
|
342
|
+
path: string | undefined = undefined,
|
|
343
|
+
hint: string | undefined = undefined,
|
|
344
|
+
) {
|
|
345
|
+
let dbcInstance: DBC | undefined;
|
|
346
|
+
return (
|
|
347
|
+
target: object,
|
|
348
|
+
propertyKey: string,
|
|
349
|
+
descriptor: PropertyDescriptor,
|
|
350
|
+
): PropertyDescriptor => {
|
|
351
|
+
const originalMethod = descriptor.value;
|
|
352
|
+
descriptor.value = (...args: any[]) => {
|
|
353
|
+
if (!dbcInstance) dbcInstance = DBC.getDBC(dbc);
|
|
354
|
+
if (!dbcInstance.executionSettings.checkPostconditions) {
|
|
355
|
+
return;
|
|
356
|
+
}
|
|
357
|
+
// biome-ignore lint/complexity/noThisInStatic: <explanation>
|
|
358
|
+
const result = originalMethod.apply(this, args);
|
|
359
|
+
const realValue = path ? DBC.resolve(result, path) : result;
|
|
360
|
+
const checkResult = check(realValue, target, propertyKey);
|
|
361
|
+
if (typeof checkResult === "string") {
|
|
362
|
+
dbcInstance.reportReturnvalueInfringement(
|
|
363
|
+
checkResult,
|
|
364
|
+
target,
|
|
365
|
+
path,
|
|
366
|
+
propertyKey,
|
|
367
|
+
realValue,
|
|
368
|
+
hint,
|
|
369
|
+
);
|
|
370
|
+
}
|
|
371
|
+
return result;
|
|
372
|
+
};
|
|
373
|
+
return descriptor;
|
|
374
|
+
};
|
|
375
|
+
}
|
|
376
|
+
// #endregion Postcondition
|
|
377
|
+
// #region Decorator
|
|
378
|
+
// #region Precondition
|
|
379
|
+
/**
|
|
380
|
+
* A parameter-decorator factory that requests the tagged parameter's value passing it to the provided
|
|
381
|
+
* "check"-method when the value becomes available.
|
|
382
|
+
*
|
|
383
|
+
* @param check The "( unknown ) => void" to be invoked along with the tagged parameter's value as soon
|
|
384
|
+
* as it becomes available.
|
|
385
|
+
* @param dbc See {@link DBC.resolveDBCPath }.
|
|
386
|
+
* @param path The dotted path referring to the actual value to check, starting form the specified one.
|
|
387
|
+
* May contain :: to separate multiple paths.
|
|
388
|
+
*
|
|
389
|
+
* @returns The **(target: object, methodName: string | symbol, parameterIndex: number ) => void** invoked by Typescript- */
|
|
390
|
+
protected static decPrecondition(
|
|
391
|
+
check: (
|
|
392
|
+
value: unknown,
|
|
393
|
+
target: object,
|
|
394
|
+
methodName: string | symbol | undefined,
|
|
395
|
+
parameterIndex: number,
|
|
396
|
+
) => boolean | string,
|
|
397
|
+
dbc: string | undefined = undefined,
|
|
398
|
+
path: string | undefined = undefined,
|
|
399
|
+
hint: string | undefined = undefined,
|
|
400
|
+
): (
|
|
401
|
+
target: object,
|
|
402
|
+
methodName: string | symbol | undefined,
|
|
403
|
+
parameterIndex: number,
|
|
404
|
+
) => void {
|
|
405
|
+
const paths = path ? path.replace(/ /g, "").split("::") : [undefined];
|
|
406
|
+
let dbcInstance: DBC | undefined;
|
|
407
|
+
return (
|
|
408
|
+
target: object,
|
|
409
|
+
methodName: string | symbol | undefined,
|
|
410
|
+
parameterIndex: number,
|
|
411
|
+
): void => {
|
|
412
|
+
DBC.requestParamValue(
|
|
413
|
+
target,
|
|
414
|
+
methodName,
|
|
415
|
+
parameterIndex,
|
|
416
|
+
(value: unknown) => {
|
|
417
|
+
if (!dbcInstance) dbcInstance = DBC.getDBC(dbc);
|
|
418
|
+
if (!dbcInstance.executionSettings.checkPreconditions) {
|
|
419
|
+
return;
|
|
420
|
+
}
|
|
421
|
+
for (const singlePath of paths) {
|
|
422
|
+
const realValue = singlePath
|
|
423
|
+
? DBC.resolve(value, singlePath)
|
|
424
|
+
: value;
|
|
425
|
+
const result = check(realValue, target, methodName, parameterIndex);
|
|
426
|
+
if (typeof result === "string") {
|
|
427
|
+
dbcInstance.reportParameterInfringement(
|
|
428
|
+
result,
|
|
429
|
+
target,
|
|
430
|
+
singlePath,
|
|
431
|
+
methodName as string,
|
|
432
|
+
parameterIndex,
|
|
433
|
+
realValue,
|
|
434
|
+
hint,
|
|
435
|
+
);
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
},
|
|
439
|
+
);
|
|
440
|
+
};
|
|
441
|
+
}
|
|
442
|
+
// #endregion Precondition
|
|
443
|
+
// #endregion Decorator
|
|
444
|
+
// #region Contract Factory Helpers
|
|
445
|
+
/**
|
|
446
|
+
* Creates a PRE decorator from a checkAlgorithm function and its bound arguments.
|
|
447
|
+
* Reduces boilerplate across contract classes.
|
|
448
|
+
*
|
|
449
|
+
* @param checkFn A function that takes (value, ...boundArgs) and returns true or an error string.
|
|
450
|
+
* @param boundArgs The arguments to bind to the check function after the value.
|
|
451
|
+
* @param dbc See {@link DBC.decPrecondition}.
|
|
452
|
+
* @param path See {@link DBC.decPrecondition}.
|
|
453
|
+
* @param hint See {@link DBC.decPrecondition}.
|
|
454
|
+
*/
|
|
455
|
+
public static createPRE(
|
|
456
|
+
checkFn: (...args: any[]) => boolean | string,
|
|
457
|
+
boundArgs: any[],
|
|
458
|
+
dbc?: string,
|
|
459
|
+
path?: string,
|
|
460
|
+
hint?: string,
|
|
461
|
+
) {
|
|
462
|
+
return DBC.decPrecondition(
|
|
463
|
+
(value, _target, _methodName, _parameterIndex) => {
|
|
464
|
+
return checkFn(value, ...boundArgs);
|
|
465
|
+
},
|
|
466
|
+
dbc,
|
|
467
|
+
path,
|
|
468
|
+
hint,
|
|
469
|
+
);
|
|
470
|
+
}
|
|
471
|
+
/**
|
|
472
|
+
* Creates a POST decorator from a checkAlgorithm function and its bound arguments.
|
|
473
|
+
*
|
|
474
|
+
* @param checkFn A function that takes (value, ...boundArgs) and returns true or an error string.
|
|
475
|
+
* @param boundArgs The arguments to bind to the check function after the value.
|
|
476
|
+
* @param dbc See {@link DBC.decPostcondition}.
|
|
477
|
+
* @param path See {@link DBC.decPostcondition}.
|
|
478
|
+
* @param hint See {@link DBC.decPostcondition}.
|
|
479
|
+
*/
|
|
480
|
+
public static createPOST(
|
|
481
|
+
checkFn: (...args: any[]) => boolean | string,
|
|
482
|
+
boundArgs: any[],
|
|
483
|
+
dbc?: string,
|
|
484
|
+
path?: string,
|
|
485
|
+
hint?: string,
|
|
486
|
+
) {
|
|
487
|
+
return DBC.decPostcondition(
|
|
488
|
+
(value, _target, _propertyKey) => {
|
|
489
|
+
return checkFn(value, ...boundArgs);
|
|
490
|
+
},
|
|
491
|
+
dbc,
|
|
492
|
+
path,
|
|
493
|
+
hint,
|
|
494
|
+
);
|
|
495
|
+
}
|
|
496
|
+
/**
|
|
497
|
+
* Creates an INVARIANT decorator from a contract constructor and its bound arguments.
|
|
498
|
+
*
|
|
499
|
+
* @param ContractClass A class with a constructor that produces an object with a `check` method.
|
|
500
|
+
* @param ctorArgs The arguments to pass to the contract constructor.
|
|
501
|
+
* @param dbc See {@link DBC.decInvariant}.
|
|
502
|
+
* @param path See {@link DBC.decInvariant}.
|
|
503
|
+
* @param hint See {@link DBC.decInvariant}.
|
|
504
|
+
*/
|
|
505
|
+
public static createINVARIANT(
|
|
506
|
+
ContractClass: new (
|
|
507
|
+
...args: any[]
|
|
508
|
+
) => { check: (toCheck: unknown | null | undefined) => boolean | string },
|
|
509
|
+
ctorArgs: any[],
|
|
510
|
+
dbc?: string,
|
|
511
|
+
path?: string,
|
|
512
|
+
hint?: string,
|
|
513
|
+
) {
|
|
514
|
+
return DBC.decInvariant([new ContractClass(...ctorArgs)], path, dbc, hint);
|
|
515
|
+
}
|
|
516
|
+
// #endregion Contract Factory Helpers
|
|
517
|
+
// #region Execution Handling
|
|
518
|
+
/** Stores settings concerning the execution of checks. */
|
|
519
|
+
public executionSettings: {
|
|
520
|
+
checkPreconditions: boolean;
|
|
521
|
+
checkPostconditions: boolean;
|
|
522
|
+
checkInvariants: boolean;
|
|
523
|
+
} = {
|
|
524
|
+
checkPreconditions: true,
|
|
525
|
+
checkPostconditions: true,
|
|
526
|
+
checkInvariants: true,
|
|
527
|
+
};
|
|
528
|
+
// #endregion Execution Handling
|
|
529
|
+
// #region Warning handling.
|
|
530
|
+
/** Stores settings concerning warnings. */
|
|
531
|
+
public warningSettings: {
|
|
532
|
+
logToConsole: boolean;
|
|
533
|
+
} = { logToConsole: true };
|
|
534
|
+
/**
|
|
535
|
+
* Reports a warning.
|
|
536
|
+
*
|
|
537
|
+
* @param message The message containing the warning. */
|
|
538
|
+
protected reportWarning(message: string): undefined {
|
|
539
|
+
if (this.warningSettings.logToConsole) {
|
|
540
|
+
console.warn(message);
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
// #endregion Warning handling.
|
|
544
|
+
// #region infringement handling.
|
|
545
|
+
/** Stores the settings concerning infringements */
|
|
546
|
+
public infringementSettings: {
|
|
547
|
+
throwException: boolean;
|
|
548
|
+
logToConsole: boolean;
|
|
549
|
+
onInfringement:
|
|
550
|
+
| ((
|
|
551
|
+
infringement: InstanceType<typeof DBC.Infringement>,
|
|
552
|
+
context: {
|
|
553
|
+
type: "precondition" | "postcondition" | "invariant";
|
|
554
|
+
value: unknown;
|
|
555
|
+
},
|
|
556
|
+
) => void)
|
|
557
|
+
| undefined;
|
|
558
|
+
} = { throwException: true, logToConsole: false, onInfringement: undefined };
|
|
559
|
+
/** Sanitizes a value for safe inclusion in error messages. */
|
|
560
|
+
private static sanitize(value: unknown): string {
|
|
561
|
+
const str = typeof value === "string" ? value : String(value);
|
|
562
|
+
return str.replace(/[<>&"']/g, (ch) => {
|
|
563
|
+
switch (ch) {
|
|
564
|
+
case "<":
|
|
565
|
+
return "<";
|
|
566
|
+
case ">":
|
|
567
|
+
return ">";
|
|
568
|
+
case "&":
|
|
569
|
+
return "&";
|
|
570
|
+
case '"':
|
|
571
|
+
return """;
|
|
572
|
+
case "'":
|
|
573
|
+
return "'";
|
|
574
|
+
default:
|
|
575
|
+
return ch;
|
|
576
|
+
}
|
|
577
|
+
});
|
|
578
|
+
}
|
|
579
|
+
/**
|
|
580
|
+
* Reports an infringement according to the {@link infringementSettings } also generating a proper {@link string }-wrapper
|
|
581
|
+
* for the given "message" & violator.
|
|
582
|
+
*
|
|
583
|
+
* @param message The {@link string } describing the infringement and it's provenience.
|
|
584
|
+
* @param violator The {@link string } describing or naming the violator. */
|
|
585
|
+
protected reportInfringement(
|
|
586
|
+
message: string,
|
|
587
|
+
violator: string,
|
|
588
|
+
target: object,
|
|
589
|
+
value: unknown,
|
|
590
|
+
path: string | undefined,
|
|
591
|
+
hint: string | undefined = undefined,
|
|
592
|
+
type: "precondition" | "postcondition" | "invariant" = "precondition",
|
|
593
|
+
): undefined {
|
|
594
|
+
const safeViolator = DBC.sanitize(violator);
|
|
595
|
+
const targetName =
|
|
596
|
+
typeof target === "function"
|
|
597
|
+
? DBC.sanitize(target.name)
|
|
598
|
+
: typeof target === "object" &&
|
|
599
|
+
target !== null &&
|
|
600
|
+
typeof target.constructor === "function"
|
|
601
|
+
? DBC.sanitize(target.constructor.name)
|
|
602
|
+
: DBC.sanitize(target);
|
|
603
|
+
const finalMessage: string = `[ From "${safeViolator}" in "${targetName}"${path ? ` > "${DBC.sanitize(path)}"` : ""}: ${message} ${hint ? `✨ ${hint} ✨` : ""}]`;
|
|
604
|
+
const infringement = new DBC.Infringement(finalMessage);
|
|
605
|
+
if (this.infringementSettings.logToConsole) {
|
|
606
|
+
console.log(finalMessage);
|
|
607
|
+
}
|
|
608
|
+
if (this.infringementSettings.onInfringement) {
|
|
609
|
+
this.infringementSettings.onInfringement(infringement, { type, value });
|
|
610
|
+
}
|
|
611
|
+
if (this.infringementSettings.throwException) {
|
|
612
|
+
throw infringement;
|
|
613
|
+
}
|
|
614
|
+
}
|
|
615
|
+
/**
|
|
616
|
+
* Reports a parameter-infringement via {@link reportInfringement } also generating a proper {@link string }-wrapper
|
|
617
|
+
* for the given "message","method", parameter-"index" & value.
|
|
618
|
+
*
|
|
619
|
+
* @param message The {@link string } describing the infringement and it's provenience.
|
|
620
|
+
* @param method The {@link string } describing or naming the violator.
|
|
621
|
+
* @param index The index of the parameter within the argument listing.
|
|
622
|
+
* @param value The parameter's value. */
|
|
623
|
+
public reportParameterInfringement(
|
|
624
|
+
message: string,
|
|
625
|
+
target: object,
|
|
626
|
+
path: string | undefined,
|
|
627
|
+
method: string,
|
|
628
|
+
index: number,
|
|
629
|
+
value: unknown,
|
|
630
|
+
hint: string | undefined = undefined,
|
|
631
|
+
): undefined {
|
|
632
|
+
const properIndex = index + 1;
|
|
633
|
+
this.reportInfringement(
|
|
634
|
+
`[ Parameter-value "${value}" of the ${properIndex}${properIndex === 1 ? "st" : properIndex === 2 ? "nd" : properIndex === 3 ? "rd" : "th"} parameter did not fulfill one of it's contracts: ${message} ]`,
|
|
635
|
+
method,
|
|
636
|
+
target,
|
|
637
|
+
value,
|
|
638
|
+
path,
|
|
639
|
+
hint,
|
|
640
|
+
"precondition",
|
|
641
|
+
);
|
|
642
|
+
}
|
|
643
|
+
/**
|
|
644
|
+
* Reports a field-infringement via {@link reportInfringement } also generating a proper {@link string }-wrapper
|
|
645
|
+
* for the given **message** & **name**.
|
|
646
|
+
*
|
|
647
|
+
* @param message A {@link string } describing the infringement and it's provenience.
|
|
648
|
+
* @param key The property key.
|
|
649
|
+
* @param path The dotted-path {@link string } that leads to the value not fulfilling the contract starting from
|
|
650
|
+
* the tagged one.
|
|
651
|
+
* @param value The value not fulfilling a contract. */
|
|
652
|
+
public reportFieldInfringement(
|
|
653
|
+
message: string,
|
|
654
|
+
target: object,
|
|
655
|
+
path: string | undefined,
|
|
656
|
+
key: string,
|
|
657
|
+
value: unknown,
|
|
658
|
+
hint: string | undefined = undefined,
|
|
659
|
+
): undefined {
|
|
660
|
+
this.reportInfringement(
|
|
661
|
+
`[ New value for "${key}"${path === undefined ? "" : `.${path}`} with value "${value}" did not fulfill one of it's contracts: ${message} ]`,
|
|
662
|
+
key,
|
|
663
|
+
target,
|
|
664
|
+
value,
|
|
665
|
+
path,
|
|
666
|
+
hint,
|
|
667
|
+
"invariant",
|
|
668
|
+
);
|
|
669
|
+
}
|
|
670
|
+
/**
|
|
671
|
+
* Reports a returnvalue-infringement according via {@link reportInfringement } also generating a proper {@link string }-wrapper
|
|
672
|
+
* for the given "message","method" & value.
|
|
673
|
+
*
|
|
674
|
+
* @param message The {@link string } describing the infringement and it's provenience.
|
|
675
|
+
* @param method The {@link string } describing or naming the violator.
|
|
676
|
+
* @param value The parameter's value. */
|
|
677
|
+
public reportReturnvalueInfringement(
|
|
678
|
+
message: string,
|
|
679
|
+
target: object,
|
|
680
|
+
path: string | undefined,
|
|
681
|
+
method: string,
|
|
682
|
+
value: any,
|
|
683
|
+
hint: string | undefined = undefined,
|
|
684
|
+
) {
|
|
685
|
+
this.reportInfringement(
|
|
686
|
+
`[ Return-value "${value}" did not fulfill one of it's contracts: ${message} ]`,
|
|
687
|
+
method,
|
|
688
|
+
target,
|
|
689
|
+
value,
|
|
690
|
+
path,
|
|
691
|
+
hint,
|
|
692
|
+
"postcondition",
|
|
693
|
+
);
|
|
694
|
+
}
|
|
695
|
+
/**
|
|
696
|
+
* Routes an imperative infringement (from {@link tsCheck } or static {@link check } calls) through the
|
|
697
|
+
* registered DBC instance's {@link infringementSettings }, respecting whether to throw, log, or both.
|
|
698
|
+
* Falls back to throwing {@link DBC.Infringement } directly if no DBC instance is registered at the
|
|
699
|
+
* specified path, preserving the pre-existing behaviour for code that does not register a DBC instance.
|
|
700
|
+
*
|
|
701
|
+
* @param message The fully formatted infringement message.
|
|
702
|
+
* @param dbc The path to the DBC instance. Defaults to `"WaXCode.DBC"`. */
|
|
703
|
+
public static reportTsCheckInfringement(
|
|
704
|
+
message: string,
|
|
705
|
+
dbc: string | undefined = undefined,
|
|
706
|
+
value: unknown = undefined,
|
|
707
|
+
): void {
|
|
708
|
+
const infringement = new DBC.Infringement(message);
|
|
709
|
+
let dbcInstance: DBC | undefined;
|
|
710
|
+
try {
|
|
711
|
+
dbcInstance = DBC.getDBC(dbc);
|
|
712
|
+
} catch {
|
|
713
|
+
throw infringement;
|
|
714
|
+
}
|
|
715
|
+
if (dbcInstance.infringementSettings.logToConsole) {
|
|
716
|
+
console.log(message);
|
|
717
|
+
}
|
|
718
|
+
if (dbcInstance.infringementSettings.onInfringement) {
|
|
719
|
+
dbcInstance.infringementSettings.onInfringement(infringement, {
|
|
720
|
+
type: "precondition",
|
|
721
|
+
value,
|
|
722
|
+
});
|
|
723
|
+
}
|
|
724
|
+
if (dbcInstance.infringementSettings.throwException) {
|
|
725
|
+
throw infringement;
|
|
726
|
+
}
|
|
727
|
+
}
|
|
728
|
+
// #region Classes
|
|
729
|
+
// #region Errors
|
|
730
|
+
/** An {@link Error } to be thrown whenever an infringement is detected. */
|
|
731
|
+
public static Infringement = class extends Error {
|
|
732
|
+
/**
|
|
733
|
+
* Constructs this {@link Error } by tagging the specified message-{@link string } as an XDBC-Infringement.
|
|
734
|
+
*
|
|
735
|
+
* @param message The {@link string } describing the infringement. */
|
|
736
|
+
constructor(message: string) {
|
|
737
|
+
super(`[ XDBC Infringement ${message}]`);
|
|
738
|
+
}
|
|
739
|
+
};
|
|
740
|
+
// #endregion Errors
|
|
741
|
+
// #endregion Classes
|
|
742
|
+
// #endregion infringement handling.
|
|
743
|
+
/**
|
|
744
|
+
* Resolves the specified dotted {@link string }-path to a {@link DBC }.
|
|
745
|
+
*
|
|
746
|
+
* @param obj The {@link object } to start resolving from.
|
|
747
|
+
* @param path The dotted {@link string }-path leading to the {@link DBC }.
|
|
748
|
+
*
|
|
749
|
+
* @returns The requested {@link DBC }.
|
|
750
|
+
*/
|
|
751
|
+
static resolveDBCPath = (obj: any, path: string): DBC =>
|
|
752
|
+
path
|
|
753
|
+
?.split(".")
|
|
754
|
+
.reduce((accumulator: any, current: string) => accumulator[current], obj);
|
|
755
|
+
/**
|
|
756
|
+
* Constructs this {@link DBC } without mounting it on the global namespace.
|
|
757
|
+
* Use {@link DBC.register } to make the instance available at a specific path on globalThis.
|
|
758
|
+
*
|
|
759
|
+
* @param infringementSettings See {@link DBC.infringementSettings }.
|
|
760
|
+
* @param executionSettings See {@link DBC.executionSettings }. */
|
|
761
|
+
constructor(
|
|
762
|
+
infringementSettings: {
|
|
763
|
+
throwException: boolean;
|
|
764
|
+
logToConsole: boolean;
|
|
765
|
+
onInfringement?: (
|
|
766
|
+
infringement: InstanceType<typeof DBC.Infringement>,
|
|
767
|
+
context: {
|
|
768
|
+
type: "precondition" | "postcondition" | "invariant";
|
|
769
|
+
value: unknown;
|
|
770
|
+
},
|
|
771
|
+
) => void;
|
|
772
|
+
} = { throwException: true, logToConsole: false },
|
|
773
|
+
executionSettings: {
|
|
774
|
+
checkPreconditions: boolean;
|
|
775
|
+
checkPostconditions: boolean;
|
|
776
|
+
checkInvariants: boolean;
|
|
777
|
+
} = {
|
|
778
|
+
checkPreconditions: true,
|
|
779
|
+
checkPostconditions: true,
|
|
780
|
+
checkInvariants: true,
|
|
781
|
+
},
|
|
782
|
+
) {
|
|
783
|
+
this.infringementSettings = {
|
|
784
|
+
...infringementSettings,
|
|
785
|
+
onInfringement: infringementSettings.onInfringement ?? undefined,
|
|
786
|
+
};
|
|
787
|
+
this.executionSettings = executionSettings;
|
|
788
|
+
}
|
|
789
|
+
/**
|
|
790
|
+
* Registers a {@link DBC } instance at the specified dotted path on globalThis (or window),
|
|
791
|
+
* making it available for decorator resolution via string paths.
|
|
792
|
+
*
|
|
793
|
+
* @param instance The {@link DBC } instance to register.
|
|
794
|
+
* @param path The dotted path to register at (default: `"WaXCode.DBC"`). */
|
|
795
|
+
static register(instance: DBC, path = "WaXCode.DBC"): void {
|
|
796
|
+
const segments = path.split(".");
|
|
797
|
+
let obj: any = DBC.getHost();
|
|
798
|
+
for (let i = 0; i < segments.length - 1; i++) {
|
|
799
|
+
if (obj[segments[i]] === undefined) obj[segments[i]] = {};
|
|
800
|
+
obj = obj[segments[i]];
|
|
801
|
+
}
|
|
802
|
+
obj[segments[segments.length - 1]] = instance;
|
|
803
|
+
DBC.dbcCache.set(path, instance);
|
|
804
|
+
}
|
|
805
|
+
/**
|
|
806
|
+
* Executes a callback with an isolated {@link DBC } instance temporarily registered at the default path.
|
|
807
|
+
* The previous instance (if any) is restored after the callback completes — even if it throws.
|
|
808
|
+
* Useful for test isolation.
|
|
809
|
+
*
|
|
810
|
+
* @param fn The callback receiving the isolated {@link DBC } instance. */
|
|
811
|
+
static isolated(fn: (dbc: DBC) => void): void {
|
|
812
|
+
const saved = DBC.dbcCache.get("WaXCode.DBC");
|
|
813
|
+
const testDbc = new DBC();
|
|
814
|
+
DBC.register(testDbc);
|
|
815
|
+
try {
|
|
816
|
+
fn(testDbc);
|
|
817
|
+
} finally {
|
|
818
|
+
if (saved) {
|
|
819
|
+
DBC.register(saved);
|
|
820
|
+
} else {
|
|
821
|
+
DBC.dbcCache.delete("WaXCode.DBC");
|
|
822
|
+
}
|
|
823
|
+
}
|
|
824
|
+
}
|
|
825
|
+
/**
|
|
826
|
+
* Resolves the desired {@link object } out a given one **toResolveFrom** using the specified **path**.
|
|
827
|
+
*
|
|
828
|
+
* @param toResolveFrom The {@link object } starting to resolve from.
|
|
829
|
+
* @param path The dotted path-{@link string }.
|
|
830
|
+
* This string uses ., [...], and () to represent accessing nested properties,
|
|
831
|
+
* array elements/object keys, and calling methods, respectively, mimicking JavaScript syntax to navigate
|
|
832
|
+
* an object's structure. Code, e.g. something like a.b( 1 as number ).c, will not be executed and
|
|
833
|
+
* thus make the retrieval fail.
|
|
834
|
+
*
|
|
835
|
+
* @returns The requested {@link object }, NULL or UNDEFINED. */
|
|
836
|
+
public static resolve(toResolveFrom: unknown, path: string) {
|
|
837
|
+
if (!toResolveFrom || typeof path !== "string") {
|
|
838
|
+
return undefined;
|
|
839
|
+
}
|
|
840
|
+
// Security: block prototype pollution paths
|
|
841
|
+
const dangerousTokens = ["__proto__", "constructor", "prototype"];
|
|
842
|
+
const cachedParts = DBC.pathTokenCache.get(path);
|
|
843
|
+
const parts =
|
|
844
|
+
cachedParts ?? path.replace(/\[(['"]?)(.*?)\1\]/g, ".$2").split(".");
|
|
845
|
+
if (!cachedParts) {
|
|
846
|
+
// Validate tokens before caching
|
|
847
|
+
for (const part of parts) {
|
|
848
|
+
const tokenName = part.replace(/\(.*\)$/, "");
|
|
849
|
+
if (dangerousTokens.indexOf(tokenName) >= 0) {
|
|
850
|
+
throw new Error(
|
|
851
|
+
`[XDBC] Path "${path}" contains forbidden token "${tokenName}".`,
|
|
852
|
+
);
|
|
853
|
+
}
|
|
854
|
+
}
|
|
855
|
+
DBC.evictIfNeeded(DBC.pathTokenCache);
|
|
856
|
+
DBC.pathTokenCache.set(path, parts);
|
|
857
|
+
}
|
|
858
|
+
let current: any = toResolveFrom;
|
|
859
|
+
for (const part of parts) {
|
|
860
|
+
if (current === null || typeof current === "undefined") {
|
|
861
|
+
return undefined;
|
|
862
|
+
}
|
|
863
|
+
const methodMatch = part.match(/(\w+)\((.*)\)/);
|
|
864
|
+
if (methodMatch) {
|
|
865
|
+
const methodName = methodMatch[1];
|
|
866
|
+
const argsStr = methodMatch[2];
|
|
867
|
+
const args = argsStr.split(",").map((arg) => arg.trim());
|
|
868
|
+
if (typeof current[methodName] === "function") {
|
|
869
|
+
current = current[methodName].apply(current, args);
|
|
870
|
+
} else {
|
|
871
|
+
return undefined;
|
|
872
|
+
}
|
|
873
|
+
} else {
|
|
874
|
+
if (
|
|
875
|
+
typeof window !== "undefined" &&
|
|
876
|
+
typeof HTMLElement !== "undefined" &&
|
|
877
|
+
current instanceof HTMLElement &&
|
|
878
|
+
part.startsWith("@")
|
|
879
|
+
) {
|
|
880
|
+
current = current.getAttribute(part.slice(1));
|
|
881
|
+
} else if (
|
|
882
|
+
typeof current === "object" &&
|
|
883
|
+
current !== null &&
|
|
884
|
+
part in current
|
|
885
|
+
) {
|
|
886
|
+
current = current[part];
|
|
887
|
+
} else if (
|
|
888
|
+
typeof window !== "undefined" &&
|
|
889
|
+
typeof HTMLElement !== "undefined" &&
|
|
890
|
+
current instanceof HTMLElement
|
|
891
|
+
) {
|
|
892
|
+
current = undefined;
|
|
893
|
+
} else {
|
|
894
|
+
current = undefined;
|
|
895
|
+
}
|
|
896
|
+
}
|
|
897
|
+
}
|
|
898
|
+
return current;
|
|
899
|
+
}
|
|
900
|
+
}
|
|
901
|
+
// Register the default instance with standard settings.
|
|
902
|
+
DBC.register(new DBC());
|