@litert/typeguard 1.2.0 → 1.3.0-dev.0

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.
@@ -22,8 +22,7 @@ import * as B from './BuiltInTypes';
22
22
  import { BuiltInTypeCompiler } from './BuiltInTypeCompiler';
23
23
  import { FilterCompiler } from './FilterCompiler';
24
24
 
25
- class Compiler
26
- implements C.ICompiler {
25
+ class Compiler implements C.ICompiler {
27
26
 
28
27
  private _defTypes: Record<string, C.ICompileResult>;
29
28
 
@@ -45,12 +44,43 @@ implements C.ICompiler {
45
44
  return null;
46
45
  }
47
46
 
47
+ private _addTrace(ctx: I.IContext): string {
48
+
49
+ if (!ctx.vTraceName) {
50
+
51
+ return this._lang.literalFalse;
52
+ }
53
+
54
+ return this._lang.addTrace(
55
+ ctx.vTraceName,
56
+ ctx.vTracePrefix,
57
+ ctx.tracePath
58
+ );
59
+ }
60
+
61
+ private _addTraceOr(ctx: I.IContext, expr: string, subPath: string = ''): string {
62
+
63
+ if (!ctx.vTraceName) {
64
+
65
+ return expr;
66
+ }
67
+
68
+ return this._lang.orAddTrace(
69
+ expr,
70
+ ctx.vTraceName,
71
+ ctx.vTracePrefix,
72
+ `${ctx.tracePath}${subPath}`
73
+ );
74
+ }
75
+
48
76
  public compile(options: C.ICompileOptions): C.ICompileResult {
49
77
 
50
78
  const referredTypes: Record<string, true> = {};
51
79
 
52
80
  const ctx: I.IContext = new Context(
53
81
  this._lang.varName('entry'),
82
+ options.traceErrors ? this._lang.varName('failedAsserts') : '',
83
+ options.traceErrors ? this._lang.varName('tracePrefix') : '',
54
84
  this._lang.varName('types'),
55
85
  referredTypes
56
86
  );
@@ -71,12 +101,28 @@ implements C.ICompiler {
71
101
  source: '',
72
102
  arguments: [{
73
103
  'name': ctx.vName,
74
- 'type': 'unknown'
104
+ 'type': 'unknown',
105
+ 'initial': ''
75
106
  }],
76
107
  typeSlotName: ctx.typeSlotName,
77
108
  referredTypes: []
78
109
  };
79
110
 
111
+ if (ctx.vTraceName) {
112
+
113
+ ret.arguments.push({
114
+ 'name': ctx.vTraceName,
115
+ 'type': 'string[]',
116
+ 'initial': '[]'
117
+ });
118
+
119
+ ret.arguments.push({
120
+ 'name': ctx.vTracePrefix,
121
+ 'type': 'string',
122
+ 'initial': this._lang.literal('data')
123
+ });
124
+ }
125
+
80
126
  ret.source = this._compile(
81
127
  ctx,
82
128
  options.rule
@@ -200,7 +246,7 @@ implements C.ICompiler {
200
246
  let regResult: RegExpMatchArray | null = /\[\s*(\d*|\d+\s*,\s*\d*)\s*\]$/.exec(rule);
201
247
 
202
248
  /**
203
- * For rules like `xxx[123]` or `xxx[1,5]`.
249
+ * For rules like `xxx[123]` or `xxx[1,5]` or `xxx[1,]`.
204
250
  */
205
251
  if (regResult) {
206
252
 
@@ -210,43 +256,55 @@ implements C.ICompiler {
210
256
 
211
257
  if (range.length === 1) {
212
258
 
259
+ /**
260
+ * For rules like `xxx[123]`.
261
+ */
213
262
  return this._compileModifiedRule(ctx, [
214
263
  M.ARRAY,
215
264
  range[0],
216
- rule.substr(0, regResult.index)
265
+ rule.slice(0, regResult.index)
217
266
  ]);
218
267
  }
219
268
  else if (Number.isNaN(range[1])) {
220
269
 
270
+ /**
271
+ * For rules like `xxx[1,]`.
272
+ */
221
273
  return this._compileModifiedRule(ctx, [
222
274
  M.ARRAY,
223
275
  [range[0]],
224
- rule.substr(0, regResult.index)
276
+ rule.slice(0, regResult.index)
225
277
  ]);
226
278
  }
227
279
  else {
228
280
 
281
+ /**
282
+ * For rules like `xxx[1,5]`.
283
+ */
229
284
  return this._compileModifiedRule(ctx, [
230
285
  M.ARRAY,
231
286
  range,
232
- rule.substr(0, regResult.index)
287
+ rule.slice(0, regResult.index)
233
288
  ]);
234
289
  }
235
290
  }
236
291
  else {
237
292
 
293
+ /**
294
+ * For rules like `xxx[]`.
295
+ */
238
296
  return this._compileModifiedRule(ctx, [
239
297
  M.LIST,
240
- rule.substr(0, regResult.index)
298
+ rule.slice(0, regResult.index)
241
299
  ]);
242
300
  }
243
301
  }
244
302
 
245
- /**
246
- * For rules like `xxx{}`.
247
- */
248
303
  if (rule.endsWith(I.MAP_SUFFIX)) {
249
304
 
305
+ /**
306
+ * For rules like `xxx{}`.
307
+ */
250
308
  return this._lang.and([
251
309
  this._compileModifiedRule(ctx, [
252
310
  M.MAP,
@@ -327,7 +385,7 @@ implements C.ICompiler {
327
385
 
328
386
  const offset = assertRule ? assertRule[1].length + 2 : 2;
329
387
 
330
- switch ((assertRule?.[1]) ?? rule.substr(0, 2)) {
388
+ switch ((assertRule?.[1]) ?? rule.slice(0, 2)) {
331
389
  case '==':
332
390
  case 'equal':
333
391
 
@@ -600,7 +658,7 @@ implements C.ICompiler {
600
658
  );
601
659
  }
602
660
 
603
- private _compileModifierLIST(ctx: I.IContext, rules: any[]): string {
661
+ private _compileModifierLIST(ctx: I.IContext, rules: any[], traceOffset: number = 0): string {
604
662
 
605
663
  const result: string[] = [];
606
664
 
@@ -618,6 +676,10 @@ implements C.ICompiler {
618
676
  const CLOSURE_PARAM = this._lang.varName(ctx.vCursor++);
619
677
 
620
678
  ctx.vName = this._lang.varName(ctx.vCursor++);
679
+ const vIter = this._lang.varName(ctx.vCursor++);
680
+ ctx.tracePath = `${ctx.tracePath}[${this._lang.numberTemplateVar(
681
+ traceOffset ? this._lang.add(traceOffset, vIter) : vIter
682
+ )}]`;
621
683
 
622
684
  if (rules[0] !== B.ANY) {
623
685
 
@@ -626,9 +688,9 @@ implements C.ICompiler {
626
688
  [CLOSURE_ARG],
627
689
  this._lang.series([
628
690
  this._lang.forEach(
629
- CLOSURE_PARAM, ctx.vName, this._lang.ifThen(
691
+ CLOSURE_PARAM, vIter, ctx.vName, this._lang.ifThen(
630
692
  this._lang.not(this._compile(ctx, rules)),
631
- this._lang.returnValue(this._lang.literal(false))
693
+ this._lang.returnValue(this._addTrace(ctx))
632
694
  )
633
695
  ),
634
696
  this._lang.returnValue(this._lang.literal(true))
@@ -641,7 +703,7 @@ implements C.ICompiler {
641
703
  return this._lang.and(result);
642
704
  }
643
705
 
644
- private _compileModifierARRAY(ctx: I.IContext, rules: any[]): string {
706
+ private _compileModifierARRAY(ctx: I.IContext, rules: any[], traceOffset: number = 0): string {
645
707
 
646
708
  let a: number = 0;
647
709
  let b: number = -1;
@@ -721,6 +783,10 @@ implements C.ICompiler {
721
783
  const CLOSURE_PARAM = this._lang.varName(ctx.vCursor++);
722
784
 
723
785
  ctx.vName = this._lang.varName(ctx.vCursor++);
786
+ const vIter = this._lang.varName(ctx.vCursor++);
787
+ ctx.tracePath = `${ctx.tracePath}[${this._lang.numberTemplateVar(
788
+ traceOffset ? this._lang.add(traceOffset, vIter) : vIter
789
+ )}]`;
724
790
 
725
791
  switch (b) {
726
792
  case -1: {
@@ -754,9 +820,9 @@ implements C.ICompiler {
754
820
  [CLOSURE_ARG],
755
821
  this._lang.series([
756
822
  this._lang.forEach(
757
- CLOSURE_PARAM, ctx.vName, this._lang.ifThen(
823
+ CLOSURE_PARAM, vIter, ctx.vName, this._lang.ifThen(
758
824
  this._lang.not(this._compile(ctx, rules.slice(1))),
759
- this._lang.returnValue(this._lang.literal(false))
825
+ this._lang.returnValue(this._addTrace(ctx))
760
826
  )
761
827
  ),
762
828
  this._lang.returnValue(this._lang.literal(true))
@@ -786,6 +852,7 @@ implements C.ICompiler {
786
852
 
787
853
  const types = rules.slice();
788
854
  let tupleLength = 0;
855
+ let tupleLengthMin = 0;
789
856
 
790
857
  while (1) {
791
858
 
@@ -822,10 +889,11 @@ implements C.ICompiler {
822
889
  if (type !== 'any') {
823
890
 
824
891
  result.push(this._compileModifierLIST(
825
- ctx, type
892
+ ctx, type, i
826
893
  ));
827
894
  }
828
895
 
896
+ tupleLengthMin = tupleLength;
829
897
  tupleLength = -1;
830
898
  }
831
899
  else if (!/^\d+$/.test(dots.slice(3))) {
@@ -836,22 +904,27 @@ implements C.ICompiler {
836
904
 
837
905
  const length = parseInt(dots.slice(3));
838
906
 
839
- if (length <= 3) {
907
+ if (length === 0) {
840
908
 
841
- const vName = ctx.vName;
909
+ throw new TypeError(`Invalid syntax for tuple: ${dots}`);
910
+ }
911
+ else if (length === 1) {
842
912
 
843
- for (let j = 0; j < length; j++) {
913
+ const vName = ctx.vName;
844
914
 
845
- ctx.vName = this._lang.arrayIndex(vName, i++);
846
- result.push(this._compile(ctx, type));
847
- }
915
+ ctx.tracePath = `${ctx.tracePath}[${i}]`;
916
+ ctx.vName = this._lang.arrayIndex(vName, i++);
917
+ result.push(this._addTraceOr(
918
+ ctx,
919
+ this._compile(ctx, type),
920
+ ));
848
921
  }
849
922
  else {
850
923
 
851
924
  ctx.vName = this._lang.arraySlice(ctx.vName, i, i + length);
852
925
 
853
926
  result.push(this._compileModifierARRAY(
854
- ctx, [length, type]
927
+ ctx, [length, type], i
855
928
  ));
856
929
 
857
930
  i += length;
@@ -864,8 +937,12 @@ implements C.ICompiler {
864
937
 
865
938
  ctx.trap(true);
866
939
 
940
+ ctx.tracePath = `${ctx.tracePath}[${i}]`;
867
941
  ctx.vName = this._lang.arrayIndex(ctx.vName, i++);
868
- result.push(this._compile(ctx, type));
942
+ result.push(this._addTraceOr(
943
+ ctx,
944
+ this._compile(ctx, type),
945
+ ));
869
946
  tupleLength++;
870
947
  }
871
948
 
@@ -874,9 +951,24 @@ implements C.ICompiler {
874
951
 
875
952
  if (tupleLength >= 0) {
876
953
 
877
- result.splice(1, -1, this._lang.eq(
878
- this._lang.arrayLength(ctx.vName),
879
- tupleLength
954
+ result.splice(1, -1, this._addTraceOr(
955
+ ctx,
956
+ this._lang.eq(
957
+ this._lang.arrayLength(ctx.vName),
958
+ tupleLength
959
+ ),
960
+ '.length'
961
+ ));
962
+ }
963
+ else if (tupleLengthMin >= 0) {
964
+
965
+ result.splice(1, -1, this._addTraceOr(
966
+ ctx,
967
+ this._lang.gte(
968
+ this._lang.arrayLength(ctx.vName),
969
+ tupleLengthMin
970
+ ),
971
+ '.length'
880
972
  ));
881
973
  }
882
974
 
@@ -903,7 +995,8 @@ implements C.ICompiler {
903
995
  }
904
996
 
905
997
  this._defTypes[rules[0]] = this.compile({
906
- rule: rules.slice(1)
998
+ rule: rules.slice(1),
999
+ traceErrors: !!ctx.vTraceName
907
1000
  });
908
1001
 
909
1002
  return this._usePredefinedType(ctx, rules[0]);
@@ -954,24 +1047,25 @@ implements C.ICompiler {
954
1047
 
955
1048
  ctx.trap(true);
956
1049
 
957
- const CLOSURE_ARG = ctx.vName;
1050
+ const vCArg = ctx.vName;
958
1051
 
959
- const CLOSURE_PARAM = this._lang.varName(ctx.vCursor++);
1052
+ const vCParam = this._lang.varName(ctx.vCursor++);
960
1053
 
961
- const FOR_IN_KEY = this._lang.varName(ctx.vCursor++);
1054
+ const vKey = this._lang.varName(ctx.vCursor++);
962
1055
 
963
1056
  ctx.vName = this._lang.varName(ctx.vCursor++);
1057
+ ctx.tracePath = `${ctx.tracePath}[${this._lang.stringTemplateVar(vKey)}]`;
964
1058
 
965
1059
  const result = this._lang.and([
966
- this._lang.isStrucutre(CLOSURE_ARG, true),
1060
+ this._lang.isStrucutre(vCArg, true),
967
1061
  this._lang.closure(
968
- [CLOSURE_PARAM],
969
- [CLOSURE_ARG],
1062
+ [vCParam],
1063
+ [vCArg],
970
1064
  this._lang.series([
971
1065
  this._lang.forIn(
972
- CLOSURE_PARAM, FOR_IN_KEY, ctx.vName, this._lang.ifThen(
1066
+ vCParam, vKey, ctx.vName, this._lang.ifThen(
973
1067
  this._lang.not(this._compile(ctx, rules)),
974
- this._lang.returnValue(this._lang.literal(false))
1068
+ this._lang.returnValue(this._addTrace(ctx))
975
1069
  )
976
1070
  ),
977
1071
  this._lang.returnValue(this._lang.literal(true))
@@ -1023,7 +1117,11 @@ implements C.ICompiler {
1023
1117
  const strict = !!ctx.flags[I.EFlags.STRICT];
1024
1118
 
1025
1119
  const result: string[] = [
1026
- this._lang.isStrucutre(ctx.vName, true)
1120
+ this._addTraceOr(
1121
+ ctx,
1122
+ this._lang.isStrucutre(ctx.vName, true),
1123
+ '!object'
1124
+ )
1027
1125
  ];
1028
1126
 
1029
1127
  const keys: string[] = [];
@@ -1107,8 +1205,12 @@ implements C.ICompiler {
1107
1205
  keys.push(k);
1108
1206
 
1109
1207
  ctx.vName = this._lang.fieldIndex(ctx.vName, this._lang.literal(k));
1208
+ ctx.tracePath = `${ctx.tracePath}[${this._lang.literal(k)}]`;
1110
1209
 
1111
- result.push(this._compile(ctx, rule));
1210
+ result.push(this._addTraceOr(
1211
+ ctx,
1212
+ this._compile(ctx, rule)
1213
+ ));
1112
1214
 
1113
1215
  ctx.untrap();
1114
1216
  }
@@ -18,18 +18,18 @@ import * as i from './Internal';
18
18
 
19
19
  export class Context implements i.IContext {
20
20
 
21
- public trace: boolean;
22
-
23
- public tracePoint: number;
24
-
25
21
  public vCursor: number;
26
22
 
27
23
  public stack: i.IContextData[];
28
24
 
29
25
  public flags: Record<string, i.EFlagValue>;
30
26
 
27
+ public tracePath: string = '';
28
+
31
29
  public constructor(
32
30
  public vName: string,
31
+ public vTraceName: string,
32
+ public vTracePrefix: string,
33
33
  public readonly typeSlotName: string,
34
34
  public readonly referredTypes: Record<string, boolean>
35
35
  ) {
@@ -38,17 +38,14 @@ export class Context implements i.IContext {
38
38
  this.vCursor = 0;
39
39
 
40
40
  this.flags = {};
41
-
42
- this.tracePoint = 0;
43
-
44
- this.trace = false;
45
41
  }
46
42
 
47
43
  public trap(subjChanged: boolean = false): void {
48
44
 
49
45
  this.stack.push({
50
46
  vName: this.vName,
51
- flags: this.flags
47
+ flags: this.flags,
48
+ tracePath: this.tracePath,
52
49
  });
53
50
 
54
51
  const prevFlags = this.flags;
@@ -83,7 +80,8 @@ export class Context implements i.IContext {
83
80
  throw new Error('Failed to pop stack.');
84
81
  }
85
82
 
86
- this.vName = prev.vName;
87
83
  this.flags = prev.flags;
84
+ this.vName = prev.vName;
85
+ this.tracePath = prev.tracePath;
88
86
  }
89
87
  }
@@ -165,7 +165,12 @@ implements IInlineCompiler {
165
165
 
166
166
  return (new Function(
167
167
  info.typeSlotName,
168
- `return function(${info.arguments[0].name}) {${soe}
168
+ `return function(${
169
+ info.arguments
170
+ .map((a) => a.initial ? `${a.name} = ${a.initial}` : a.name)
171
+ .join(',')
172
+ }) {
173
+ ${soe}
169
174
 
170
175
  return ${info.source};
171
176
  };`
@@ -72,16 +72,18 @@ export enum EFlagValue {
72
72
 
73
73
  export interface IContextData {
74
74
 
75
+ flags: Record<string, EFlagValue>;
76
+
75
77
  vName: string;
76
78
 
77
- flags: Record<string, EFlagValue>;
79
+ tracePath: string;
78
80
  }
79
81
 
80
82
  export interface IContext extends IContextData {
81
83
 
82
- trace: boolean;
84
+ vTraceName: string;
83
85
 
84
- tracePoint: number;
86
+ vTracePrefix: string;
85
87
 
86
88
  vCursor: number;
87
89
 
@@ -289,12 +289,14 @@ implements C.ILanguageBuilder {
289
289
  }
290
290
 
291
291
  public forEach(
292
- an: string,
293
- it: string,
292
+ arrVar: string,
293
+ elVar: string,
294
+ iterVar: string,
294
295
  body: string
295
296
  ): string {
296
297
 
297
- return `for (const ${it} of ${an}) {
298
+ return `for (let ${elVar} = 0; ${elVar} < ${arrVar}.length; ${elVar}++) {
299
+ const ${iterVar} = ${arrVar}[${elVar}];
298
300
  ${body}
299
301
  }`;
300
302
  }
@@ -424,6 +426,57 @@ implements C.ILanguageBuilder {
424
426
  args.join(', ')
425
427
  })`;
426
428
  }
429
+
430
+ public escape(str: string): string {
431
+
432
+ return str.replace(/(['"])/g, '\\$1').replace(/\n/g, '\\n').replace(/\r/g, '\\r');
433
+ }
434
+
435
+ public stringTemplateVar(varExpr: string): string {
436
+
437
+ return `"\${${varExpr}.replace(/(['"])/g, '\\\\$1').replace(/\\n/g, '\\\\n').replace(/\\r/g, '\\\\r')}"`;
438
+ }
439
+
440
+ public numberTemplateVar(varExpr: string): string {
441
+
442
+ return `\${${varExpr}}`;
443
+ }
444
+
445
+ public addTrace(
446
+ vTrace: string,
447
+ vTraceStack: string,
448
+ path: string,
449
+ negative: boolean = false
450
+ ): string {
451
+
452
+ if (negative) {
453
+
454
+ return `(${vTrace}.push(\`\${${vTraceStack}}${path}\`), true)`;
455
+ }
456
+
457
+ return `(${vTrace}.push(\`\${${vTraceStack}}${path}\`), false)`;
458
+ }
459
+
460
+ public add(a: string | number, b: string | number): string {
461
+
462
+ return `${a} + ${b}`;
463
+ }
464
+
465
+ public orAddTrace(
466
+ expr: string,
467
+ vTrace: string,
468
+ vTraceStack: string,
469
+ path: string,
470
+ negative: boolean = false
471
+ ): string {
472
+
473
+ if (negative) {
474
+
475
+ return `((${expr}) && (${vTrace}.push(\`\${${vTraceStack}}${path}\`), true))`;
476
+ }
477
+
478
+ return `((${expr}) || (${vTrace}.push(\`\${${vTraceStack}}${path}\`), false))`;
479
+ }
427
480
  }
428
481
 
429
482
  /**
@@ -112,6 +112,17 @@ compiler.addPredefinedType<string>(
112
112
  }
113
113
  );
114
114
 
115
+ const compiler2 = TypeGuard.createInlineCompiler();
116
+
117
+ compiler2.addPredefinedType<string>(
118
+ 'ipv4_address',
119
+ function(i): i is string {
120
+ return typeof i === 'string'
121
+ && /^[0-9]{1,3}(\.[0-9]{1,3}){3}$/.test(i)
122
+ && i.split('.').map(x => parseInt(x, 10)).every(x => x >= 0 && x <= 255);
123
+ }
124
+ );
125
+
115
126
  export function assertItem(input: unknown, expect: boolean | 'throw'): ITestItem {
116
127
 
117
128
  return {
@@ -150,15 +161,28 @@ export function createTestDefinition(suite: ITestSuite) {
150
161
  assert.throws(() => {
151
162
 
152
163
  compiler.compile<any>({
153
- 'rule': section.rule
164
+ 'rule': section.rule,
165
+ traceErrors: true,
166
+ });
167
+ });
168
+
169
+ assert.throws(() => {
170
+
171
+ compiler2.compile<any>({
172
+ 'rule': section.rule,
154
173
  });
155
174
  });
156
175
  });
157
176
  return;
158
177
  }
159
178
 
160
- const check = compiler.compile<any>({
161
- 'rule': section.rule
179
+ const checkWithTrace = compiler.compile<any>({
180
+ 'rule': section.rule,
181
+ traceErrors: true,
182
+ });
183
+
184
+ const checkWithoutTrace = compiler2.compile<any>({
185
+ 'rule': section.rule,
162
186
  });
163
187
 
164
188
  for (const item of section.items.sort((a, b) => a.expect === b.expect ? 0 : (a.expect ? -1 : 1))) {
@@ -170,7 +194,12 @@ export function createTestDefinition(suite: ITestSuite) {
170
194
  }.`, function() {
171
195
 
172
196
  assert.equal(
173
- check(item.inputValue),
197
+ checkWithTrace(item.inputValue),
198
+ item.expect
199
+ );
200
+
201
+ assert.equal(
202
+ checkWithoutTrace(item.inputValue),
174
203
  item.expect
175
204
  );
176
205
  });