@@ -4,6 +4,72 @@ import { luhnCheckValue } from './luhn-check';
4
4
import type { RecordKey } from './unique' ;
5
5
import * as uniqueExec from './unique' ;
6
6
7
+ /**
8
+ * Returns a number based on given RegEx-based quantifier symbol or quantifier values.
9
+ *
10
+ * @param faker Faker instance
11
+ * @param quantifierSymbol Quantifier symbols can be either of these: `?`, `*`, `+`.
12
+ * @param quantifierMin Quantifier minimum value. If given without a maximum, this will be used as the quantifier value.
13
+ * @param quantifierMax Quantifier maximum value. Will randomly get a value between the minimum and maximum if both are provided.
14
+ *
15
+ * @returns a random number based on the given quantifier parameters.
16
+ *
17
+ * @example
18
+ * getRepetitionsBasedOnQuantifierParameters(this.faker, '*', null, null) // 3
19
+ * getRepetitionsBasedOnQuantifierParameters(this.faker, null, 10, null) // 10
20
+ * getRepetitionsBasedOnQuantifierParameters(this.faker, null, 5, 8) // 6
21
+ *
22
+ * @since 8.0.0
23
+ */
24
+ function getRepetitionsBasedOnQuantifierParameters (
25
+ faker : Faker ,
26
+ quantifierSymbol : string ,
27
+ quantifierMin : string ,
28
+ quantifierMax : string
29
+ ) {
30
+ let repetitions = 1 ;
31
+ if ( quantifierSymbol ) {
32
+ switch ( quantifierSymbol ) {
33
+ case '?' : {
34
+ repetitions = faker . datatype . boolean ( ) ? 0 : 1 ;
35
+ break ;
36
+ }
37
+
38
+ case '*' : {
39
+ let limit = 1 ;
40
+ while ( faker . datatype . boolean ( ) ) {
41
+ limit *= 2 ;
42
+ }
43
+
44
+ repetitions = faker . number . int ( { min : 0 , max : limit } ) ;
45
+ break ;
46
+ }
47
+
48
+ case '+' : {
49
+ let limit = 1 ;
50
+ while ( faker . datatype . boolean ( ) ) {
51
+ limit *= 2 ;
52
+ }
53
+
54
+ repetitions = faker . number . int ( { min : 1 , max : limit } ) ;
55
+ break ;
56
+ }
57
+
58
+ default :
59
+ throw new FakerError ( 'Unknown quantifier symbol provided.' ) ;
60
+ }
61
+ } else if ( quantifierMin != null && quantifierMax != null ) {
62
+ repetitions = faker . number . int ( {
63
+ min : parseInt ( quantifierMin ) ,
64
+ max : parseInt ( quantifierMax ) ,
65
+ } ) ;
66
+ } else if ( quantifierMin != null && quantifierMax == null ) {
67
+ repetitions = parseInt ( quantifierMin ) ;
68
+ }
69
+
70
+ return repetitions ;
71
+ }
72
+
7
73
/**
8
74
* Module with various helper methods providing basic (seed-dependent) operations useful for implementing faker methods.
9
75
*/
@@ -247,6 +313,235 @@ export class HelpersModule {
247
313
return string ;
248
314
}
249
315
316
+ /**
317
+ * Generates a string matching the given regex like expressions.
318
+ *
319
+ * This function doesn't provide full support of actual `RegExp`.
320
+ * Features such as grouping, anchors and character classes are not supported.
321
+ * If you are looking for a library that randomly generates strings based on
322
+ * `RegExp`s, see [randexp.js](https://github.com/fent/randexp.js)
323
+ *
324
+ * Supported patterns:
325
+ * - `x{times}` => Repeat the `x` exactly `times` times.
326
+ * - `x{min,max}` => Repeat the `x` `min` to `max` times.
327
+ * - `[x-y]` => Randomly get a character between `x` and `y` (inclusive).
328
+ * - `[x-y]{times}` => Randomly get a character between `x` and `y` (inclusive) and repeat it `times` times.
329
+ * - `[x-y]{min,max}` => Randomly get a character between `x` and `y` (inclusive) and repeat it `min` to `max` times.
330
+ * - `[^...]` => Randomly get an ASCII number or letter character that is not in the given range. (e.g. `[^0-9]` will get a random non-numeric character).
331
+ * - `[-...]` => Include dashes in the range. Must be placed after the negate character `^` and before any character sets if used (e.g. `[^-0-9]` will not get any numeric characters or dashes).
332
+ * - `/[x-y]/i` => Randomly gets an uppercase or lowercase character between `x` and `y` (inclusive).
333
+ * - `x?` => Randomly decide to include or not include `x`.
334
+ * - `[x-y]?` => Randomly decide to include or not include characters between `x` and `y` (inclusive).
335
+ * - `x*` => Repeat `x` 0 or more times.
336
+ * - `[x-y]*` => Repeat characters between `x` and `y` (inclusive) 0 or more times.
337
+ * - `x+` => Repeat `x` 1 or more times.
338
+ * - `[x-y]+` => Repeat characters between `x` and `y` (inclusive) 1 or more times.
339
+ * - `.` => returns a wildcard ASCII character that can be any number, character or symbol. Can be combined with quantifiers as well.
340
+ *
341
+ * @param pattern The template string/RegExp to to generate a matching string for.
342
+ *
343
+ * @throws If min value is more than max value in quantifier. e.g. `#{10,5}`
344
+ * @throws If invalid quantifier symbol is passed in.
345
+ *
346
+ * @example
347
+ * faker.helpers.fromRegExp('#{5}') // '#####'
348
+ * faker.helpers.fromRegExp('#{2,9}') // '#######'
349
+ * faker.helpers.fromRegExp('[1-7]') // '5'
350
+ * faker.helpers.fromRegExp('#{3}test[1-5]') // '###test3'
351
+ * faker.helpers.fromRegExp('[0-9a-dmno]') // '5'
352
+ * faker.helpers.fromRegExp('[^a-zA-Z0-8]') // '9'
353
+ * faker.helpers.fromRegExp('[a-d0-6]{2,8}') // 'a0dc45b0'
354
+ * faker.helpers.fromRegExp('[-a-z]{5}') // 'a-zab'
355
+ * faker.helpers.fromRegExp(/[A-Z0-9]{4}-[A-Z0-9]{4}/) // 'BS4G-485H'
356
+ * faker.helpers.fromRegExp(/[A-Z]{5}/i) // 'pDKfh'
357
+ * faker.helpers.fromRegExp(/.{5}/) // '14(#B'
358
+ * faker.helpers.fromRegExp(/Joh?n/) // 'Jon'
359
+ * faker.helpers.fromRegExp(/ABC*DE/) // 'ABDE'
360
+ * faker.helpers.fromRegExp(/bee+p/) // 'beeeeeeeep'
361
+ *
362
+ * @since 8.0.0
363
+ */
364
+ fromRegExp ( pattern : string | RegExp ) : string {
365
+ let isCaseInsensitive = false ;
366
+
367
+ if ( pattern instanceof RegExp ) {
368
+ isCaseInsensitive = pattern . flags . includes ( 'i' ) ;
369
+ pattern = pattern . toString ( ) ;
370
+ pattern = pattern . match ( / \/ ( .+ ?) \/ / ) ?. [ 1 ] ?? '' ; // Remove frontslash from front and back of RegExp
371
+ }
372
+
373
+ let min : number ;
374
+ let max : number ;
375
+ let repetitions : number ;
376
+
377
+ // Deal with single wildcards
378
+ const SINGLE_CHAR_REG =
379
+ / ( [ . A - Z a - z 0 - 9 ] ) (?: \{ ( \d + ) (?: \, ( \d + ) | ) \} | ( \? | \* | \+ ) ) (? ! [ ^ [ ] * ] | [ ^ { ] * } ) / ;
380
+ let token = pattern . match ( SINGLE_CHAR_REG ) ;
381
+ while ( token != null ) {
382
+ const quantifierMin : string = token [ 2 ] ;
383
+ const quantifierMax : string = token [ 3 ] ;
384
+ const quantifierSymbol : string = token [ 4 ] ;
385
+
386
+ repetitions = getRepetitionsBasedOnQuantifierParameters (
387
+ this . faker ,
388
+ quantifierSymbol ,
389
+ quantifierMin ,
390
+ quantifierMax
391
+ ) ;
392
+
393
+ pattern =
394
+ pattern . slice ( 0 , token . index ) +
395
+ token [ 1 ] . repeat ( repetitions ) +
396
+ pattern . slice ( token . index + token [ 0 ] . length ) ;
397
+ token = pattern . match ( SINGLE_CHAR_REG ) ;
398
+ }
399
+
400
+ const SINGLE_RANGE_REG = / ( \d - \d | \w - \w | \d | \w | [ - ! @ # $ & ( ) ` . + , / " ] ) / ;
401
+ const RANGE_ALPHANUMEMRIC_REG =
402
+ / \[ ( \^ | ) ( - | ) ( .+ ?) \] (?: \{ ( \d + ) (?: \, ( \d + ) | ) \} | ( \? | \* | \+ ) | ) / ;
403
+ // Deal with character classes with quantifiers `[a-z0-9]{min[, max]}`
404
+ token = pattern . match ( RANGE_ALPHANUMEMRIC_REG ) ;
405
+ while ( token != null ) {
406
+ const isNegated = token [ 1 ] === '^' ;
407
+ const includesDash : boolean = token [ 2 ] === '-' ;
408
+ const quantifierMin : string = token [ 4 ] ;
409
+ const quantifierMax : string = token [ 5 ] ;
410
+ const quantifierSymbol : string = token [ 6 ] ;
411
+
412
+ const rangeCodes : number [ ] = [ ] ;
413
+
414
+ let ranges = token [ 3 ] ;
415
+ let range = ranges . match ( SINGLE_RANGE_REG ) ;
416
+
417
+ if ( includesDash ) {
418
+ // 45 is the ascii code for '-'
419
+ rangeCodes . push ( 45 ) ;
420
+ }
421
+
422
+ while ( range != null ) {
423
+ if ( range [ 0 ] . indexOf ( '-' ) === - 1 ) {
424
+ // handle non-ranges
425
+ if ( isCaseInsensitive && isNaN ( Number ( range [ 0 ] ) ) ) {
426
+ rangeCodes . push ( range [ 0 ] . toUpperCase ( ) . charCodeAt ( 0 ) ) ;
427
+ rangeCodes . push ( range [ 0 ] . toLowerCase ( ) . charCodeAt ( 0 ) ) ;
428
+ } else {
429
+ rangeCodes . push ( range [ 0 ] . charCodeAt ( 0 ) ) ;
430
+ }
431
+ } else {
432
+ // handle ranges
433
+ const rangeMinMax = range [ 0 ] . split ( '-' ) . map ( ( x ) => x . charCodeAt ( 0 ) ) ;
434
+ min = rangeMinMax [ 0 ] ;
435
+ max = rangeMinMax [ 1 ] ;
436
+ // throw error if min larger than max
437
+ if ( min > max ) {
438
+ throw new FakerError ( 'Character range provided is out of order.' ) ;
439
+ }
440
+
441
+ for ( let i = min ; i <= max ; i ++ ) {
442
+ if ( isCaseInsensitive && isNaN ( Number ( String . fromCharCode ( i ) ) ) ) {
443
+ const ch = String . fromCharCode ( i ) ;
444
+ rangeCodes . push ( ch . toUpperCase ( ) . charCodeAt ( 0 ) ) ;
445
+ rangeCodes . push ( ch . toLowerCase ( ) . charCodeAt ( 0 ) ) ;
446
+ } else {
447
+ rangeCodes . push ( i ) ;
448
+ }
449
+ }
450
+ }
451
+
452
+ ranges = ranges . substring ( range [ 0 ] . length ) ;
453
+ range = ranges . match ( SINGLE_RANGE_REG ) ;
454
+ }
455
+
456
+ repetitions = getRepetitionsBasedOnQuantifierParameters (
457
+ this . faker ,
458
+ quantifierSymbol ,
459
+ quantifierMin ,
460
+ quantifierMax
461
+ ) ;
462
+
463
+ if ( isNegated ) {
464
+ let index = - 1 ;
465
+ // 0-9
466
+ for ( let i = 48 ; i <= 57 ; i ++ ) {
467
+ index = rangeCodes . indexOf ( i ) ;
468
+ if ( index > - 1 ) {
469
+ rangeCodes . splice ( index , 1 ) ;
470
+ continue ;
471
+ }
472
+
473
+ rangeCodes . push ( i ) ;
474
+ }
475
+
476
+ // A-Z
477
+ for ( let i = 65 ; i <= 90 ; i ++ ) {
478
+ index = rangeCodes . indexOf ( i ) ;
479
+ if ( index > - 1 ) {
480
+ rangeCodes . splice ( index , 1 ) ;
481
+ continue ;
482
+ }
483
+
484
+ rangeCodes . push ( i ) ;
485
+ }
486
+
487
+ // a-z
488
+ for ( let i = 97 ; i <= 122 ; i ++ ) {
489
+ index = rangeCodes . indexOf ( i ) ;
490
+ if ( index > - 1 ) {
491
+ rangeCodes . splice ( index , 1 ) ;
492
+ continue ;
493
+ }
494
+
495
+ rangeCodes . push ( i ) ;
496
+ }
497
+ }
498
+
499
+ const generatedString = this . multiple (
500
+ ( ) => String . fromCharCode ( this . arrayElement ( rangeCodes ) ) ,
501
+ { count : repetitions }
502
+ ) . join ( '' ) ;
503
+
504
+ pattern =
505
+ pattern . slice ( 0 , token . index ) +
506
+ generatedString +
507
+ pattern . slice ( token . index + token [ 0 ] . length ) ;
508
+ token = pattern . match ( RANGE_ALPHANUMEMRIC_REG ) ;
509
+ }
510
+
511
+ const RANGE_REP_REG = / ( .) \{ ( \d + ) \, ( \d + ) \} / ;
512
+ // Deal with quantifier ranges `{min,max}`
513
+ token = pattern . match ( RANGE_REP_REG ) ;
514
+ while ( token != null ) {
515
+ min = parseInt ( token [ 2 ] ) ;
516
+ max = parseInt ( token [ 3 ] ) ;
517
+ // throw error if min larger than max
518
+ if ( min > max ) {
519
+ throw new FakerError ( 'Numbers out of order in {} quantifier.' ) ;
520
+ }
521
+
522
+ repetitions = this . faker . number . int ( { min, max } ) ;
523
+ pattern =
524
+ pattern . slice ( 0 , token . index ) +
525
+ token [ 1 ] . repeat ( repetitions ) +
526
+ pattern . slice ( token . index + token [ 0 ] . length ) ;
527
+ token = pattern . match ( RANGE_REP_REG ) ;
528
+ }
529
+
530
+ const REP_REG = / ( .) \{ ( \d + ) \} / ;
531
+ // Deal with repeat `{num}`
532
+ token = pattern . match ( REP_REG ) ;
533
+ while ( token != null ) {
534
+ repetitions = parseInt ( token [ 2 ] ) ;
535
+ pattern =
536
+ pattern . slice ( 0 , token . index ) +
537
+ token [ 1 ] . repeat ( repetitions ) +
538
+ pattern . slice ( token . index + token [ 0 ] . length ) ;
539
+ token = pattern . match ( REP_REG ) ;
540
+ }
541
+
542
+ return pattern ;
543
+ }
544
+
250
545
/**
251
546
* Takes an array and randomizes it in place then returns it.
252
547
*
0 commit comments