@sanity/validation 3.14.4 → 6.12.0-next.112

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 (40) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +52 -0
  3. package/lib/_internal.d.ts +96 -0
  4. package/lib/_internal.js +39 -0
  5. package/lib/_internal.js.map +1 -0
  6. package/lib/index.d.ts +2 -0
  7. package/lib/index.js +2 -1306
  8. package/lib/validateDocument-6pLTIHIn.d.ts +277 -0
  9. package/lib/validateDocument-Cq33kUmN.js +1127 -0
  10. package/lib/validateDocument-Cq33kUmN.js.map +1 -0
  11. package/package.json +53 -45
  12. package/lib/dts/src/index.d.ts +0 -50
  13. package/lib/index.cjs.mjs +0 -9
  14. package/lib/index.esm.js +0 -1286
  15. package/lib/index.esm.js.map +0 -1
  16. package/lib/index.js.map +0 -1
  17. package/src/Rule.ts +0 -424
  18. package/src/ValidationError.ts +0 -32
  19. package/src/index.ts +0 -9
  20. package/src/inferFromSchema.ts +0 -19
  21. package/src/inferFromSchemaType.ts +0 -50
  22. package/src/util/convertToValidationMarker.ts +0 -84
  23. package/src/util/deepEquals.ts +0 -77
  24. package/src/util/escapeRegex.ts +0 -5
  25. package/src/util/normalizeValidationRules.test.ts +0 -170
  26. package/src/util/normalizeValidationRules.ts +0 -118
  27. package/src/util/pathToString.ts +0 -21
  28. package/src/util/requestIdleCallback.ts +0 -31
  29. package/src/util/typeString.test.ts +0 -27
  30. package/src/util/typeString.ts +0 -23
  31. package/src/validateDocument.test.ts +0 -703
  32. package/src/validateDocument.ts +0 -240
  33. package/src/validators/arrayValidator.ts +0 -100
  34. package/src/validators/booleanValidator.ts +0 -16
  35. package/src/validators/dateValidator.ts +0 -113
  36. package/src/validators/genericValidator.ts +0 -117
  37. package/src/validators/numberValidator.ts +0 -66
  38. package/src/validators/objectValidator.ts +0 -64
  39. package/src/validators/slugValidator.ts +0 -117
  40. package/src/validators/stringValidator.ts +0 -120
package/src/Rule.ts DELETED
@@ -1,424 +0,0 @@
1
- import {
2
- SchemaType,
3
- Rule as IRule,
4
- RuleClass,
5
- CustomValidator,
6
- RuleSpecConstraint,
7
- FieldRules,
8
- ValidationContext,
9
- RuleSpec,
10
- ValidationMarker,
11
- RuleTypeConstraint,
12
- Validator,
13
- } from '@sanity/types'
14
- import {cloneDeep, get} from 'lodash'
15
- import ValidationErrorClass from './ValidationError'
16
- import escapeRegex from './util/escapeRegex'
17
- import {convertToValidationMarker} from './util/convertToValidationMarker'
18
- import pathToString from './util/pathToString'
19
- import genericValidator from './validators/genericValidator'
20
- import booleanValidator from './validators/booleanValidator'
21
- import numberValidator from './validators/numberValidator'
22
- import stringValidator from './validators/stringValidator'
23
- import arrayValidator from './validators/arrayValidator'
24
- import objectValidator from './validators/objectValidator'
25
- import dateValidator from './validators/dateValidator'
26
-
27
- const typeValidators = {
28
- Boolean: booleanValidator,
29
- Number: numberValidator,
30
- String: stringValidator,
31
- Array: arrayValidator,
32
- Object: objectValidator,
33
- Date: dateValidator,
34
- }
35
-
36
- const getBaseType = (type: SchemaType | undefined): SchemaType | undefined => {
37
- return type && type.type ? getBaseType(type.type) : type
38
- }
39
-
40
- const isFieldRef = (constraint: unknown): constraint is {type: symbol; path: string | string[]} => {
41
- if (typeof constraint !== 'object' || !constraint) return false
42
- return (constraint as Record<string, unknown>).type === Rule.FIELD_REF
43
- }
44
-
45
- const EMPTY_ARRAY: unknown[] = []
46
- const FIELD_REF = Symbol('FIELD_REF')
47
- const ruleConstraintTypes: RuleTypeConstraint[] = [
48
- 'Array',
49
- 'Boolean',
50
- 'Date',
51
- 'Number',
52
- 'Object',
53
- 'String',
54
- ]
55
-
56
- // Note: `RuleClass` and `Rule` are split to fit the current `@sanity/types`
57
- // setup. Classes are a bit weird in the `@sanity/types` package because classes
58
- // create an actual javascript class while simultaneously creating a type
59
- // definition.
60
- //
61
- // This implicitly creates two types:
62
- // 1. the instance type — `Rule` and
63
- // 2. the static/class type - `RuleClass`
64
- //
65
- // The `RuleClass` type contains the static methods and the `Rule` instance
66
- // contains the instance methods.
67
- //
68
- // This package exports the RuleClass as a value without implicitly exporting
69
- // an instance definition. This should help reminder downstream users to import
70
- // from the `@sanity/types` package.
71
- const Rule: RuleClass = class Rule implements IRule {
72
- static readonly FIELD_REF = FIELD_REF
73
- static array = (def?: SchemaType): Rule => new Rule(def).type('Array')
74
- static object = (def?: SchemaType): Rule => new Rule(def).type('Object')
75
- static string = (def?: SchemaType): Rule => new Rule(def).type('String')
76
- static number = (def?: SchemaType): Rule => new Rule(def).type('Number')
77
- static boolean = (def?: SchemaType): Rule => new Rule(def).type('Boolean')
78
- static dateTime = (def?: SchemaType): Rule => new Rule(def).type('Date')
79
- static valueOfField = (path: string | string[]): {type: symbol; path: string | string[]} => ({
80
- type: FIELD_REF,
81
- path,
82
- })
83
-
84
- _type: RuleTypeConstraint | undefined = undefined
85
- _level: 'error' | 'warning' | 'info' | undefined = undefined
86
- _required: 'required' | 'optional' | undefined = undefined
87
- _typeDef: SchemaType | undefined = undefined
88
- _message: string | undefined = undefined
89
- _rules: RuleSpec[] = []
90
- _fieldRules: FieldRules | undefined = undefined
91
-
92
- constructor(typeDef?: SchemaType) {
93
- this._typeDef = typeDef
94
- this.reset()
95
- }
96
-
97
- private _mergeRequired(next: Rule) {
98
- if (this._required === 'required' || next._required === 'required') return 'required'
99
- if (this._required === 'optional' || next._required === 'optional') return 'optional'
100
- return undefined
101
- }
102
-
103
- // Alias to static method, since we often have access to an _instance_ of a rule but not the actual Rule class
104
- valueOfField = Rule.valueOfField.bind(Rule)
105
-
106
- error(message?: string): Rule {
107
- const rule = this.clone()
108
- rule._level = 'error'
109
- rule._message = message || undefined
110
- return rule
111
- }
112
-
113
- warning(message?: string): Rule {
114
- const rule = this.clone()
115
- rule._level = 'warning'
116
- rule._message = message || undefined
117
- return rule
118
- }
119
-
120
- info(message?: string): Rule {
121
- const rule = this.clone()
122
- rule._level = 'info'
123
- rule._message = message || undefined
124
- return rule
125
- }
126
-
127
- reset(): this {
128
- this._type = this._type || undefined
129
- this._rules = (this._rules || []).filter((rule) => rule.flag === 'type')
130
- this._message = undefined
131
- this._required = undefined
132
- this._level = 'error'
133
- this._fieldRules = undefined
134
- return this
135
- }
136
-
137
- isRequired(): boolean {
138
- return this._required === 'required'
139
- }
140
-
141
- clone(): Rule {
142
- const rule = new Rule()
143
- rule._type = this._type
144
- rule._message = this._message
145
- rule._required = this._required
146
- rule._rules = cloneDeep(this._rules)
147
- rule._level = this._level
148
- rule._fieldRules = this._fieldRules
149
- rule._typeDef = this._typeDef
150
- return rule
151
- }
152
-
153
- cloneWithRules(rules: RuleSpec[]): Rule {
154
- const rule = this.clone()
155
- const newRules = new Set()
156
- rules.forEach((curr) => {
157
- if (curr.flag === 'type') {
158
- rule._type = curr.constraint
159
- }
160
-
161
- newRules.add(curr.flag)
162
- })
163
-
164
- rule._rules = rule._rules
165
- .filter((curr) => {
166
- const disallowDuplicate = ['type', 'uri', 'email'].includes(curr.flag)
167
- const isDuplicate = newRules.has(curr.flag)
168
- return !(disallowDuplicate && isDuplicate)
169
- })
170
- .concat(rules)
171
-
172
- return rule
173
- }
174
-
175
- merge(rule: Rule): Rule {
176
- if (this._type && rule._type && this._type !== rule._type) {
177
- throw new Error('merge() failed: conflicting types')
178
- }
179
-
180
- const newRule = this.cloneWithRules(rule._rules)
181
- newRule._type = this._type || rule._type
182
- newRule._message = this._message || rule._message
183
- newRule._required = this._mergeRequired(rule)
184
- newRule._level = this._level === 'error' ? rule._level : this._level
185
- return newRule
186
- }
187
-
188
- // Validation flag setters
189
- type(targetType: RuleTypeConstraint | Lowercase<RuleTypeConstraint>): Rule {
190
- const type = `${targetType.slice(0, 1).toUpperCase()}${targetType.slice(1)}` as Capitalize<
191
- typeof targetType
192
- >
193
-
194
- if (!ruleConstraintTypes.includes(type)) {
195
- throw new Error(`Unknown type "${targetType}"`)
196
- }
197
-
198
- const rule = this.cloneWithRules([{flag: 'type', constraint: type}])
199
- rule._type = type
200
- return rule
201
- }
202
-
203
- all(children: Rule[]): Rule {
204
- return this.cloneWithRules([{flag: 'all', constraint: children}])
205
- }
206
-
207
- either(children: Rule[]): Rule {
208
- return this.cloneWithRules([{flag: 'either', constraint: children}])
209
- }
210
-
211
- // Shared rules
212
- optional(): Rule {
213
- const rule = this.cloneWithRules([{flag: 'presence', constraint: 'optional'}])
214
- rule._required = 'optional'
215
- return rule
216
- }
217
-
218
- required(): Rule {
219
- const rule = this.cloneWithRules([{flag: 'presence', constraint: 'required'}])
220
- rule._required = 'required'
221
- return rule
222
- }
223
-
224
- custom<T = unknown>(fn: CustomValidator<T>): Rule {
225
- return this.cloneWithRules([{flag: 'custom', constraint: fn as CustomValidator}])
226
- }
227
-
228
- min(len: number): Rule {
229
- return this.cloneWithRules([{flag: 'min', constraint: len}])
230
- }
231
-
232
- max(len: number): Rule {
233
- return this.cloneWithRules([{flag: 'max', constraint: len}])
234
- }
235
-
236
- length(len: number): Rule {
237
- return this.cloneWithRules([{flag: 'length', constraint: len}])
238
- }
239
-
240
- valid(value: unknown | unknown[]): Rule {
241
- const values = Array.isArray(value) ? value : [value]
242
- return this.cloneWithRules([{flag: 'valid', constraint: values}])
243
- }
244
-
245
- // Numbers only
246
- integer(): Rule {
247
- return this.cloneWithRules([{flag: 'integer'}])
248
- }
249
-
250
- precision(limit: number): Rule {
251
- return this.cloneWithRules([{flag: 'precision', constraint: limit}])
252
- }
253
-
254
- positive(): Rule {
255
- return this.cloneWithRules([{flag: 'min', constraint: 0}])
256
- }
257
-
258
- negative(): Rule {
259
- return this.cloneWithRules([{flag: 'lessThan', constraint: 0}])
260
- }
261
-
262
- greaterThan(num: number): Rule {
263
- return this.cloneWithRules([{flag: 'greaterThan', constraint: num}])
264
- }
265
-
266
- lessThan(num: number): Rule {
267
- return this.cloneWithRules([{flag: 'lessThan', constraint: num}])
268
- }
269
-
270
- // String only
271
- uppercase(): Rule {
272
- return this.cloneWithRules([{flag: 'stringCasing', constraint: 'uppercase'}])
273
- }
274
-
275
- lowercase(): Rule {
276
- return this.cloneWithRules([{flag: 'stringCasing', constraint: 'lowercase'}])
277
- }
278
-
279
- regex(pattern: RegExp, name: string, options: {name?: string; invert?: boolean}): Rule
280
- regex(pattern: RegExp, options: {name?: string; invert?: boolean}): Rule
281
- regex(pattern: RegExp, name: string): Rule
282
- regex(pattern: RegExp): Rule
283
- regex(
284
- pattern: RegExp,
285
- a?: string | {name?: string; invert?: boolean},
286
- b?: {name?: string; invert?: boolean}
287
- ): Rule {
288
- const name = typeof a === 'string' ? a : a?.name ?? b?.name
289
- const invert = typeof a === 'string' ? false : a?.invert ?? b?.invert
290
-
291
- const constraint: RuleSpecConstraint<'regex'> = {
292
- pattern,
293
- name,
294
- invert: invert || false,
295
- }
296
-
297
- return this.cloneWithRules([{flag: 'regex', constraint}])
298
- }
299
-
300
- email(): Rule {
301
- return this.cloneWithRules([{flag: 'email'}])
302
- }
303
-
304
- uri(opts?: {
305
- scheme?: (string | RegExp) | Array<string | RegExp>
306
- allowRelative?: boolean
307
- relativeOnly?: boolean
308
- allowCredentials?: boolean
309
- }): Rule {
310
- const optsScheme = opts?.scheme || ['http', 'https']
311
- const schemes = Array.isArray(optsScheme) ? optsScheme : [optsScheme]
312
-
313
- if (!schemes.length) {
314
- throw new Error('scheme must have at least 1 scheme specified')
315
- }
316
-
317
- const constraint: RuleSpecConstraint<'uri'> = {
318
- options: {
319
- scheme: schemes.map((scheme) => {
320
- if (!(scheme instanceof RegExp) && typeof scheme !== 'string') {
321
- throw new Error('scheme must be a RegExp or a String')
322
- }
323
-
324
- return typeof scheme === 'string' ? new RegExp(`^${escapeRegex(scheme)}$`) : scheme
325
- }),
326
- allowRelative: opts?.allowRelative || false,
327
- relativeOnly: opts?.relativeOnly || false,
328
- allowCredentials: opts?.allowCredentials || false,
329
- },
330
- }
331
-
332
- return this.cloneWithRules([{flag: 'uri', constraint}])
333
- }
334
-
335
- // Array only
336
- unique(): Rule {
337
- return this.cloneWithRules([{flag: 'unique'}])
338
- }
339
-
340
- // Objects only
341
- reference(): Rule {
342
- return this.cloneWithRules([{flag: 'reference'}])
343
- }
344
-
345
- fields(rules: FieldRules): Rule {
346
- if (this._type !== 'Object') {
347
- throw new Error('fields() can only be called on an object type')
348
- }
349
-
350
- const rule = this.cloneWithRules([])
351
- rule._fieldRules = rules
352
- return rule
353
- }
354
-
355
- assetRequired(): Rule {
356
- const base = getBaseType(this._typeDef)
357
- let assetType: 'Asset' | 'Image' | 'File'
358
- if (base && ['image', 'file'].includes(base.name)) {
359
- assetType = base.name === 'image' ? 'Image' : 'File'
360
- } else {
361
- assetType = 'Asset'
362
- }
363
-
364
- return this.cloneWithRules([{flag: 'assetRequired', constraint: {assetType}}])
365
- }
366
-
367
- async validate(value: unknown, context: ValidationContext): Promise<ValidationMarker[]> {
368
- if (!context) {
369
- throw new Error('missing context')
370
- }
371
-
372
- const valueIsEmpty = value === null || value === undefined
373
-
374
- // Short-circuit on optional, empty fields
375
- if (valueIsEmpty && this._required === 'optional') {
376
- return EMPTY_ARRAY as ValidationMarker[]
377
- }
378
-
379
- const rules =
380
- // Run only the _custom_ functions if the rule is not set to required or optional
381
- this._required === undefined && valueIsEmpty
382
- ? this._rules.filter((curr) => curr.flag === 'custom')
383
- : this._rules
384
-
385
- const validators = (this._type && typeValidators[this._type]) || genericValidator
386
-
387
- const results = await Promise.all(
388
- rules.map(async (curr) => {
389
- if (curr.flag === undefined) {
390
- throw new Error('Invalid rule, did not contain "flag"-property')
391
- }
392
-
393
- const validator: Validator | undefined = validators[curr.flag]
394
- if (!validator) {
395
- const forType = this._type ? `type "${this._type}"` : 'rule without declared type'
396
- throw new Error(`Validator for flag "${curr.flag}" not found for ${forType}`)
397
- }
398
-
399
- let specConstraint = 'constraint' in curr ? curr.constraint : null
400
- if (isFieldRef(specConstraint)) {
401
- specConstraint = get(context.parent, specConstraint.path)
402
- }
403
-
404
- let result
405
- try {
406
- result = await validator(specConstraint, value, this._message, context)
407
- } catch (err) {
408
- const errorFromException = new ValidationErrorClass(
409
- `${pathToString(context.path)}: Exception occurred while validating value: ${
410
- err.message
411
- }`
412
- )
413
- return convertToValidationMarker(errorFromException, 'error', context)
414
- }
415
-
416
- return convertToValidationMarker(result, this._level, context)
417
- })
418
- )
419
-
420
- return results.flat()
421
- }
422
- }
423
-
424
- export default Rule
@@ -1,32 +0,0 @@
1
- import {
2
- Path,
3
- ValidationMarker,
4
- ValidationError as IValidationError,
5
- ValidationErrorOptions,
6
- ValidationErrorClass,
7
- } from '@sanity/types'
8
-
9
- // Follows the same pattern as Rule and RuleClass. @see Rule
10
- const ValidationError: ValidationErrorClass = class ValidationError implements IValidationError {
11
- message: string
12
- paths: Path[]
13
- children: ValidationMarker[] | undefined
14
- operation: 'AND' | 'OR' | undefined
15
-
16
- constructor(message: string, options: ValidationErrorOptions = {}) {
17
- this.message = message
18
- this.paths = options.paths || []
19
- this.children = options.children
20
- this.operation = options.operation
21
- }
22
-
23
- cloneWithMessage(msg: string): ValidationError {
24
- return new ValidationError(msg, {
25
- paths: this.paths,
26
- children: this.children,
27
- operation: this.operation,
28
- })
29
- }
30
- }
31
-
32
- export default ValidationError
package/src/index.ts DELETED
@@ -1,9 +0,0 @@
1
- import RuleClass from './Rule'
2
- import validateDocument from './validateDocument'
3
- import inferFromSchema from './inferFromSchema'
4
- import inferFromSchemaType from './inferFromSchemaType'
5
-
6
- // export default {Rule: RuleClass, validateDocument, inferFromSchema, inferFromSchemaType}
7
- export {RuleClass as Rule, validateDocument, inferFromSchema, inferFromSchemaType}
8
-
9
- export {validateDocumentObservable} from './validateDocument'
@@ -1,19 +0,0 @@
1
- import {Schema} from '@sanity/types'
2
- import inferFromSchemaType from './inferFromSchemaType'
3
-
4
- // Note: Mutates schema. Refactor when @sanity/schema supports middlewares
5
- function inferFromSchema(schema: Schema): Schema {
6
- const typeNames = schema.getTypeNames()
7
-
8
- typeNames.forEach((typeName) => {
9
- const schemaType = schema.get(typeName)
10
-
11
- if (schemaType) {
12
- inferFromSchemaType(schemaType)
13
- }
14
- })
15
-
16
- return schema
17
- }
18
-
19
- export default inferFromSchema
@@ -1,50 +0,0 @@
1
- import {Schema, SchemaType} from '@sanity/types'
2
- import normalizeValidationRules from './util/normalizeValidationRules'
3
-
4
- function traverse(typeDef: SchemaType, visited: Set<SchemaType>) {
5
- if (visited.has(typeDef)) {
6
- return
7
- }
8
-
9
- visited.add(typeDef)
10
-
11
- typeDef.validation = normalizeValidationRules(typeDef)
12
-
13
- if ('fields' in typeDef) {
14
- for (const field of typeDef.fields) {
15
- traverse(field.type, visited)
16
- }
17
- }
18
-
19
- if ('of' in typeDef) {
20
- for (const candidate of typeDef.of) {
21
- traverse(candidate, visited)
22
- }
23
- }
24
-
25
- // eslint-disable-next-line no-warning-comments
26
- // @ts-expect-error TODO (eventually): `annotations` does not exist on the SchemaType yet
27
- if (typeDef.annotations) {
28
- // eslint-disable-next-line no-warning-comments
29
- // @ts-expect-error TODO (eventually): `annotations` does not exist on the SchemaType yet
30
- for (const annotation of typeDef.annotations) {
31
- traverse(annotation, visited)
32
- }
33
- }
34
- }
35
-
36
- // NOTE: this overload is for TS API compatibility with a previous implementation
37
- function inferFromSchemaType(
38
- typeDef: SchemaType,
39
- // these are intentionally unused
40
- _schema: Schema,
41
- _visited?: Set<SchemaType>
42
- ): SchemaType
43
- // note: this seemingly redundant overload is required
44
- function inferFromSchemaType(typeDef: SchemaType): SchemaType
45
- function inferFromSchemaType(typeDef: SchemaType): SchemaType {
46
- traverse(typeDef, new Set())
47
- return typeDef
48
- }
49
-
50
- export default inferFromSchemaType
@@ -1,84 +0,0 @@
1
- import {ValidationMarker, ValidationError, ValidationContext} from '@sanity/types'
2
- import ValidationErrorClass from '../ValidationError'
3
- import pathToString from '../util/pathToString'
4
-
5
- type ValidationErrorLike = Pick<ValidationError, 'message'> & Partial<ValidationError>
6
-
7
- export function isNonNullable<T>(t: T): t is NonNullable<T> {
8
- return t !== null || t !== undefined
9
- }
10
-
11
- export function convertToValidationMarker(
12
- validatorResult:
13
- | true
14
- | true[]
15
- | string
16
- | string[]
17
- | ValidationError
18
- | ValidationError[]
19
- | ValidationErrorLike
20
- | ValidationErrorLike[],
21
- level: 'error' | 'warning' | 'info' | undefined,
22
- context: ValidationContext
23
- ): ValidationMarker[] {
24
- if (!context) {
25
- throw new Error('missing context')
26
- }
27
-
28
- if (validatorResult === true) return []
29
-
30
- if (Array.isArray(validatorResult)) {
31
- return validatorResult
32
- .flatMap((child) => convertToValidationMarker(child, level, context))
33
- .filter(isNonNullable)
34
- }
35
-
36
- if (typeof validatorResult === 'string') {
37
- return convertToValidationMarker(new ValidationErrorClass(validatorResult), level, context)
38
- }
39
-
40
- if (!(validatorResult instanceof ValidationErrorClass)) {
41
- // in order to accept the `ValidationErrorLike`, it at least needs to have
42
- // a `message` in the object
43
- if (typeof validatorResult?.message !== 'string') {
44
- throw new Error(
45
- `${pathToString(
46
- context.path
47
- )}: Validator must return 'true' if valid or an error message as a string on errors`
48
- )
49
- }
50
-
51
- // this is the occurs when an object is returned that wasn't created with the
52
- // `ValidationErrorClass`. in this case, we want to convert it to a class
53
- return convertToValidationMarker(
54
- new ValidationErrorClass(validatorResult.message, validatorResult),
55
- level,
56
- context
57
- )
58
- }
59
-
60
- const results: ValidationMarker[] = []
61
-
62
- // the validator result does not include any item-level relative paths,
63
- // then just return the top-level path with the validation result
64
- if (!validatorResult.paths?.length) {
65
- return [
66
- {
67
- level: level || 'error',
68
- item: validatorResult,
69
- path: context.path || [],
70
- },
71
- ]
72
- }
73
-
74
- // if the validator result did include item-level relative paths, then for
75
- // each item-level relative path, create a validation marker that concatenates
76
- // the relative path with the path from the validation context
77
- return results.concat(
78
- validatorResult.paths.map((path) => ({
79
- path: (context.path || []).concat(path),
80
- level: level || 'error',
81
- item: validatorResult,
82
- }))
83
- )
84
- }
@@ -1,77 +0,0 @@
1
- /**
2
- * Modified version of fast-deep-equal (https://github.com/epoberezkin/fast-deep-equal)
3
- * MIT-licensed, copyright (c) 2017 Evgeny Poberezkin
4
- **/
5
-
6
- // NOTE: when converting to typescript, some of the checks were inlined (vs
7
- // having them in a variable) because the type predicate type narrowing only
8
- // works when type predicate is called inline in the condition that starts the
9
- // control flow branch.
10
- // see here: https://www.typescriptlang.org/docs/handbook/2/narrowing.html
11
- export default function deepEquals(a: unknown, b: unknown): boolean {
12
- if (a === b) {
13
- return true
14
- }
15
-
16
- if (Array.isArray(a) && Array.isArray(b)) {
17
- if (a.length != b.length) return false
18
- for (let i = 0; i < a.length; i++) {
19
- if (!deepEquals(a[i], b[i])) {
20
- return false
21
- }
22
- }
23
- return true
24
- }
25
-
26
- if (Array.isArray(a) != Array.isArray(b)) {
27
- return false
28
- }
29
-
30
- if (a && b && typeof a === 'object' && typeof b === 'object') {
31
- const keys = Object.keys(a)
32
- if (keys.length !== Object.keys(b).length) {
33
- return false
34
- }
35
-
36
- if (a instanceof Date && b instanceof Date) {
37
- return a.getTime() === b.getTime()
38
- }
39
-
40
- if (a instanceof Date != b instanceof Date) {
41
- return false
42
- }
43
-
44
- if (a instanceof RegExp && b instanceof RegExp) {
45
- return a.toString() == b.toString()
46
- }
47
-
48
- if (a instanceof RegExp != b instanceof RegExp) {
49
- return false
50
- }
51
-
52
- for (let i = 0; i < keys.length; i++) {
53
- if (keys[i] === '_key') {
54
- continue
55
- }
56
-
57
- if (!Object.prototype.hasOwnProperty.call(b, keys[i])) {
58
- return false
59
- }
60
- }
61
-
62
- for (let i = 0; i < keys.length; i++) {
63
- const key = keys[i] as keyof typeof a
64
- if (key === '_key') {
65
- continue
66
- }
67
-
68
- if (!deepEquals(a[key], b[key])) {
69
- return false
70
- }
71
- }
72
-
73
- return true
74
- }
75
-
76
- return false
77
- }
@@ -1,5 +0,0 @@
1
- /* eslint-disable no-useless-escape */
2
- export default (string: string): string => {
3
- // Escape ^$.*+-?=!:|\/()[]{},
4
- return string.replace(/[\^\$\.\*\+\-\?\=\!\:\|\\\/\(\)\[\]\{\}\,]/g, '\\$&')
5
- }