@api-client/core 0.17.7 → 0.18.1

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.
Files changed (59) hide show
  1. package/build/src/amf/ApiSchemaGenerator.d.ts +2 -2
  2. package/build/src/amf/ApiSchemaGenerator.d.ts.map +1 -1
  3. package/build/src/amf/ApiSchemaGenerator.js.map +1 -1
  4. package/build/src/amf/ApiSchemaValues.d.ts.map +1 -1
  5. package/build/src/amf/ApiSchemaValues.js +8 -1
  6. package/build/src/amf/ApiSchemaValues.js.map +1 -1
  7. package/build/src/amf/shape/ShapeBase.d.ts +1 -1
  8. package/build/src/amf/shape/ShapeBase.d.ts.map +1 -1
  9. package/build/src/amf/shape/ShapeBase.js.map +1 -1
  10. package/build/src/amf/shape/ShapeJsonSchemaGenerator.d.ts +1 -1
  11. package/build/src/amf/shape/ShapeJsonSchemaGenerator.d.ts.map +1 -1
  12. package/build/src/amf/shape/ShapeJsonSchemaGenerator.js +7 -1
  13. package/build/src/amf/shape/ShapeJsonSchemaGenerator.js.map +1 -1
  14. package/build/src/amf/shape/ShapeXmlSchemaGenerator.d.ts +1 -1
  15. package/build/src/amf/shape/ShapeXmlSchemaGenerator.d.ts.map +1 -1
  16. package/build/src/amf/shape/ShapeXmlSchemaGenerator.js +8 -2
  17. package/build/src/amf/shape/ShapeXmlSchemaGenerator.js.map +1 -1
  18. package/build/src/mocking/RandExp.d.ts +55 -0
  19. package/build/src/mocking/RandExp.d.ts.map +1 -0
  20. package/build/src/mocking/RandExp.js +302 -0
  21. package/build/src/mocking/RandExp.js.map +1 -0
  22. package/build/src/mocking/lib/ret.d.ts +16 -0
  23. package/build/src/mocking/lib/ret.d.ts.map +1 -0
  24. package/build/src/mocking/lib/ret.js +284 -0
  25. package/build/src/mocking/lib/ret.js.map +1 -0
  26. package/build/src/modeling/Bindings.d.ts +0 -4
  27. package/build/src/modeling/Bindings.d.ts.map +1 -1
  28. package/build/src/modeling/Bindings.js.map +1 -1
  29. package/build/src/modeling/DomainEntity.js +3 -3
  30. package/build/src/modeling/DomainEntity.js.map +1 -1
  31. package/build/src/modeling/DomainProperty.d.ts +18 -0
  32. package/build/src/modeling/DomainProperty.d.ts.map +1 -1
  33. package/build/src/modeling/DomainProperty.js +31 -0
  34. package/build/src/modeling/DomainProperty.js.map +1 -1
  35. package/build/src/modeling/amf/ShapeGenerator.js +3 -3
  36. package/build/src/modeling/amf/ShapeGenerator.js.map +1 -1
  37. package/build/src/modeling/types.d.ts +4 -0
  38. package/build/src/modeling/types.d.ts.map +1 -1
  39. package/build/src/modeling/types.js.map +1 -1
  40. package/build/src/models/DataCatalog.d.ts +12 -1
  41. package/build/src/models/DataCatalog.d.ts.map +1 -1
  42. package/build/src/models/DataCatalog.js.map +1 -1
  43. package/build/tsconfig.tsbuildinfo +1 -1
  44. package/package.json +1 -1
  45. package/src/amf/ApiSchemaGenerator.ts +2 -2
  46. package/src/amf/ApiSchemaValues.ts +8 -1
  47. package/src/amf/shape/ShapeBase.ts +1 -1
  48. package/src/amf/shape/ShapeJsonSchemaGenerator.ts +7 -2
  49. package/src/amf/shape/ShapeXmlSchemaGenerator.ts +8 -3
  50. package/src/mocking/RandExp.ts +335 -0
  51. package/src/mocking/lib/ret.ts +279 -0
  52. package/src/modeling/Bindings.ts +0 -4
  53. package/src/modeling/DomainEntity.ts +3 -3
  54. package/src/modeling/DomainProperty.ts +33 -0
  55. package/src/modeling/amf/ShapeGenerator.ts +3 -3
  56. package/src/modeling/types.ts +4 -0
  57. package/src/models/DataCatalog.ts +13 -1
  58. package/tests/unit/modeling/amf/shape_generator.spec.ts +3 -8
  59. package/tests/unit/modeling/domain_property.spec.ts +335 -0
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@api-client/core",
3
3
  "description": "The API Client's core client library. Works in NodeJS and in a ES enabled browser.",
4
- "version": "0.17.7",
4
+ "version": "0.18.1",
5
5
  "license": "Apache-2.0",
6
6
  "exports": {
7
7
  "./browser.js": {
@@ -1,5 +1,5 @@
1
1
  import { AmfNamespace as ns } from './definitions/Namespace.js'
2
- import { IApiAnyShape, IApiDataExample, IShapeUnion } from './definitions/Shapes.js'
2
+ import { IApiAnyShape, IApiDataExample, IApiPropertyShape, IShapeUnion } from './definitions/Shapes.js'
3
3
  import { ShapeBase, IShapeRenderOptions } from './shape/ShapeBase.js'
4
4
  import { ShapeJsonSchemaGenerator } from './shape/ShapeJsonSchemaGenerator.js'
5
5
  import { ShapeXmlSchemaGenerator } from './shape/ShapeXmlSchemaGenerator.js'
@@ -69,7 +69,7 @@ export class ApiSchemaGenerator {
69
69
  *
70
70
  * @param shape The Shape definition
71
71
  */
72
- generate(shape: IShapeUnion): string | number | boolean | null | undefined {
72
+ generate(shape: IShapeUnion | IApiPropertyShape<IShapeUnion>): string | number | boolean | null | undefined {
73
73
  const { generator } = this
74
74
  if (!generator) {
75
75
  return undefined
@@ -16,6 +16,7 @@ import {
16
16
  IShapeUnion,
17
17
  } from './definitions/Shapes.js'
18
18
  import type { ILoremWordInit, ITypeHashInit, ITypeNumberInit } from '@pawel-up/data-mock/types.js'
19
+ import { RandExp } from '../mocking/RandExp.js'
19
20
 
20
21
  export interface IApiSchemaReadOptions {
21
22
  /**
@@ -275,7 +276,13 @@ export class ApiSchemaValues {
275
276
  }
276
277
 
277
278
  static generateStringValue(schema: IApiScalarShape): string {
278
- const { minLength, maxLength, name = '', format } = schema
279
+ const { minLength, maxLength, name = '', format, pattern } = schema
280
+ if (pattern) {
281
+ // if the pattern is set we generate a random string that matches the pattern.
282
+ // Note, this is not a full regex support, just a simple one.
283
+ const randExp = new RandExp(pattern, undefined, { max: 10 })
284
+ return randExp.gen()
285
+ }
279
286
  const lowerName = name.toLowerCase()
280
287
  // we employ some heuristics to generate content based on the property name.
281
288
  if (lowerName === 'description') {
@@ -138,7 +138,7 @@ export abstract class ShapeBase {
138
138
  * @param schema The Shape definition
139
139
  * @returns The generated example
140
140
  */
141
- abstract generate(schema: IShapeUnion): string | number | boolean | null | undefined
141
+ abstract generate(schema: IShapeUnion | IApiPropertyShape<IShapeUnion>): string | number | boolean | null | undefined
142
142
 
143
143
  /**
144
144
  * Serializes generated values into the final mime type related form.
@@ -20,8 +20,13 @@ export class ShapeJsonSchemaGenerator extends ShapeBase {
20
20
  *
21
21
  * @param schema The Shape definition
22
22
  */
23
- generate(schema: IShapeUnion): string | number | boolean | null | undefined {
24
- const result = this.toObject(schema)
23
+ generate(schema: IShapeUnion | IApiPropertyShape<IShapeUnion>): string | number | boolean | null | undefined {
24
+ let result: string | number | boolean | object | object[] | null | undefined
25
+ if (schema.types.includes(ns.w3.shacl.PropertyShape)) {
26
+ result = this._propertyShapeObject(schema as IApiPropertyShape<IShapeUnion>)
27
+ } else {
28
+ result = this.toObject(schema as IShapeUnion)
29
+ }
25
30
  if (result !== null && typeof result === 'object') {
26
31
  return this.serialize(result)
27
32
  }
@@ -55,9 +55,14 @@ export class ShapeXmlSchemaGenerator extends ShapeBase {
55
55
  *
56
56
  * @param schema The Shape definition
57
57
  */
58
- generate(schema: IShapeUnion): string | undefined {
59
- const value = this.processNode(schema, {}, true)
60
- return value
58
+ generate(schema: IShapeUnion | IApiPropertyShape<IShapeUnion>): string | undefined {
59
+ let result: string | undefined
60
+ if (schema.types.includes(ns.w3.shacl.PropertyShape)) {
61
+ result = this._propertyShapeObject(schema as IApiPropertyShape<IShapeUnion>)
62
+ } else {
63
+ result = this.processNode(schema as IShapeUnion, {}, true)
64
+ }
65
+ return result
61
66
  }
62
67
 
63
68
  /**
@@ -0,0 +1,335 @@
1
+ /* eslint-disable @typescript-eslint/no-explicit-any */
2
+ /**
3
+ * randexp v0.4.6
4
+ * Create random strings that match a given regular expression.
5
+ *
6
+ * Copyright (C) 2017 by fent (https://github.com/fent)
7
+ * MIT License
8
+ */
9
+
10
+ import { ret, Token, types } from './lib/ret.js'
11
+
12
+ class DRange {
13
+ low: number
14
+ high: number
15
+ length: number
16
+
17
+ constructor(low: number, high: number) {
18
+ this.low = low
19
+ this.high = high
20
+ this.length = 1 + high - low
21
+ }
22
+
23
+ overlaps(range: DRange): boolean {
24
+ return !(this.high < range.low || this.low > range.high)
25
+ }
26
+
27
+ touches(range: DRange): boolean {
28
+ return !(this.high + 1 < range.low || this.low - 1 > range.high)
29
+ }
30
+
31
+ add(range: DRange): DRange | null {
32
+ if (this.touches(range)) {
33
+ return new DRange(Math.min(this.low, range.low), Math.max(this.high, range.high))
34
+ }
35
+ return null
36
+ }
37
+
38
+ subtract(range: DRange): DRange[] {
39
+ if (!this.overlaps(range)) {
40
+ return [this.clone()]
41
+ }
42
+ if (range.low <= this.low && range.high >= this.high) {
43
+ return []
44
+ }
45
+ if (range.low > this.low && range.high < this.high) {
46
+ return [new DRange(this.low, range.low - 1), new DRange(range.high + 1, this.high)]
47
+ }
48
+ if (range.low <= this.low) {
49
+ return [new DRange(range.high + 1, this.high)]
50
+ }
51
+ // if (range.high >= this.high)
52
+ return [new DRange(this.low, range.low - 1)]
53
+ }
54
+
55
+ toString(): string {
56
+ return this.low === this.high ? String(this.low) : `${this.low}-${this.high}`
57
+ }
58
+
59
+ clone(): DRange {
60
+ return new DRange(this.low, this.high)
61
+ }
62
+ }
63
+
64
+ class DiscontinuousRange {
65
+ ranges: DRange[] = []
66
+ length = 0
67
+
68
+ constructor(low?: number | DiscontinuousRange, high?: number) {
69
+ if (low !== undefined) {
70
+ this.add(low, high)
71
+ }
72
+ }
73
+
74
+ private _setLength(): void {
75
+ this.length = this.ranges.reduce((sum, range) => sum + range.length, 0)
76
+ }
77
+
78
+ add(low: number | DiscontinuousRange | DRange, high?: number): this {
79
+ const addRange = (range: DRange) => {
80
+ const newRanges: DRange[] = []
81
+ let i = 0
82
+ while (i < this.ranges.length && !range.touches(this.ranges[i])) {
83
+ newRanges.push(this.ranges[i].clone())
84
+ i++
85
+ }
86
+ while (i < this.ranges.length && range.touches(this.ranges[i])) {
87
+ range = range.add(this.ranges[i]) as DRange
88
+ i++
89
+ }
90
+ newRanges.push(range)
91
+ while (i < this.ranges.length) {
92
+ newRanges.push(this.ranges[i].clone())
93
+ i++
94
+ }
95
+ this.ranges = newRanges
96
+ this._setLength()
97
+ }
98
+
99
+ if (low instanceof DiscontinuousRange) {
100
+ low.ranges.forEach(addRange)
101
+ } else if (low instanceof DRange) {
102
+ addRange(low)
103
+ } else {
104
+ if (high === undefined) high = low
105
+ addRange(new DRange(low, high))
106
+ }
107
+ return this
108
+ }
109
+
110
+ subtract(low: number | DiscontinuousRange | DRange, high?: number): this {
111
+ const subtractRange = (range: DRange) => {
112
+ const newRanges: DRange[] = []
113
+ let i = 0
114
+ while (i < this.ranges.length && !range.overlaps(this.ranges[i])) {
115
+ newRanges.push(this.ranges[i].clone())
116
+ i++
117
+ }
118
+ while (i < this.ranges.length && range.overlaps(this.ranges[i])) {
119
+ newRanges.push(...this.ranges[i].subtract(range))
120
+ i++
121
+ }
122
+ while (i < this.ranges.length) {
123
+ newRanges.push(this.ranges[i].clone())
124
+ i++
125
+ }
126
+ this.ranges = newRanges
127
+ this._setLength()
128
+ }
129
+
130
+ if (low instanceof DiscontinuousRange) {
131
+ low.ranges.forEach(subtractRange)
132
+ } else if (low instanceof DRange) {
133
+ subtractRange(low)
134
+ } else {
135
+ if (high === undefined) high = low
136
+ subtractRange(new DRange(low, high))
137
+ }
138
+ return this
139
+ }
140
+
141
+ index(i: number): number | null {
142
+ let j = 0
143
+ while (j < this.ranges.length && this.ranges[j].length <= i) {
144
+ i -= this.ranges[j].length
145
+ j++
146
+ }
147
+ if (j >= this.ranges.length) {
148
+ return null
149
+ }
150
+ return this.ranges[j].low + i
151
+ }
152
+
153
+ toString(): string {
154
+ return `[ ${this.ranges.join(', ')} ]`
155
+ }
156
+
157
+ clone(): DiscontinuousRange {
158
+ return new DiscontinuousRange(this)
159
+ }
160
+ }
161
+
162
+ export interface RandExpOptions {
163
+ max?: number
164
+ defaultRange?: DiscontinuousRange
165
+ randInt?: (min: number, max: number) => number
166
+ }
167
+
168
+ export class RandExp {
169
+ max = 10 // Reduced from 100 to 10 for more reasonable string lengths
170
+ defaultRange: DiscontinuousRange = new DiscontinuousRange(32, 126)
171
+ randInt: (min: number, max: number) => number = (min, max) => {
172
+ return min + Math.floor(Math.random() * (max - min + 1))
173
+ }
174
+
175
+ private tokens: Token
176
+ private ignoreCase: boolean
177
+ private multiline: boolean
178
+
179
+ constructor(regexp: RegExp | string, flags?: string, options?: RandExpOptions) {
180
+ this.defaultRange = this.defaultRange.clone()
181
+
182
+ // Apply options if provided
183
+ if (options) {
184
+ if (typeof options.max === 'number') {
185
+ this.max = options.max
186
+ }
187
+ if (options.defaultRange instanceof DiscontinuousRange) {
188
+ this.defaultRange = options.defaultRange
189
+ }
190
+ if (typeof options.randInt === 'function') {
191
+ this.randInt = options.randInt
192
+ }
193
+ }
194
+
195
+ if (regexp instanceof RegExp) {
196
+ this.ignoreCase = regexp.ignoreCase
197
+ this.multiline = regexp.multiline
198
+ this._setDefaults(regexp)
199
+ regexp = regexp.source
200
+ } else if (typeof regexp === 'string') {
201
+ this.ignoreCase = flags?.includes('i') ?? false
202
+ this.multiline = flags?.includes('m') ?? false
203
+ } else {
204
+ throw new Error('Expected a regexp or string')
205
+ }
206
+ this.tokens = ret(regexp)
207
+ }
208
+
209
+ private _setDefaults(regexp: any): void {
210
+ if (typeof regexp.max === 'number') {
211
+ this.max = regexp.max
212
+ }
213
+ if (regexp.defaultRange instanceof DiscontinuousRange) {
214
+ this.defaultRange = regexp.defaultRange
215
+ }
216
+ if (typeof regexp.randInt === 'function') {
217
+ this.randInt = regexp.randInt
218
+ }
219
+ }
220
+
221
+ gen(): string {
222
+ return this._gen(this.tokens, [])
223
+ }
224
+
225
+ private _gen(token: Token, groups: (string | null)[]): string {
226
+ let stack, str, i, l
227
+
228
+ switch (token.type) {
229
+ case types.ROOT:
230
+ case types.GROUP:
231
+ if (token.followedBy || token.notFollowedBy) return ''
232
+
233
+ if (token.remember && token.groupNumber === undefined) {
234
+ token.groupNumber = groups.push(null) - 1
235
+ }
236
+
237
+ stack = token.options ? this._choice(token.options) : token.stack
238
+ str = ''
239
+ for (i = 0, l = stack.length; i < l; i++) {
240
+ str += this._gen(stack[i], groups)
241
+ }
242
+
243
+ if (token.remember) {
244
+ groups[token.groupNumber] = str
245
+ }
246
+ return str
247
+
248
+ case types.POSITION:
249
+ return ''
250
+
251
+ case types.SET: {
252
+ const set = this._processSet(token)
253
+ if (!set.length) return ''
254
+ return String.fromCharCode(this._choice(set) as number)
255
+ }
256
+ case types.REPETITION: {
257
+ const n = this.randInt(token.min, token.max === Infinity ? token.min + this.max : token.max)
258
+ str = ''
259
+ for (i = 0; i < n; i++) {
260
+ str += this._gen(token.value, groups)
261
+ }
262
+ return str
263
+ }
264
+ case types.REFERENCE:
265
+ return groups[token.value - 1] || ''
266
+
267
+ case types.CHAR: {
268
+ const code = this.ignoreCase && this._coin() ? RandExp._toCaseInverse(token.value) : token.value
269
+ return String.fromCharCode(code)
270
+ }
271
+ }
272
+ return ''
273
+ }
274
+
275
+ private _processSet(token: Token): DiscontinuousRange {
276
+ if (token.type === types.CHAR) {
277
+ return new DiscontinuousRange(token.value)
278
+ }
279
+ if (token.type === types.RANGE) {
280
+ return new DiscontinuousRange(token.from, token.to)
281
+ }
282
+
283
+ const dr = new DiscontinuousRange()
284
+ for (const char of token.set) {
285
+ const sub = this._processSet(char)
286
+ dr.add(sub)
287
+ if (this.ignoreCase) {
288
+ for (let j = 0; j < sub.length; j++) {
289
+ const charCode = sub.index(j) as number
290
+ const otherCase = RandExp._toCaseInverse(charCode)
291
+ if (charCode !== otherCase) {
292
+ dr.add(otherCase)
293
+ }
294
+ }
295
+ }
296
+ }
297
+
298
+ return token.not ? this.defaultRange.clone().subtract(dr) : dr
299
+ }
300
+
301
+ private static _toCaseInverse(code: number): number {
302
+ if (code >= 97 && code <= 122) return code - 32
303
+ if (code >= 65 && code <= 90) return code + 32
304
+ return code
305
+ }
306
+
307
+ private _coin(): boolean {
308
+ return this.randInt(0, 1) === 0
309
+ }
310
+
311
+ private _choice(choices: any[] | DiscontinuousRange): any {
312
+ if (choices instanceof DiscontinuousRange) {
313
+ return choices.index(this.randInt(0, choices.length - 1))
314
+ }
315
+ return choices[this.randInt(0, choices.length - 1)]
316
+ }
317
+
318
+ static randexp(regexp: RegExp | string, flags?: string, options?: RandExpOptions): string {
319
+ let randexpInstance
320
+ if ((regexp as any)._randexp === undefined) {
321
+ randexpInstance = new RandExp(regexp, flags, options)
322
+ ;(regexp as any)._randexp = randexpInstance
323
+ } else {
324
+ randexpInstance = (regexp as any)._randexp
325
+ }
326
+ randexpInstance._setDefaults(regexp)
327
+ return randexpInstance.gen()
328
+ }
329
+
330
+ static sugar(): void {
331
+ ;(RegExp.prototype as any).gen = function () {
332
+ return RandExp.randexp(this)
333
+ }
334
+ }
335
+ }
@@ -0,0 +1,279 @@
1
+ // Regular Expression Tokenizer
2
+ // https://github.com/fent/ret.js
3
+
4
+ export enum types {
5
+ ROOT = 0,
6
+ GROUP = 1,
7
+ POSITION = 2,
8
+ SET = 3,
9
+ RANGE = 4,
10
+ REPETITION = 5,
11
+ REFERENCE = 6,
12
+ CHAR = 7,
13
+ }
14
+
15
+ export interface Token {
16
+ type: types
17
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
18
+ [key: string]: any
19
+ }
20
+ const wordBoundary = (): Token => ({ type: types.POSITION, value: 'b' })
21
+ const nonWordBoundary = (): Token => ({ type: types.POSITION, value: 'B' })
22
+ const begin = (): Token => ({ type: types.POSITION, value: '^' })
23
+ const end = (): Token => ({ type: types.POSITION, value: '$' })
24
+
25
+ const ints = (): Token[] => [{ type: types.RANGE, from: 48, to: 57 }]
26
+ const words = (): Token[] => [
27
+ { type: types.CHAR, value: 95 },
28
+ { type: types.RANGE, from: 97, to: 122 },
29
+ { type: types.RANGE, from: 65, to: 90 },
30
+ ...ints(),
31
+ ]
32
+ const whitespace = (): Token[] => [
33
+ { type: types.CHAR, value: 9 },
34
+ { type: types.CHAR, value: 10 },
35
+ { type: types.CHAR, value: 11 },
36
+ { type: types.CHAR, value: 12 },
37
+ { type: types.CHAR, value: 13 },
38
+ { type: types.CHAR, value: 32 },
39
+ { type: types.CHAR, value: 160 },
40
+ { type: types.CHAR, value: 5760 },
41
+ { type: types.CHAR, value: 6158 },
42
+ { type: types.CHAR, value: 8192 },
43
+ { type: types.CHAR, value: 8193 },
44
+ { type: types.CHAR, value: 8194 },
45
+ { type: types.CHAR, value: 8195 },
46
+ { type: types.CHAR, value: 8196 },
47
+ { type: types.CHAR, value: 8197 },
48
+ { type: types.CHAR, value: 8198 },
49
+ { type: types.CHAR, value: 8199 },
50
+ { type: types.CHAR, value: 8200 },
51
+ { type: types.CHAR, value: 8201 },
52
+ { type: types.CHAR, value: 8202 },
53
+ { type: types.CHAR, value: 8232 },
54
+ { type: types.CHAR, value: 8233 },
55
+ { type: types.CHAR, value: 8239 },
56
+ { type: types.CHAR, value: 8287 },
57
+ { type: types.CHAR, value: 12288 },
58
+ { type: types.CHAR, value: 65279 },
59
+ ]
60
+ const notanychar = (): Token[] => [
61
+ { type: types.CHAR, value: 10 },
62
+ { type: types.CHAR, value: 13 },
63
+ { type: types.CHAR, value: 8232 },
64
+ { type: types.CHAR, value: 8233 },
65
+ ]
66
+
67
+ const words_ = (): Token => ({ type: types.SET, set: words(), not: false })
68
+ const notWords = (): Token => ({ type: types.SET, set: words(), not: true })
69
+ const ints_ = (): Token => ({ type: types.SET, set: ints(), not: false })
70
+ const notInts = (): Token => ({ type: types.SET, set: ints(), not: true })
71
+ const whitespace_ = (): Token => ({ type: types.SET, set: whitespace(), not: false })
72
+ const notWhitespace = (): Token => ({ type: types.SET, set: whitespace(), not: true })
73
+ const anyChar = (): Token => ({ type: types.SET, set: notanychar(), not: true })
74
+
75
+ const specialChars: Record<string, number> = {
76
+ '0': 0,
77
+ 't': 9,
78
+ 'n': 10,
79
+ 'v': 11,
80
+ 'f': 12,
81
+ 'r': 13,
82
+ }
83
+
84
+ const strToChars = (str: string): string => {
85
+ const chars = /(\[\\b\])|(\\)?\\(?:u([A-F0-9]{4})|x([A-F0-9]{2})|(0?[0-7]{2})|c([@A-Z[\\\]^?])|([0tnvfr]))/g
86
+ return str.replace(chars, (match, b, s, uh, xh, o, c, sp) => {
87
+ if (s) return match
88
+ const code = b
89
+ ? 8
90
+ : uh
91
+ ? parseInt(uh, 16)
92
+ : xh
93
+ ? parseInt(xh, 16)
94
+ : o
95
+ ? parseInt(o, 8)
96
+ : c
97
+ ? '@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^ ?'.indexOf(c)
98
+ : specialChars[sp]
99
+ let ch = String.fromCharCode(code)
100
+ if (/[[{}^$.|?*+()]/.test(ch)) {
101
+ ch = `\\${ch}`
102
+ }
103
+ return ch
104
+ })
105
+ }
106
+
107
+ const tokenizeClass = (str: string, regexpStr: string): [Token[], number] => {
108
+ const tokens: Token[] = []
109
+ const classTokens = /\\(?:(w)|(d)|(s)|(W)|(D)|(S))|((?:(?:\\)(.)|([^\]\\]))-(?:\\)?([^\]]))|(\])|(?:\\)?(.)/g
110
+ let match
111
+ let char
112
+ while ((match = classTokens.exec(str)) !== null) {
113
+ if (match[1]) {
114
+ tokens.push(words_())
115
+ } else if (match[2]) {
116
+ tokens.push(ints_())
117
+ } else if (match[3]) {
118
+ tokens.push(whitespace_())
119
+ } else if (match[4]) {
120
+ tokens.push(notWords())
121
+ } else if (match[5]) {
122
+ tokens.push(notInts())
123
+ } else if (match[6]) {
124
+ tokens.push(notWhitespace())
125
+ } else if (match[7]) {
126
+ tokens.push({
127
+ type: types.RANGE,
128
+ from: (match[8] || match[9]).charCodeAt(0),
129
+ to: match[10].charCodeAt(0),
130
+ })
131
+ } else if ((char = match[12])) {
132
+ tokens.push({ type: types.CHAR, value: char.charCodeAt(0) })
133
+ } else {
134
+ return [tokens, classTokens.lastIndex]
135
+ }
136
+ }
137
+ throw new SyntaxError(`Invalid regular expression: /${regexpStr}/: Unterminated character class`)
138
+ }
139
+
140
+ const error = (regexpStr: string, msg: string): never => {
141
+ throw new SyntaxError(`Invalid regular expression: /${regexpStr}/: ${msg}`)
142
+ }
143
+
144
+ export const ret = (regexpStr: string): Token => {
145
+ let i = 0
146
+ const root: Token = { type: types.ROOT, stack: [] }
147
+ let p: Token = root
148
+ let stack: Token[] = root.stack
149
+ const groupStack: Token[] = []
150
+
151
+ const repeatErr = (i: number) => error(regexpStr, `Nothing to repeat at column ${i - 1}`)
152
+ const chars = strToChars(regexpStr)
153
+
154
+ while (i < chars.length) {
155
+ let c = chars[i++]
156
+ switch (c) {
157
+ case '\\':
158
+ c = chars[i++]
159
+ switch (c) {
160
+ case 'b':
161
+ stack.push(wordBoundary())
162
+ break
163
+ case 'B':
164
+ stack.push(nonWordBoundary())
165
+ break
166
+ case 'w':
167
+ stack.push(words_())
168
+ break
169
+ case 'W':
170
+ stack.push(notWords())
171
+ break
172
+ case 'd':
173
+ stack.push(ints_())
174
+ break
175
+ case 'D':
176
+ stack.push(notInts())
177
+ break
178
+ case 's':
179
+ stack.push(whitespace_())
180
+ break
181
+ case 'S':
182
+ stack.push(notWhitespace())
183
+ break
184
+ default:
185
+ if (/\d/.test(c)) {
186
+ stack.push({ type: types.REFERENCE, value: parseInt(c, 10) })
187
+ } else {
188
+ stack.push({ type: types.CHAR, value: c.charCodeAt(0) })
189
+ }
190
+ }
191
+ break
192
+ case '^':
193
+ stack.push(begin())
194
+ break
195
+ case '$':
196
+ stack.push(end())
197
+ break
198
+ case '[': {
199
+ let not = false
200
+ if (chars[i] === '^') {
201
+ not = true
202
+ i++
203
+ }
204
+ const [set, lastIndex] = tokenizeClass(chars.slice(i), regexpStr)
205
+ i += lastIndex
206
+ stack.push({ type: types.SET, set, not })
207
+ break
208
+ }
209
+ case '.':
210
+ stack.push(anyChar())
211
+ break
212
+ case '(': {
213
+ const group: Token = { type: types.GROUP, stack: [], remember: true }
214
+ if (chars[i] === '?') {
215
+ c = chars[i + 1]
216
+ i += 2
217
+ if (c === '=') {
218
+ group.followedBy = true
219
+ } else if (c === '!') {
220
+ group.notFollowedBy = true
221
+ } else if (c !== ':') {
222
+ error(regexpStr, `Invalid group, character '${c}' after '?' at column ${i - 1}`)
223
+ }
224
+ group.remember = false
225
+ }
226
+ stack.push(group)
227
+ groupStack.push(p)
228
+ p = group
229
+ stack = group.stack
230
+ break
231
+ }
232
+ case ')':
233
+ if (groupStack.length === 0) error(regexpStr, `Unmatched ) at column ${i - 1}`)
234
+ p = groupStack.pop() as Token
235
+ stack = p.options ? p.options[p.options.length - 1] : p.stack
236
+ break
237
+ case '|':
238
+ if (!p.options) {
239
+ p.options = [p.stack]
240
+ delete p.stack
241
+ }
242
+ {
243
+ const newStack: Token[] = []
244
+ p.options.push(newStack)
245
+ stack = newStack
246
+ }
247
+ break
248
+ case '{': {
249
+ const repetition = /^(\d+)(,(\d+)?)?\}/.exec(chars.slice(i))
250
+ if (repetition) {
251
+ if (stack.length === 0) repeatErr(i)
252
+ const min = parseInt(repetition[1], 10)
253
+ const max = repetition[2] ? (repetition[3] ? parseInt(repetition[3], 10) : Infinity) : min
254
+ i += repetition[0].length
255
+ stack.push({ type: types.REPETITION, min, max, value: stack.pop() })
256
+ } else {
257
+ stack.push({ type: types.CHAR, value: 123 })
258
+ }
259
+ break
260
+ }
261
+ case '?':
262
+ if (stack.length === 0) repeatErr(i)
263
+ stack.push({ type: types.REPETITION, min: 0, max: 1, value: stack.pop() })
264
+ break
265
+ case '+':
266
+ if (stack.length === 0) repeatErr(i)
267
+ stack.push({ type: types.REPETITION, min: 1, max: Infinity, value: stack.pop() })
268
+ break
269
+ case '*':
270
+ if (stack.length === 0) repeatErr(i)
271
+ stack.push({ type: types.REPETITION, min: 0, max: Infinity, value: stack.pop() })
272
+ break
273
+ default:
274
+ stack.push({ type: types.CHAR, value: c.charCodeAt(0) })
275
+ }
276
+ }
277
+ if (groupStack.length !== 0) error(regexpStr, 'Unterminated group')
278
+ return root
279
+ }