aboutsummaryrefslogtreecommitdiff
path: root/node_modules/concordance/lib
diff options
context:
space:
mode:
authorFlorian Dold <florian.dold@gmail.com>2017-08-14 05:01:11 +0200
committerFlorian Dold <florian.dold@gmail.com>2017-08-14 05:02:09 +0200
commit363723fc84f7b8477592e0105aeb331ec9a017af (patch)
tree29f92724f34131bac64d6a318dd7e30612e631c7 /node_modules/concordance/lib
parent5634e77ad96bfe1818f6b6ee70b7379652e5487f (diff)
downloadwallet-core-363723fc84f7b8477592e0105aeb331ec9a017af.tar.xz
node_modules
Diffstat (limited to 'node_modules/concordance/lib')
-rw-r--r--node_modules/concordance/lib/Circular.js35
-rw-r--r--node_modules/concordance/lib/Indenter.js22
-rw-r--r--node_modules/concordance/lib/Registry.js24
-rw-r--r--node_modules/concordance/lib/compare.js103
-rw-r--r--node_modules/concordance/lib/complexValues/arguments.js48
-rw-r--r--node_modules/concordance/lib/complexValues/arrayBuffer.js30
-rw-r--r--node_modules/concordance/lib/complexValues/boxed.js51
-rw-r--r--node_modules/concordance/lib/complexValues/dataView.js29
-rw-r--r--node_modules/concordance/lib/complexValues/date.js89
-rw-r--r--node_modules/concordance/lib/complexValues/error.js133
-rw-r--r--node_modules/concordance/lib/complexValues/function.js159
-rw-r--r--node_modules/concordance/lib/complexValues/global.js33
-rw-r--r--node_modules/concordance/lib/complexValues/map.js78
-rw-r--r--node_modules/concordance/lib/complexValues/object.js283
-rw-r--r--node_modules/concordance/lib/complexValues/promise.js40
-rw-r--r--node_modules/concordance/lib/complexValues/regexp.js90
-rw-r--r--node_modules/concordance/lib/complexValues/set.js78
-rw-r--r--node_modules/concordance/lib/complexValues/typedArray.js162
-rw-r--r--node_modules/concordance/lib/constants.js13
-rw-r--r--node_modules/concordance/lib/describe.js170
-rw-r--r--node_modules/concordance/lib/diff.js391
-rw-r--r--node_modules/concordance/lib/encoder.js293
-rw-r--r--node_modules/concordance/lib/format.js101
-rw-r--r--node_modules/concordance/lib/formatUtils.js123
-rw-r--r--node_modules/concordance/lib/getCtor.js43
-rw-r--r--node_modules/concordance/lib/getObjectKeys.js36
-rw-r--r--node_modules/concordance/lib/getStringTag.js30
-rw-r--r--node_modules/concordance/lib/hasLength.js7
-rw-r--r--node_modules/concordance/lib/isEnumerable.js7
-rw-r--r--node_modules/concordance/lib/lineBuilder.js309
-rw-r--r--node_modules/concordance/lib/metaDescriptors/item.js254
-rw-r--r--node_modules/concordance/lib/metaDescriptors/mapEntry.js223
-rw-r--r--node_modules/concordance/lib/metaDescriptors/pointer.js31
-rw-r--r--node_modules/concordance/lib/metaDescriptors/property.js190
-rw-r--r--node_modules/concordance/lib/metaDescriptors/stats.js136
-rw-r--r--node_modules/concordance/lib/pluginRegistry.js222
-rw-r--r--node_modules/concordance/lib/primitiveValues/boolean.js40
-rw-r--r--node_modules/concordance/lib/primitiveValues/null.js32
-rw-r--r--node_modules/concordance/lib/primitiveValues/number.js41
-rw-r--r--node_modules/concordance/lib/primitiveValues/string.js306
-rw-r--r--node_modules/concordance/lib/primitiveValues/symbol.js114
-rw-r--r--node_modules/concordance/lib/primitiveValues/undefined.js32
-rw-r--r--node_modules/concordance/lib/recursorUtils.js110
-rw-r--r--node_modules/concordance/lib/serialize.js339
-rw-r--r--node_modules/concordance/lib/shouldCompareDeep.js17
-rw-r--r--node_modules/concordance/lib/symbolProperties.js106
-rw-r--r--node_modules/concordance/lib/themeUtils.js195
47 files changed, 5398 insertions, 0 deletions
diff --git a/node_modules/concordance/lib/Circular.js b/node_modules/concordance/lib/Circular.js
new file mode 100644
index 000000000..c5c020267
--- /dev/null
+++ b/node_modules/concordance/lib/Circular.js
@@ -0,0 +1,35 @@
+'use strict'
+
+class Circular {
+ constructor () {
+ this.stack = new Map()
+ }
+
+ add (descriptor) {
+ if (this.stack.has(descriptor)) throw new Error('Already in stack')
+
+ if (descriptor.isItem !== true && descriptor.isMapEntry !== true && descriptor.isProperty !== true) {
+ this.stack.set(descriptor, this.stack.size + 1)
+ }
+ return this
+ }
+
+ delete (descriptor) {
+ if (this.stack.has(descriptor)) {
+ if (this.stack.get(descriptor) !== this.stack.size) throw new Error('Not on top of stack')
+ this.stack.delete(descriptor)
+ }
+ return this
+ }
+
+ has (descriptor) {
+ return this.stack.has(descriptor)
+ }
+
+ get (descriptor) {
+ return this.stack.has(descriptor)
+ ? this.stack.get(descriptor)
+ : 0
+ }
+}
+module.exports = Circular
diff --git a/node_modules/concordance/lib/Indenter.js b/node_modules/concordance/lib/Indenter.js
new file mode 100644
index 000000000..deeca842e
--- /dev/null
+++ b/node_modules/concordance/lib/Indenter.js
@@ -0,0 +1,22 @@
+'use strict'
+
+class Indenter {
+ constructor (level, step) {
+ this.level = level
+ this.step = step
+ this.value = step.repeat(level)
+ }
+
+ increase () {
+ return new Indenter(this.level + 1, this.step)
+ }
+
+ decrease () {
+ return new Indenter(this.level - 1, this.step)
+ }
+
+ toString () {
+ return this.value
+ }
+}
+module.exports = Indenter
diff --git a/node_modules/concordance/lib/Registry.js b/node_modules/concordance/lib/Registry.js
new file mode 100644
index 000000000..f4a4b6808
--- /dev/null
+++ b/node_modules/concordance/lib/Registry.js
@@ -0,0 +1,24 @@
+'use strict'
+
+class Registry {
+ constructor () {
+ this.counter = 0
+ this.map = new WeakMap()
+ }
+
+ has (value) {
+ return this.map.has(value)
+ }
+
+ get (value) {
+ return this.map.get(value).descriptor
+ }
+
+ alloc (value) {
+ const index = ++this.counter
+ const pointer = {descriptor: null, index}
+ this.map.set(value, pointer)
+ return pointer
+ }
+}
+module.exports = Registry
diff --git a/node_modules/concordance/lib/compare.js b/node_modules/concordance/lib/compare.js
new file mode 100644
index 000000000..7e24228e6
--- /dev/null
+++ b/node_modules/concordance/lib/compare.js
@@ -0,0 +1,103 @@
+'use strict'
+
+const constants = require('./constants')
+const describe = require('./describe')
+const recursorUtils = require('./recursorUtils')
+const shouldCompareDeep = require('./shouldCompareDeep')
+const symbolProperties = require('./symbolProperties')
+const Circular = require('./Circular')
+
+const AMBIGUOUS = constants.AMBIGUOUS
+const DEEP_EQUAL = constants.DEEP_EQUAL
+const UNEQUAL = constants.UNEQUAL
+
+function shortcircuitPrimitive (value) {
+ if (value === null || value === undefined || value === true || value === false) return true
+
+ const type = typeof value
+ if (type === 'string' || type === 'symbol') return true
+ // Don't shortcircuit NaN values
+ if (type === 'number') return !isNaN(value)
+
+ return false
+}
+
+function compareDescriptors (lhs, rhs) {
+ const lhsCircular = new Circular()
+ const rhsCircular = new Circular()
+
+ const lhsStack = []
+ const rhsStack = []
+ let topIndex = -1
+
+ do {
+ let result
+ if (lhsCircular.has(lhs)) {
+ result = lhsCircular.get(lhs) === rhsCircular.get(rhs)
+ ? DEEP_EQUAL
+ : UNEQUAL
+ } else if (rhsCircular.has(rhs)) {
+ result = UNEQUAL
+ } else {
+ result = lhs.compare(rhs)
+ }
+
+ if (result === UNEQUAL) return false
+ if (result !== DEEP_EQUAL) {
+ if (!shouldCompareDeep(result, lhs, rhs)) return false
+
+ if (result === AMBIGUOUS && lhs.isProperty === true) {
+ // Replace both sides by a pseudo-descriptor which collects symbol
+ // properties instead.
+ lhs = new symbolProperties.Collector(lhs, lhsStack[topIndex].recursor)
+ rhs = new symbolProperties.Collector(rhs, rhsStack[topIndex].recursor)
+ // Replace the current recursors so they can continue correctly after
+ // the collectors have been "compared". This is necessary since the
+ // collectors eat the first value after the last symbol property.
+ lhsStack[topIndex].recursor = recursorUtils.unshift(lhsStack[topIndex].recursor, lhs.collectAll())
+ rhsStack[topIndex].recursor = recursorUtils.unshift(rhsStack[topIndex].recursor, rhs.collectAll())
+ }
+
+ lhsCircular.add(lhs)
+ rhsCircular.add(rhs)
+
+ lhsStack.push({ subject: lhs, recursor: lhs.createRecursor() })
+ rhsStack.push({ subject: rhs, recursor: rhs.createRecursor() })
+ topIndex++
+ }
+
+ while (topIndex >= 0) {
+ lhs = lhsStack[topIndex].recursor()
+ rhs = rhsStack[topIndex].recursor()
+ if (lhs !== null && rhs !== null) {
+ break
+ }
+
+ if (lhs === null && rhs === null) {
+ const lhsRecord = lhsStack.pop()
+ const rhsRecord = rhsStack.pop()
+ lhsCircular.delete(lhsRecord.subject)
+ rhsCircular.delete(rhsRecord.subject)
+ topIndex--
+ } else {
+ return false
+ }
+ }
+ } while (topIndex >= 0)
+
+ return true
+}
+exports.compareDescriptors = compareDescriptors
+
+function compare (actual, expected, options) {
+ if (Object.is(actual, expected)) return { pass: true }
+ // Primitive values should be the same, so if actual or expected is primitive
+ // then the values will never compare.
+ if (shortcircuitPrimitive(actual) || shortcircuitPrimitive(expected)) return { pass: false }
+
+ actual = describe(actual, options)
+ expected = describe(expected, options)
+ const pass = compareDescriptors(actual, expected)
+ return { actual, expected, pass }
+}
+exports.compare = compare
diff --git a/node_modules/concordance/lib/complexValues/arguments.js b/node_modules/concordance/lib/complexValues/arguments.js
new file mode 100644
index 000000000..446407222
--- /dev/null
+++ b/node_modules/concordance/lib/complexValues/arguments.js
@@ -0,0 +1,48 @@
+'use strict'
+
+const constants = require('../constants')
+const object = require('./object')
+
+const AMBIGUOUS = constants.AMBIGUOUS
+const UNEQUAL = constants.UNEQUAL
+
+function describe (props) {
+ return new DescribedArgumentsValue(Object.assign({
+ // Treat as an array, to allow comparisons with arrays
+ isArray: true,
+ isList: true
+ }, props, { ctor: 'Arguments' }))
+}
+exports.describe = describe
+
+function deserialize (state, recursor) {
+ return new DeserializedArgumentsValue(state, recursor)
+}
+exports.deserialize = deserialize
+
+const tag = Symbol('ArgumentsValue')
+exports.tag = tag
+
+class ArgumentsValue extends object.ObjectValue {
+ compare (expected) {
+ if (expected.isComplex !== true) return UNEQUAL
+
+ // When used on the left-hand side of a comparison, argument values may be
+ // compared to arrays.
+ if (expected.stringTag === 'Array') return AMBIGUOUS
+
+ return super.compare(expected)
+ }
+}
+Object.defineProperty(ArgumentsValue.prototype, 'tag', { value: tag })
+
+const DescribedArgumentsValue = object.DescribedMixin(ArgumentsValue)
+
+class DeserializedArgumentsValue extends object.DeserializedMixin(ArgumentsValue) {
+ compare (expected) {
+ // Deserialized argument values may only be compared to argument values.
+ return expected.isComplex === true && expected.stringTag === 'Array'
+ ? UNEQUAL
+ : super.compare(expected)
+ }
+}
diff --git a/node_modules/concordance/lib/complexValues/arrayBuffer.js b/node_modules/concordance/lib/complexValues/arrayBuffer.js
new file mode 100644
index 000000000..1053b893e
--- /dev/null
+++ b/node_modules/concordance/lib/complexValues/arrayBuffer.js
@@ -0,0 +1,30 @@
+'use strict'
+
+const typedArray = require('./typedArray')
+
+function describe (props) {
+ return new DescribedArrayBufferValue(Object.assign({
+ // Assume at least Node.js 4.5.0, which introduces Buffer.from()
+ buffer: Buffer.from(props.value),
+ // Set isArray and isList so the property recursor excludes the byte accessors
+ isArray: true,
+ isList: true
+ }, props))
+}
+exports.describe = describe
+
+function deserialize (state, recursor) {
+ return new DeserializedArrayBufferValue(state, recursor)
+}
+exports.deserialize = deserialize
+
+const tag = Symbol('ArrayBufferValue')
+exports.tag = tag
+
+// ArrayBuffers can be represented as regular Buffers, allowing them to be
+// treated as TypedArrays for the purposes of this package.
+class ArrayBufferValue extends typedArray.TypedArrayValue {}
+Object.defineProperty(ArrayBufferValue.prototype, 'tag', { value: tag })
+
+const DescribedArrayBufferValue = typedArray.DescribedMixin(ArrayBufferValue)
+const DeserializedArrayBufferValue = typedArray.DeserializedMixin(ArrayBufferValue)
diff --git a/node_modules/concordance/lib/complexValues/boxed.js b/node_modules/concordance/lib/complexValues/boxed.js
new file mode 100644
index 000000000..3736ad137
--- /dev/null
+++ b/node_modules/concordance/lib/complexValues/boxed.js
@@ -0,0 +1,51 @@
+'use strict'
+
+const recursorUtils = require('../recursorUtils')
+const stringPrimitive = require('../primitiveValues/string').tag
+const object = require('./object')
+
+function describe (props) {
+ return new DescribedBoxedValue(props)
+}
+exports.describe = describe
+
+function deserialize (state, recursor) {
+ return new DeserializedBoxedValue(state, recursor)
+}
+exports.deserialize = deserialize
+
+const tag = Symbol('BoxedValue')
+exports.tag = tag
+
+class BoxedValue extends object.ObjectValue {}
+Object.defineProperty(BoxedValue.prototype, 'tag', {value: tag})
+
+class DescribedBoxedValue extends object.DescribedMixin(BoxedValue) {
+ constructor (props) {
+ super(props)
+ this.unboxed = props.unboxed
+ }
+
+ createListRecursor () {
+ return recursorUtils.NOOP_RECURSOR
+ }
+
+ createPropertyRecursor () {
+ if (this.unboxed.tag !== stringPrimitive) return super.createPropertyRecursor()
+
+ // Just so that createPropertyRecursor() skips the index-based character
+ // properties.
+ try {
+ this.isList = true
+ return super.createPropertyRecursor()
+ } finally {
+ this.isList = false
+ }
+ }
+
+ createRecursor () {
+ return recursorUtils.unshift(super.createRecursor(), this.unboxed)
+ }
+}
+
+const DeserializedBoxedValue = object.DeserializedMixin(BoxedValue)
diff --git a/node_modules/concordance/lib/complexValues/dataView.js b/node_modules/concordance/lib/complexValues/dataView.js
new file mode 100644
index 000000000..0dd6199f6
--- /dev/null
+++ b/node_modules/concordance/lib/complexValues/dataView.js
@@ -0,0 +1,29 @@
+'use strict'
+
+const typedArray = require('./typedArray')
+
+function describe (props) {
+ return new DescribedDataViewValue(Object.assign({
+ buffer: typedArray.getBuffer(props.value),
+ // Set isArray and isList so the property recursor excludes the byte accessors
+ isArray: true,
+ isList: true
+ }, props))
+}
+exports.describe = describe
+
+function deserialize (state, recursor) {
+ return new DeserializedDataViewValue(state, recursor)
+}
+exports.deserialize = deserialize
+
+const tag = Symbol('DataViewValue')
+exports.tag = tag
+
+// DataViews can be represented as regular Buffers, allowing them to be treated
+// as TypedArrays for the purposes of this package.
+class DataViewValue extends typedArray.TypedArrayValue {}
+Object.defineProperty(DataViewValue.prototype, 'tag', { value: tag })
+
+const DescribedDataViewValue = typedArray.DescribedMixin(DataViewValue)
+const DeserializedDataViewValue = typedArray.DeserializedMixin(DataViewValue)
diff --git a/node_modules/concordance/lib/complexValues/date.js b/node_modules/concordance/lib/complexValues/date.js
new file mode 100644
index 000000000..b437456b8
--- /dev/null
+++ b/node_modules/concordance/lib/complexValues/date.js
@@ -0,0 +1,89 @@
+'use strict'
+
+const dateTime = require('date-time')
+
+const constants = require('../constants')
+const formatUtils = require('../formatUtils')
+const lineBuilder = require('../lineBuilder')
+const object = require('./object')
+
+const SHALLOW_EQUAL = constants.SHALLOW_EQUAL
+const UNEQUAL = constants.UNEQUAL
+
+function describe (props) {
+ const date = props.value
+ const invalid = isNaN(date.valueOf())
+ return new DescribedDateValue(Object.assign({}, props, {invalid}))
+}
+exports.describe = describe
+
+function deserialize (state, recursor) {
+ return new DeserializedDateValue(state, recursor)
+}
+exports.deserialize = deserialize
+
+const tag = Symbol('DateValue')
+exports.tag = tag
+
+function formatDate (date) {
+ // Always format in UTC. The local timezone shouldn't be used since it's most
+ // likely different from that of CI servers.
+ return dateTime({
+ date,
+ local: false,
+ showTimeZone: true,
+ showMilliseconds: true
+ })
+}
+
+class DateValue extends object.ObjectValue {
+ constructor (props) {
+ super(props)
+ this.invalid = props.invalid
+ }
+
+ compare (expected) {
+ const result = super.compare(expected)
+ if (result !== SHALLOW_EQUAL) return result
+
+ return (this.invalid && expected.invalid) || Object.is(this.value.getTime(), expected.value.getTime())
+ ? SHALLOW_EQUAL
+ : UNEQUAL
+ }
+
+ formatShallow (theme, indent) {
+ const string = formatUtils.formatCtorAndStringTag(theme, this) + ' ' +
+ (this.invalid ? theme.date.invalid : formatUtils.wrap(theme.date.value, formatDate(this.value))) + ' ' +
+ theme.object.openBracket
+
+ return super.formatShallow(theme, indent).customize({
+ finalize (innerLines) {
+ return innerLines.isEmpty
+ ? lineBuilder.single(string + theme.object.closeBracket)
+ : lineBuilder.first(string)
+ .concat(innerLines.withFirstPrefixed(indent.increase()).stripFlags())
+ .append(lineBuilder.last(indent + theme.object.closeBracket))
+ },
+
+ maxDepth () {
+ return lineBuilder.single(string + ' ' + theme.maxDepth + ' ' + theme.object.closeBracket)
+ }
+ })
+ }
+
+ serialize () {
+ const iso = this.invalid ? null : this.value.toISOString()
+ return [this.invalid, iso, super.serialize()]
+ }
+}
+Object.defineProperty(DateValue.prototype, 'tag', { value: tag })
+
+const DescribedDateValue = object.DescribedMixin(DateValue)
+
+class DeserializedDateValue extends object.DeserializedMixin(DateValue) {
+ constructor (state, recursor) {
+ super(state[2], recursor)
+ this.invalid = state[0]
+ this.value = new Date(this.invalid ? NaN : state[1])
+ }
+}
diff --git a/node_modules/concordance/lib/complexValues/error.js b/node_modules/concordance/lib/complexValues/error.js
new file mode 100644
index 000000000..781dd7e87
--- /dev/null
+++ b/node_modules/concordance/lib/complexValues/error.js
@@ -0,0 +1,133 @@
+'use strict'
+
+const constants = require('../constants')
+const isEnumerable = require('../isEnumerable')
+const formatUtils = require('../formatUtils')
+const lineBuilder = require('../lineBuilder')
+const NOOP_RECURSOR = require('../recursorUtils').NOOP_RECURSOR
+const object = require('./object')
+
+const UNEQUAL = constants.UNEQUAL
+
+function describe (props) {
+ const error = props.value
+ return new DescribedErrorValue(Object.assign({
+ nameIsEnumerable: isEnumerable(error, 'name'),
+ name: error.name,
+ messageIsEnumerable: isEnumerable(error, 'message'),
+ message: error.message
+ }, props))
+}
+exports.describe = describe
+
+function deserialize (state, recursor) {
+ return new DeserializedErrorValue(state, recursor)
+}
+exports.deserialize = deserialize
+
+const tag = Symbol('ErrorValue')
+exports.tag = tag
+
+class ErrorValue extends object.ObjectValue {
+ constructor (props) {
+ super(props)
+ this.name = props.name
+ }
+
+ compare (expected) {
+ return this.tag === expected.tag && this.name === expected.name
+ ? super.compare(expected)
+ : UNEQUAL
+ }
+
+ formatShallow (theme, indent) {
+ const name = this.name || this.ctor
+
+ let string = name
+ ? formatUtils.wrap(theme.error.name, name)
+ : formatUtils.wrap(theme.object.stringTag, this.stringTag)
+ if (this.ctor && this.ctor !== name) {
+ string += ' ' + formatUtils.wrap(theme.error.ctor, this.ctor)
+ }
+ if (this.stringTag && this.stringTag !== this.ctor && this.name && !this.name.includes(this.stringTag)) {
+ string += ' ' + formatUtils.wrap(theme.object.secondaryStringTag, this.stringTag)
+ }
+ string += ' ' + theme.object.openBracket
+
+ return super.formatShallow(theme, indent).customize({
+ finalize (innerLines) {
+ return innerLines.isEmpty
+ ? lineBuilder.single(string + theme.object.closeBracket)
+ : lineBuilder.first(string)
+ .concat(innerLines.withFirstPrefixed(indent.increase()).stripFlags())
+ .append(lineBuilder.last(indent + theme.object.closeBracket))
+ },
+
+ maxDepth () {
+ return lineBuilder.single(string + ' ' + theme.maxDepth + ' ' + theme.object.closeBracket)
+ }
+ })
+ }
+
+ serialize () {
+ return [this.name, super.serialize()]
+ }
+}
+Object.defineProperty(ErrorValue.prototype, 'tag', { value: tag })
+
+class DescribedErrorValue extends object.DescribedMixin(ErrorValue) {
+ constructor (props) {
+ super(props)
+ this.nameIsEnumerable = props.nameIsEnumerable
+ this.messageIsEnumerable = props.messageIsEnumerable
+ this.message = props.message
+ }
+
+ createPropertyRecursor () {
+ const recursor = super.createPropertyRecursor()
+
+ let skipName = this.nameIsEnumerable
+ let emitMessage = !this.messageIsEnumerable
+
+ let size = recursor.size
+ if (skipName && size > 0) {
+ size -= 1
+ }
+ if (emitMessage) {
+ size += 1
+ }
+
+ if (size === 0) return NOOP_RECURSOR
+
+ let done = false
+ const next = () => {
+ if (done) return null
+
+ const property = recursor.next()
+ if (property) {
+ if (skipName && property.key.value === 'name') {
+ skipName = false
+ return next()
+ }
+ return property
+ }
+
+ if (emitMessage) {
+ emitMessage = false
+ return this.describeProperty('message', this.describeAny(this.message))
+ }
+
+ done = true
+ return null
+ }
+
+ return { size, next }
+ }
+}
+
+class DeserializedErrorValue extends object.DeserializedMixin(ErrorValue) {
+ constructor (state, recursor) {
+ super(state[1], recursor)
+ this.name = state[0]
+ }
+}
diff --git a/node_modules/concordance/lib/complexValues/function.js b/node_modules/concordance/lib/complexValues/function.js
new file mode 100644
index 000000000..4ddd38f39
--- /dev/null
+++ b/node_modules/concordance/lib/complexValues/function.js
@@ -0,0 +1,159 @@
+'use strict'
+
+const functionNameSupport = require('function-name-support')
+
+const constants = require('../constants')
+const getStringTag = require('../getStringTag')
+const isEnumerable = require('../isEnumerable')
+const formatUtils = require('../formatUtils')
+const lineBuilder = require('../lineBuilder')
+const NOOP_RECURSOR = require('../recursorUtils').NOOP_RECURSOR
+const object = require('./object')
+
+const UNEQUAL = constants.UNEQUAL
+const SHALLOW_EQUAL = constants.SHALLOW_EQUAL
+
+// Node.js 4 provides Function, more recent versions use GeneratorFunction
+const generatorsHaveGeneratorTag = getStringTag(function * () {}) === 'GeneratorFunction'
+
+function describe (props) {
+ const fn = props.value
+ return new DescribedFunctionValue(Object.assign({
+ nameIsEnumerable: isEnumerable(fn, 'name'),
+ name: typeof fn.name === 'string' ? fn.name : null
+ }, props))
+}
+exports.describe = describe
+
+function deserialize (state, recursor) {
+ return new DeserializedFunctionValue(state, recursor)
+}
+exports.deserialize = deserialize
+
+const tag = Symbol('FunctionValue')
+exports.tag = tag
+
+class FunctionValue extends object.ObjectValue {
+ constructor (props) {
+ super(props)
+ this.name = props.name
+ }
+
+ formatShallow (theme, indent) {
+ const string = formatUtils.wrap(theme.function.stringTag, this.stringTag) +
+ (this.name ? ' ' + formatUtils.wrap(theme.function.name, this.name) : '') +
+ ' ' + theme.object.openBracket
+
+ return super.formatShallow(theme, indent).customize({
+ finalize (innerLines) {
+ return innerLines.isEmpty
+ ? lineBuilder.single(string + theme.object.closeBracket)
+ : lineBuilder.first(string)
+ .concat(innerLines.withFirstPrefixed(indent.increase()).stripFlags())
+ .append(lineBuilder.last(indent + theme.object.closeBracket))
+ },
+
+ maxDepth () {
+ return lineBuilder.single(string + ' ' + theme.maxDepth + ' ' + theme.object.closeBracket)
+ }
+ })
+ }
+}
+Object.defineProperty(FunctionValue.prototype, 'tag', { value: tag })
+
+class DescribedFunctionValue extends object.DescribedMixin(FunctionValue) {
+ constructor (props) {
+ super(props)
+ this.nameIsEnumerable = props.nameIsEnumerable
+ }
+
+ compare (expected) {
+ if (this.tag !== expected.tag) return UNEQUAL
+ if (this.name !== expected.name) return UNEQUAL
+ if (this.value && expected.value && this.value !== expected.value) return UNEQUAL
+
+ return super.compare(expected)
+ }
+
+ createPropertyRecursor () {
+ const recursor = super.createPropertyRecursor()
+
+ const skipName = this.nameIsEnumerable
+ if (!skipName) return recursor
+
+ let size = recursor.size
+ if (skipName) {
+ size -= 1
+ }
+
+ if (size === 0) return NOOP_RECURSOR
+
+ const next = () => {
+ const property = recursor.next()
+ if (property) {
+ if (skipName && property.key.value === 'name') {
+ return next()
+ }
+ return property
+ }
+
+ return null
+ }
+
+ return { size, next }
+ }
+
+ serialize () {
+ return [this.name, generatorsHaveGeneratorTag, super.serialize()]
+ }
+}
+
+class DeserializedFunctionValue extends object.DeserializedMixin(FunctionValue) {
+ constructor (state, recursor) {
+ super(state[2], recursor)
+ this.name = state[0]
+ this.trustStringTag = state[1]
+ }
+
+ compare (expected) {
+ if (this.tag !== expected.tag) return UNEQUAL
+
+ if (this.name !== expected.name) {
+ if (this.functionNameSupportFlags === functionNameSupport.bitFlags) {
+ // The engine used to create the serialization supports the same
+ // function name inference as the current engine. That said, unless
+ // the engine has full support for name inference, it's possible that
+ // names were lost simply due to refactoring. Names are unequal if
+ // the engine has full support, or if names were inferred.
+ if (functionNameSupport.hasFullSupport === true || (this.name !== '' && expected.name !== '')) return UNEQUAL
+ } else if (functionNameSupport.isSubsetOf(this.functionNameSupportFlags)) {
+ // The engine used to create the serialization could infer more function
+ // names than the current engine. Assume `expected.name` comes from the
+ // current engine and treat the names as unequal only if the current
+ // engine could infer a name.
+ if (expected.name !== '') return UNEQUAL
+ } else {
+ /* istanbul ignore else */
+ if (functionNameSupport.isSupersetOf(this.functionNameSupportFlags)) {
+ // The engine used to create the serialization could infer fewer
+ // function names than the current engine. Treat the names as unequal
+ // only if a name was in the serialization.
+ if (this.name !== '') return UNEQUAL
+ }
+ }
+ }
+
+ // Assume `stringTag` is either 'Function' or 'GeneratorFunction', and that
+ // it always equals `ctor`. Since Node.js 4 only provides 'Function', even
+ // for generator functions, only compare `stringTag` if the serialized value
+ // legitimately would have been `Function`, and the expected `stringTag` can
+ // reliably be inferred.
+ if (this.trustStringTag && generatorsHaveGeneratorTag && this.stringTag !== expected.stringTag) return UNEQUAL
+
+ return SHALLOW_EQUAL
+ }
+
+ serialize () {
+ return [this.name, this.trustStringTag, super.serialize()]
+ }
+}
diff --git a/node_modules/concordance/lib/complexValues/global.js b/node_modules/concordance/lib/complexValues/global.js
new file mode 100644
index 000000000..6acd66d7a
--- /dev/null
+++ b/node_modules/concordance/lib/complexValues/global.js
@@ -0,0 +1,33 @@
+'use strict'
+
+const constants = require('../constants')
+const formatUtils = require('../formatUtils')
+const lineBuilder = require('../lineBuilder')
+
+const DEEP_EQUAL = constants.DEEP_EQUAL
+const UNEQUAL = constants.UNEQUAL
+
+function describe () {
+ return new GlobalValue()
+}
+exports.describe = describe
+
+exports.deserialize = describe
+
+const tag = Symbol('GlobalValue')
+exports.tag = tag
+
+class GlobalValue {
+ compare (expected) {
+ return this.tag === expected.tag
+ ? DEEP_EQUAL
+ : UNEQUAL
+ }
+
+ formatDeep (theme) {
+ return lineBuilder.single(
+ formatUtils.wrap(theme.global, 'Global') + ' ' + theme.object.openBracket + theme.object.closeBracket)
+ }
+}
+Object.defineProperty(GlobalValue.prototype, 'isComplex', { value: true })
+Object.defineProperty(GlobalValue.prototype, 'tag', { value: tag })
diff --git a/node_modules/concordance/lib/complexValues/map.js b/node_modules/concordance/lib/complexValues/map.js
new file mode 100644
index 000000000..7ff13b217
--- /dev/null
+++ b/node_modules/concordance/lib/complexValues/map.js
@@ -0,0 +1,78 @@
+'use strict'
+
+const constants = require('../constants')
+const recursorUtils = require('../recursorUtils')
+const object = require('./object')
+
+const SHALLOW_EQUAL = constants.SHALLOW_EQUAL
+const UNEQUAL = constants.UNEQUAL
+
+function describe (props) {
+ return new DescribedMapValue(Object.assign({
+ size: props.value.size
+ }, props))
+}
+exports.describe = describe
+
+function deserialize (state, recursor) {
+ return new DeserializedMapValue(state, recursor)
+}
+exports.deserialize = deserialize
+
+const tag = Symbol('MapValue')
+exports.tag = tag
+
+class MapValue extends object.ObjectValue {
+ constructor (props) {
+ super(props)
+ this.size = props.size
+ }
+
+ compare (expected) {
+ const result = super.compare(expected)
+ if (result !== SHALLOW_EQUAL) return result
+
+ return this.size === expected.size
+ ? SHALLOW_EQUAL
+ : UNEQUAL
+ }
+
+ prepareDiff (expected) {
+ // Maps should be compared, even if they have a different number of entries.
+ return {compareResult: super.compare(expected)}
+ }
+
+ serialize () {
+ return [this.size, super.serialize()]
+ }
+}
+Object.defineProperty(MapValue.prototype, 'tag', { value: tag })
+
+class DescribedMapValue extends object.DescribedMixin(MapValue) {
+ createIterableRecursor () {
+ const size = this.size
+ if (size === 0) return recursorUtils.NOOP_RECURSOR
+
+ let index = 0
+ let entries
+ const next = () => {
+ if (index === size) return null
+
+ if (!entries) {
+ entries = Array.from(this.value)
+ }
+
+ const entry = entries[index++]
+ return this.describeMapEntry(this.describeAny(entry[0]), this.describeAny(entry[1]))
+ }
+
+ return { size, next }
+ }
+}
+
+class DeserializedMapValue extends object.DeserializedMixin(MapValue) {
+ constructor (state, recursor) {
+ super(state[1], recursor)
+ this.size = state[0]
+ }
+}
diff --git a/node_modules/concordance/lib/complexValues/object.js b/node_modules/concordance/lib/complexValues/object.js
new file mode 100644
index 000000000..97a59285c
--- /dev/null
+++ b/node_modules/concordance/lib/complexValues/object.js
@@ -0,0 +1,283 @@
+'use strict'
+
+const functionNameSupport = require('function-name-support')
+
+const constants = require('../constants')
+const getObjectKeys = require('../getObjectKeys')
+const ObjectFormatter = require('../formatUtils').ObjectFormatter
+const hasLength = require('../hasLength')
+const recursorUtils = require('../recursorUtils')
+const stats = require('../metaDescriptors/stats')
+
+const DEEP_EQUAL = constants.DEEP_EQUAL
+const SHALLOW_EQUAL = constants.SHALLOW_EQUAL
+const UNEQUAL = constants.UNEQUAL
+
+function describe (props) {
+ const isArray = props.stringTag === 'Array'
+ const object = props.value
+ return new DescribedObjectValue(Object.assign({
+ isArray,
+ isIterable: object[Symbol.iterator] !== undefined,
+ isList: isArray || hasLength(object)
+ }, props))
+}
+exports.describe = describe
+
+function deserialize (state, recursor) {
+ return new DeserializedObjectValue(state, recursor)
+}
+exports.deserialize = deserialize
+
+const tag = Symbol('ObjectValue')
+exports.tag = tag
+
+class ObjectValue {
+ constructor (props) {
+ this.ctor = props.ctor
+ this.pointer = props.pointer
+ this.stringTag = props.stringTag
+
+ this.isArray = props.isArray === true
+ this.isIterable = props.isIterable === true
+ this.isList = props.isList === true
+ }
+
+ compare (expected) {
+ if (this.tag !== expected.tag) return UNEQUAL
+ if (this.stringTag !== expected.stringTag || !this.hasSameCtor(expected)) return UNEQUAL
+ return SHALLOW_EQUAL
+ }
+
+ hasSameCtor (expected) {
+ return this.ctor === expected.ctor
+ }
+
+ formatShallow (theme, indent) {
+ return new ObjectFormatter(this, theme, indent)
+ }
+
+ serialize () {
+ return [
+ this.ctor, this.pointer, this.stringTag,
+ this.isArray, this.isIterable, this.isList,
+ functionNameSupport.bitFlags
+ ]
+ }
+}
+Object.defineProperty(ObjectValue.prototype, 'isComplex', { value: true })
+Object.defineProperty(ObjectValue.prototype, 'tag', { value: tag })
+exports.ObjectValue = ObjectValue
+
+const DescribedObjectValue = DescribedMixin(ObjectValue)
+const DeserializedObjectValue = DeserializedMixin(ObjectValue)
+
+function DescribedMixin (base) {
+ return class extends base {
+ constructor (props) {
+ super(props)
+
+ this.value = props.value
+ this.describeAny = props.describeAny
+ this.describeItem = props.describeItem
+ this.describeMapEntry = props.describeMapEntry
+ this.describeProperty = props.describeProperty
+
+ this.iterableState = null
+ this.listState = null
+ this.propertyState = null
+ }
+
+ compare (expected) {
+ return this.value === expected.value
+ ? DEEP_EQUAL
+ : super.compare(expected)
+ }
+
+ createPropertyRecursor () {
+ const objectKeys = getObjectKeys(this.value, this.isList ? this.value.length : 0)
+ const size = objectKeys.size
+ if (size === 0) return recursorUtils.NOOP_RECURSOR
+
+ let index = 0
+ const next = () => {
+ if (index === size) return null
+
+ const key = objectKeys.keys[index++]
+ return this.describeProperty(key, this.describeAny(this.value[key]))
+ }
+
+ return { size, next }
+ }
+
+ createListRecursor () {
+ if (!this.isList) return recursorUtils.NOOP_RECURSOR
+
+ const size = this.value.length
+ if (size === 0) return recursorUtils.NOOP_RECURSOR
+
+ let index = 0
+ const next = () => {
+ if (index === size) return null
+
+ const current = index
+ index++
+ return this.describeItem(current, this.describeAny(this.value[current]))
+ }
+
+ return { size, next }
+ }
+
+ createIterableRecursor () {
+ if (this.isArray || !this.isIterable) return recursorUtils.NOOP_RECURSOR
+
+ const iterator = this.value[Symbol.iterator]()
+ let first = iterator.next()
+
+ let done = false
+ let size = -1
+ if (first.done) {
+ if (first.value === undefined) {
+ size = 0
+ done = true
+ } else {
+ size = 1
+ }
+ }
+
+ let index = 0
+ const next = () => {
+ if (done) return null
+
+ while (!done) {
+ const current = first || iterator.next()
+ if (current === first) {
+ first = null
+ }
+ if (current.done) {
+ done = true
+ }
+
+ const item = current.value
+ if (done && item === undefined) return null
+
+ if (this.isList && this.value[index] === item) {
+ index++
+ } else {
+ return this.describeItem(index++, this.describeAny(item))
+ }
+ }
+ }
+
+ return { size, next }
+ }
+
+ createRecursor () {
+ let recursedProperty = false
+ let recursedList = false
+ let recursedIterable = false
+
+ let recursor = null
+ return () => {
+ let retval = null
+ do {
+ if (recursor !== null) {
+ retval = recursor.next()
+ if (retval === null) {
+ recursor = null
+ }
+ }
+
+ while (recursor === null && (!recursedList || !recursedProperty || !recursedIterable)) {
+ // Prioritize recursing lists
+ if (!recursedList) {
+ const replay = recursorUtils.replay(this.listState, () => this.createListRecursor())
+ this.listState = replay.state
+ recursor = replay.recursor
+ recursedList = true
+ if (recursor !== recursorUtils.NOOP_RECURSOR) {
+ retval = stats.describeListRecursor(recursor)
+ }
+ } else if (!recursedProperty) {
+ const replay = recursorUtils.replay(this.propertyState, () => this.createPropertyRecursor())
+ this.propertyState = replay.state
+ recursor = replay.recursor
+ recursedProperty = true
+ if (recursor !== recursorUtils.NOOP_RECURSOR) {
+ retval = stats.describePropertyRecursor(recursor)
+ }
+ } else if (!recursedIterable) {
+ const replay = recursorUtils.replay(this.iterableState, () => this.createIterableRecursor())
+ this.iterableState = replay.state
+ recursor = replay.recursor
+ recursedIterable = true
+ if (recursor !== recursorUtils.NOOP_RECURSOR) {
+ retval = stats.describeIterableRecursor(recursor)
+ }
+ }
+ }
+ } while (recursor !== null && retval === null)
+
+ return retval
+ }
+ }
+ }
+}
+exports.DescribedMixin = DescribedMixin
+
+function DeserializedMixin (base) {
+ return class extends base {
+ constructor (state, recursor) {
+ super({
+ ctor: state[0],
+ pointer: state[1],
+ stringTag: state[2],
+ isArray: state[3],
+ isIterable: state[4],
+ isList: state[5]
+ })
+
+ this.functionNameSupportFlags = state[6]
+ this.deserializedRecursor = recursor
+ this.replayState = null
+ }
+
+ createRecursor () {
+ if (!this.deserializedRecursor) return () => null
+
+ const replay = recursorUtils.replay(this.replayState, () => ({ size: -1, next: this.deserializedRecursor }))
+ this.replayState = replay.state
+ return replay.recursor.next
+ }
+
+ hasSameCtor (expected) {
+ if (this.ctor === expected.ctor) return true
+
+ if (this.functionNameSupportFlags === functionNameSupport.bitFlags) {
+ // The engine used to create the serialization supports the same
+ // function name inference as the current engine. That said, unless
+ // the engine has full support for name inference, it's possible that
+ // names were lost simply due to refactoring. Ctors are not the same
+ // only if the engine has full support, or if ctors were inferred.
+ if (functionNameSupport.hasFullSupport === true || (this.ctor !== null && expected.ctor !== null)) return false
+ } else if (functionNameSupport.isSubsetOf(this.functionNameSupportFlags)) {
+ // The engine used to create the serialization could infer more function
+ // names than the current engine. Assume `expected.ctor` comes from the
+ // current engine and treat the ctors as unequal only if the current
+ // engine could infer a ctor.
+ if (expected.ctor !== null) return false
+ } else {
+ /* istanbul ignore else */
+ if (functionNameSupport.isSupersetOf(this.functionNameSupportFlags)) {
+ // The engine used to create the serialization could infer fewer
+ // function names than the current engine. Treat the ctors as unequal
+ // only if a ctor was in the serialization.
+ if (this.ctor !== null) return false
+ }
+ }
+
+ return true
+ }
+ }
+}
+exports.DeserializedMixin = DeserializedMixin
diff --git a/node_modules/concordance/lib/complexValues/promise.js b/node_modules/concordance/lib/complexValues/promise.js
new file mode 100644
index 000000000..823d45386
--- /dev/null
+++ b/node_modules/concordance/lib/complexValues/promise.js
@@ -0,0 +1,40 @@
+'use strict'
+
+const constants = require('../constants')
+const object = require('./object')
+
+const DEEP_EQUAL = constants.DEEP_EQUAL
+const UNEQUAL = constants.UNEQUAL
+
+function describe (props) {
+ return new DescribedPromiseValue(props)
+}
+exports.describe = describe
+
+function deserialize (props) {
+ return new DeserializedPromiseValue(props)
+}
+exports.deserialize = deserialize
+
+const tag = Symbol('PromiseValue')
+exports.tag = tag
+
+class PromiseValue extends object.ObjectValue {}
+Object.defineProperty(PromiseValue.prototype, 'tag', { value: tag })
+
+class DescribedPromiseValue extends object.DescribedMixin(PromiseValue) {
+ compare (expected) {
+ // When comparing described promises, require them to be the exact same
+ // object.
+ return super.compare(expected) === DEEP_EQUAL
+ ? DEEP_EQUAL
+ : UNEQUAL
+ }
+}
+
+class DeserializedPromiseValue extends object.DeserializedMixin(PromiseValue) {
+ compare (expected) {
+ // Deserialized promises can never be compared using object references.
+ return super.compare(expected)
+ }
+}
diff --git a/node_modules/concordance/lib/complexValues/regexp.js b/node_modules/concordance/lib/complexValues/regexp.js
new file mode 100644
index 000000000..1c66a2fd8
--- /dev/null
+++ b/node_modules/concordance/lib/complexValues/regexp.js
@@ -0,0 +1,90 @@
+'use strict'
+
+const constants = require('../constants')
+const formatUtils = require('../formatUtils')
+const lineBuilder = require('../lineBuilder')
+const object = require('./object')
+
+const UNEQUAL = constants.UNEQUAL
+
+function describe (props) {
+ const regexp = props.value
+ return new DescribedRegexpValue(Object.assign({
+ flags: getSortedFlags(regexp),
+ source: regexp.source
+ }, props))
+}
+exports.describe = describe
+
+function deserialize (state, recursor) {
+ return new DeserializedRegexpValue(state, recursor)
+}
+exports.deserialize = deserialize
+
+const tag = Symbol('RegexpValue')
+exports.tag = tag
+
+function getSortedFlags (regexp) {
+ const flags = regexp.flags || String(regexp).slice(regexp.source.length + 2)
+ return flags.split('').sort().join('')
+}
+
+class RegexpValue extends object.ObjectValue {
+ constructor (props) {
+ super(props)
+ this.flags = props.flags
+ this.source = props.source
+ }
+
+ compare (expected) {
+ return this.tag === expected.tag && this.flags === expected.flags && this.source === expected.source
+ ? super.compare(expected)
+ : UNEQUAL
+ }
+
+ formatShallow (theme, indent) {
+ const ctor = this.ctor || this.stringTag
+ const regexp = formatUtils.wrap(theme.regexp.source, this.source) + formatUtils.wrap(theme.regexp.flags, this.flags)
+
+ return super.formatShallow(theme, indent).customize({
+ finalize: innerLines => {
+ if (ctor === 'RegExp' && innerLines.isEmpty) return lineBuilder.single(regexp)
+
+ const innerIndentation = indent.increase()
+ const header = lineBuilder.first(formatUtils.formatCtorAndStringTag(theme, this) + ' ' + theme.object.openBracket)
+ .concat(lineBuilder.line(innerIndentation + regexp))
+
+ if (!innerLines.isEmpty) {
+ header.append(lineBuilder.line(innerIndentation + theme.regexp.separator))
+ header.append(innerLines.withFirstPrefixed(innerIndentation).stripFlags())
+ }
+
+ return header.append(lineBuilder.last(indent + theme.object.closeBracket))
+ },
+
+ maxDepth: () => {
+ return lineBuilder.single(
+ formatUtils.formatCtorAndStringTag(theme, this) + ' ' +
+ theme.object.openBracket + ' ' +
+ regexp + ' ' +
+ theme.maxDepth + ' ' +
+ theme.object.closeBracket)
+ }
+ })
+ }
+
+ serialize () {
+ return [this.flags, this.source, super.serialize()]
+ }
+}
+Object.defineProperty(RegexpValue.prototype, 'tag', { value: tag })
+
+const DescribedRegexpValue = object.DescribedMixin(RegexpValue)
+
+class DeserializedRegexpValue extends object.DeserializedMixin(RegexpValue) {
+ constructor (state, recursor) {
+ super(state[2], recursor)
+ this.flags = state[0]
+ this.source = state[1]
+ }
+}
diff --git a/node_modules/concordance/lib/complexValues/set.js b/node_modules/concordance/lib/complexValues/set.js
new file mode 100644
index 000000000..fc3623ace
--- /dev/null
+++ b/node_modules/concordance/lib/complexValues/set.js
@@ -0,0 +1,78 @@
+'use strict'
+
+const constants = require('../constants')
+const recursorUtils = require('../recursorUtils')
+const object = require('./object')
+
+const SHALLOW_EQUAL = constants.SHALLOW_EQUAL
+const UNEQUAL = constants.UNEQUAL
+
+function describe (props) {
+ return new DescribedSetValue(Object.assign({
+ size: props.value.size
+ }, props))
+}
+exports.describe = describe
+
+function deserialize (state, recursor) {
+ return new DeserializedSetValue(state, recursor)
+}
+exports.deserialize = deserialize
+
+const tag = Symbol('SetValue')
+exports.tag = tag
+
+class SetValue extends object.ObjectValue {
+ constructor (props) {
+ super(props)
+ this.size = props.size
+ }
+
+ compare (expected) {
+ const result = super.compare(expected)
+ if (result !== SHALLOW_EQUAL) return result
+
+ return this.size === expected.size
+ ? SHALLOW_EQUAL
+ : UNEQUAL
+ }
+
+ prepareDiff (expected) {
+ // Sets should be compared, even if they have a different number of items.
+ return {compareResult: super.compare(expected)}
+ }
+
+ serialize () {
+ return [this.size, super.serialize()]
+ }
+}
+Object.defineProperty(SetValue.prototype, 'tag', { value: tag })
+
+class DescribedSetValue extends object.DescribedMixin(SetValue) {
+ createIterableRecursor () {
+ const size = this.size
+ if (size === 0) return recursorUtils.NOOP_RECURSOR
+
+ let index = 0
+ let members
+ const next = () => {
+ if (index === size) return null
+
+ if (!members) {
+ members = Array.from(this.value)
+ }
+
+ const value = members[index]
+ return this.describeItem(index++, this.describeAny(value))
+ }
+
+ return { size, next }
+ }
+}
+
+class DeserializedSetValue extends object.DeserializedMixin(SetValue) {
+ constructor (state, recursor) {
+ super(state[1], recursor)
+ this.size = state[0]
+ }
+}
diff --git a/node_modules/concordance/lib/complexValues/typedArray.js b/node_modules/concordance/lib/complexValues/typedArray.js
new file mode 100644
index 000000000..5c482ff1f
--- /dev/null
+++ b/node_modules/concordance/lib/complexValues/typedArray.js
@@ -0,0 +1,162 @@
+'use strict'
+
+const constants = require('../constants')
+const formatUtils = require('../formatUtils')
+const lineBuilder = require('../lineBuilder')
+const recursorUtils = require('../recursorUtils')
+const propertyStatsTag = require('../metaDescriptors/stats').propertyTag
+const object = require('./object')
+
+const DEEP_EQUAL = constants.DEEP_EQUAL
+const UNEQUAL = constants.UNEQUAL
+
+function getBuffer (value) {
+ // Assume at least Node.js 4.5.0, which introduces Buffer.from()
+ const buffer = Buffer.from(value.buffer)
+ return value.byteLength !== value.buffer.byteLength
+ ? buffer.slice(value.byteOffset, value.byteOffset + value.byteLength)
+ : buffer
+}
+exports.getBuffer = getBuffer
+
+function describe (props) {
+ return new DescribedTypedArrayValue(Object.assign({
+ buffer: getBuffer(props.value),
+ // Set isArray and isList so the property recursor excludes the byte accessors
+ isArray: true,
+ isList: true
+ }, props))
+}
+exports.describe = describe
+
+function deserialize (state, recursor) {
+ return new DeserializedTypedArrayValue(state, recursor)
+}
+exports.deserialize = deserialize
+
+function deserializeBytes (buffer) {
+ return new Bytes(buffer)
+}
+exports.deserializeBytes = deserializeBytes
+
+const bytesTag = Symbol('Bytes')
+exports.bytesTag = bytesTag
+
+const tag = Symbol('TypedArrayValue')
+exports.tag = tag
+
+class Bytes {
+ constructor (buffer) {
+ this.buffer = buffer
+ }
+
+ compare (expected) {
+ return expected.tag === bytesTag && this.buffer.equals(expected.buffer)
+ ? DEEP_EQUAL
+ : UNEQUAL
+ }
+
+ formatDeep (theme, indent) {
+ const indentation = indent
+ const lines = lineBuilder.buffer()
+
+ // Display 4-byte words, 8 per line
+ let string = ''
+ let isFirst = true
+ for (let offset = 0; offset < this.buffer.length; offset += 4) {
+ if (offset > 0) {
+ if (offset % 32 === 0) {
+ if (isFirst) {
+ lines.append(lineBuilder.first(string))
+ isFirst = false
+ } else {
+ lines.append(lineBuilder.line(string))
+ }
+ string = String(indentation)
+ } else {
+ string += ' '
+ }
+ }
+ string += formatUtils.wrap(theme.typedArray.bytes, this.buffer.toString('hex', offset, offset + 4))
+ }
+
+ return isFirst
+ ? lineBuilder.single(string)
+ : lines.append(lineBuilder.last(string))
+ }
+
+ serialize () {
+ return this.buffer
+ }
+}
+Object.defineProperty(Bytes.prototype, 'tag', { value: bytesTag })
+
+class TypedArrayValue extends object.ObjectValue {
+ constructor (props) {
+ super(props)
+ this.buffer = props.buffer
+ }
+
+ formatShallow (theme, indent) {
+ return super.formatShallow(theme, indent).customize({
+ shouldFormat (subject) {
+ if (subject.tag === propertyStatsTag) return subject.size > 1
+ if (subject.isProperty === true) return subject.key.value !== 'byteLength'
+ if (subject.tag === bytesTag) return subject.buffer.byteLength > 0
+ return true
+ }
+ })
+ }
+}
+Object.defineProperty(TypedArrayValue.prototype, 'tag', { value: tag })
+exports.TypedArrayValue = TypedArrayValue
+
+function DescribedMixin (base) {
+ return class extends object.DescribedMixin(base) {
+ // The list isn't recursed. Instead a Bytes instance is returned by the main
+ // recursor.
+ createListRecursor () {
+ return recursorUtils.NOOP_RECURSOR
+ }
+
+ createPropertyRecursor () {
+ const recursor = super.createPropertyRecursor()
+ const size = recursor.size + 1
+
+ let done = false
+ const next = () => {
+ if (done) return null
+
+ const property = recursor.next()
+ if (property) return property
+
+ done = true
+ return this.describeProperty('byteLength', this.describeAny(this.buffer.byteLength))
+ }
+
+ return { size, next }
+ }
+
+ createRecursor () {
+ return recursorUtils.unshift(super.createRecursor(), new Bytes(this.buffer))
+ }
+ }
+}
+exports.DescribedMixin = DescribedMixin
+
+const DescribedTypedArrayValue = DescribedMixin(TypedArrayValue)
+
+function DeserializedMixin (base) {
+ return class extends object.DeserializedMixin(base) {
+ constructor (state, recursor) {
+ super(state, recursor)
+
+ // Get the Bytes descriptor from the recursor. It contains the buffer.
+ const bytesDescriptor = this.createRecursor()()
+ this.buffer = bytesDescriptor.buffer
+ }
+ }
+}
+exports.DeserializedMixin = DeserializedMixin
+
+const DeserializedTypedArrayValue = DeserializedMixin(TypedArrayValue)
diff --git a/node_modules/concordance/lib/constants.js b/node_modules/concordance/lib/constants.js
new file mode 100644
index 000000000..f85235faa
--- /dev/null
+++ b/node_modules/concordance/lib/constants.js
@@ -0,0 +1,13 @@
+'use strict'
+
+const AMBIGUOUS = Symbol('AMBIGUOUS')
+const DEEP_EQUAL = Symbol('DEEP_EQUAL')
+const SHALLOW_EQUAL = Symbol('SHALLOW_EQUAL')
+const UNEQUAL = Symbol('UNEQUAL')
+
+module.exports = {
+ AMBIGUOUS,
+ DEEP_EQUAL,
+ SHALLOW_EQUAL,
+ UNEQUAL
+}
diff --git a/node_modules/concordance/lib/describe.js b/node_modules/concordance/lib/describe.js
new file mode 100644
index 000000000..a8aa94572
--- /dev/null
+++ b/node_modules/concordance/lib/describe.js
@@ -0,0 +1,170 @@
+'use strict'
+
+const argumentsValue = require('./complexValues/arguments')
+const arrayBufferValue = require('./complexValues/arrayBuffer')
+const boxedValue = require('./complexValues/boxed')
+const dataViewValue = require('./complexValues/dataView')
+const dateValue = require('./complexValues/date')
+const errorValue = require('./complexValues/error')
+const functionValue = require('./complexValues/function')
+const globalValue = require('./complexValues/global')
+const mapValue = require('./complexValues/map')
+const objectValue = require('./complexValues/object')
+const promiseValue = require('./complexValues/promise')
+const regexpValue = require('./complexValues/regexp')
+const setValue = require('./complexValues/set')
+const typedArrayValue = require('./complexValues/typedArray')
+
+const itemDescriptor = require('./metaDescriptors/item')
+const mapEntryDescriptor = require('./metaDescriptors/mapEntry')
+const propertyDescriptor = require('./metaDescriptors/property')
+
+const booleanValue = require('./primitiveValues/boolean')
+const nullValue = require('./primitiveValues/null')
+const numberValue = require('./primitiveValues/number')
+const stringValue = require('./primitiveValues/string')
+const symbolValue = require('./primitiveValues/symbol')
+const undefinedValue = require('./primitiveValues/undefined')
+
+const getCtor = require('./getCtor')
+const getStringTag = require('./getStringTag')
+const pluginRegistry = require('./pluginRegistry')
+const Registry = require('./Registry')
+
+const SpecializedComplexes = new Map([
+ ['Arguments', argumentsValue.describe],
+ ['ArrayBuffer', arrayBufferValue.describe],
+ ['DataView', dataViewValue.describe],
+ ['Date', dateValue.describe],
+ ['Error', errorValue.describe],
+ ['Float32Array', typedArrayValue.describe],
+ ['Float64Array', typedArrayValue.describe],
+ ['Function', functionValue.describe],
+ ['GeneratorFunction', functionValue.describe],
+ ['global', globalValue.describe],
+ ['Int16Array', typedArrayValue.describe],
+ ['Int32Array', typedArrayValue.describe],
+ ['Int8Array', typedArrayValue.describe],
+ ['Map', mapValue.describe],
+ ['Promise', promiseValue.describe],
+ ['RegExp', regexpValue.describe],
+ ['Set', setValue.describe],
+ ['Uint16Array', typedArrayValue.describe],
+ ['Uint32Array', typedArrayValue.describe],
+ ['Uint8Array', typedArrayValue.describe],
+ ['Uint8ClampedArray', typedArrayValue.describe]
+])
+
+function describePrimitive (value) {
+ if (value === null) return nullValue.describe()
+ if (value === undefined) return undefinedValue.describe()
+ if (value === true || value === false) return booleanValue.describe(value)
+
+ const type = typeof value
+ if (type === 'number') return numberValue.describe(value)
+ if (type === 'string') return stringValue.describe(value)
+ if (type === 'symbol') return symbolValue.describe(value)
+
+ return null
+}
+
+function unboxComplex (tag, complex) {
+ // Try to unbox by calling `valueOf()`. `describePrimitive()` will return
+ // `null` if the resulting value is not a primitive, in which case it's
+ // ignored.
+ if (typeof complex.valueOf === 'function') {
+ const value = complex.valueOf()
+ if (value !== complex) return describePrimitive(value)
+ }
+
+ return null
+}
+
+function registerPlugins (plugins) {
+ if (!Array.isArray(plugins) || plugins.length === 0) return () => null
+
+ const tryFns = pluginRegistry.getTryDescribeValues(plugins)
+ return (value, stringTag, ctor) => {
+ for (const tryDescribeValue of tryFns) {
+ const describeValue = tryDescribeValue(value, stringTag, ctor)
+ if (describeValue) return describeValue
+ }
+
+ return null
+ }
+}
+
+function describeComplex (value, registry, tryPlugins, describeAny, describeItem, describeMapEntry, describeProperty) {
+ if (registry.has(value)) return registry.get(value)
+
+ const stringTag = getStringTag(value)
+ const ctor = getCtor(stringTag, value)
+ const pointer = registry.alloc(value)
+
+ let unboxed
+ let describeValue = tryPlugins(value, stringTag, ctor)
+ if (describeValue === null) {
+ if (SpecializedComplexes.has(stringTag)) {
+ describeValue = SpecializedComplexes.get(stringTag)
+ } else {
+ unboxed = unboxComplex(stringTag, value)
+ if (unboxed !== null) {
+ describeValue = boxedValue.describe
+ } else {
+ describeValue = objectValue.describe
+ }
+ }
+ }
+
+ const descriptor = describeValue({
+ ctor,
+ describeAny,
+ describeItem,
+ describeMapEntry,
+ describeProperty,
+ pointer: pointer.index,
+ stringTag,
+ unboxed,
+ value
+ })
+ pointer.descriptor = descriptor
+ return descriptor
+}
+
+function describe (value, options) {
+ const primitive = describePrimitive(value)
+ if (primitive !== null) return primitive
+
+ const registry = new Registry()
+ const tryPlugins = registerPlugins(options && options.plugins)
+ const curriedComplex = c => {
+ return describeComplex(c, registry, tryPlugins, describeAny, describeItem, describeMapEntry, describeProperty)
+ }
+
+ const describeAny = any => {
+ const descriptor = describePrimitive(any)
+ return descriptor !== null
+ ? descriptor
+ : curriedComplex(any)
+ }
+
+ const describeItem = (index, valueDescriptor) => {
+ return valueDescriptor.isPrimitive === true
+ ? itemDescriptor.describePrimitive(index, valueDescriptor)
+ : itemDescriptor.describeComplex(index, valueDescriptor)
+ }
+
+ const describeMapEntry = (keyDescriptor, valueDescriptor) => {
+ return mapEntryDescriptor.describe(keyDescriptor, valueDescriptor)
+ }
+
+ const describeProperty = (key, valueDescriptor) => {
+ const keyDescriptor = describePrimitive(key)
+ return valueDescriptor.isPrimitive === true
+ ? propertyDescriptor.describePrimitive(keyDescriptor, valueDescriptor)
+ : propertyDescriptor.describeComplex(keyDescriptor, valueDescriptor)
+ }
+
+ return curriedComplex(value)
+}
+module.exports = describe
diff --git a/node_modules/concordance/lib/diff.js b/node_modules/concordance/lib/diff.js
new file mode 100644
index 000000000..16191143f
--- /dev/null
+++ b/node_modules/concordance/lib/diff.js
@@ -0,0 +1,391 @@
+'use strict'
+
+const constants = require('./constants')
+const describe = require('./describe')
+const lineBuilder = require('./lineBuilder')
+const recursorUtils = require('./recursorUtils')
+const shouldCompareDeep = require('./shouldCompareDeep')
+const symbolProperties = require('./symbolProperties')
+const themeUtils = require('./themeUtils')
+const Circular = require('./Circular')
+const Indenter = require('./Indenter')
+
+const AMBIGUOUS = constants.AMBIGUOUS
+const DEEP_EQUAL = constants.DEEP_EQUAL
+const UNEQUAL = constants.UNEQUAL
+const SHALLOW_EQUAL = constants.SHALLOW_EQUAL
+const NOOP = Symbol('NOOP')
+
+const alwaysFormat = () => true
+
+function compareComplexShape (lhs, rhs) {
+ let result = lhs.compare(rhs)
+ if (result === DEEP_EQUAL) return DEEP_EQUAL
+ if (result === UNEQUAL || !shouldCompareDeep(result, lhs, rhs)) return UNEQUAL
+
+ let collectedSymbolProperties = false
+ let lhsRecursor = lhs.createRecursor()
+ let rhsRecursor = rhs.createRecursor()
+
+ do {
+ lhs = lhsRecursor()
+ rhs = rhsRecursor()
+
+ if (lhs === null && rhs === null) return SHALLOW_EQUAL
+ if (lhs === null || rhs === null) return UNEQUAL
+
+ result = lhs.compare(rhs)
+ if (result === UNEQUAL) return UNEQUAL
+ if (
+ result === AMBIGUOUS &&
+ lhs.isProperty === true && !collectedSymbolProperties &&
+ shouldCompareDeep(result, lhs, rhs)
+ ) {
+ collectedSymbolProperties = true
+ const lhsCollector = new symbolProperties.Collector(lhs, lhsRecursor)
+ const rhsCollector = new symbolProperties.Collector(rhs, rhsRecursor)
+
+ lhsRecursor = recursorUtils.sequence(
+ lhsCollector.createRecursor(),
+ recursorUtils.unshift(lhsRecursor, lhsCollector.collectAll()))
+ rhsRecursor = recursorUtils.sequence(
+ rhsCollector.createRecursor(),
+ recursorUtils.unshift(rhsRecursor, rhsCollector.collectAll()))
+ }
+ } while (true)
+}
+
+function diffDescriptors (lhs, rhs, options) {
+ const theme = themeUtils.normalize(options)
+ const invert = options ? options.invert === true : false
+
+ const lhsCircular = new Circular()
+ const rhsCircular = new Circular()
+ const maxDepth = (options && options.maxDepth) || 0
+
+ let indent = new Indenter(0, ' ')
+
+ const lhsStack = []
+ const rhsStack = []
+ let topIndex = -1
+
+ const buffer = lineBuilder.buffer()
+ const diffStack = []
+ let diffIndex = -1
+
+ const isCircular = descriptor => lhsCircular.has(descriptor) || rhsCircular.has(descriptor)
+
+ const format = (builder, subject, circular) => {
+ if (diffIndex >= 0 && !diffStack[diffIndex].shouldFormat(subject)) return
+
+ if (circular.has(subject)) {
+ diffStack[diffIndex].formatter.append(builder.single(theme.circular))
+ return
+ }
+
+ const formatStack = []
+ let formatIndex = -1
+
+ do {
+ if (circular.has(subject)) {
+ formatStack[formatIndex].formatter.append(builder.single(theme.circular), subject)
+ } else {
+ let didFormat = false
+ if (typeof subject.formatDeep === 'function') {
+ let formatted = subject.formatDeep(themeUtils.applyModifiers(subject, theme), indent)
+ if (formatted !== null) {
+ didFormat = true
+
+ if (formatIndex === -1) {
+ formatted = builder.setDefaultGutter(formatted)
+ if (diffIndex === -1) {
+ buffer.append(formatted)
+ } else {
+ diffStack[diffIndex].formatter.append(formatted, subject)
+ }
+ } else {
+ formatStack[formatIndex].formatter.append(formatted, subject)
+ }
+ }
+ }
+
+ if (!didFormat && typeof subject.formatShallow === 'function') {
+ const formatter = subject.formatShallow(themeUtils.applyModifiers(subject, theme), indent)
+ const recursor = subject.createRecursor()
+
+ if (formatter.increaseIndent && maxDepth > 0 && indent.level === maxDepth) {
+ const isEmpty = recursor() === null
+ let formatted = !isEmpty && typeof formatter.maxDepth === 'function'
+ ? formatter.maxDepth()
+ : formatter.finalize()
+
+ if (formatIndex === -1) {
+ formatted = builder.setDefaultGutter(formatted)
+ diffStack[diffIndex].formatter.append(formatted, subject)
+ } else {
+ formatStack[formatIndex].formatter.append(formatted, subject)
+ }
+ } else {
+ formatStack.push({
+ formatter,
+ recursor,
+ decreaseIndent: formatter.increaseIndent,
+ shouldFormat: formatter.shouldFormat || alwaysFormat,
+ subject
+ })
+ formatIndex++
+
+ if (formatter.increaseIndent) indent = indent.increase()
+ circular.add(subject)
+ }
+ }
+ }
+
+ while (formatIndex >= 0) {
+ do {
+ subject = formatStack[formatIndex].recursor()
+ } while (subject && !formatStack[formatIndex].shouldFormat(subject))
+
+ if (subject) {
+ break
+ }
+
+ const record = formatStack.pop()
+ formatIndex--
+ if (record.decreaseIndent) indent = indent.decrease()
+ circular.delete(record.subject)
+
+ let formatted = record.formatter.finalize()
+ if (formatIndex === -1) {
+ formatted = builder.setDefaultGutter(formatted)
+ if (diffIndex === -1) {
+ buffer.append(formatted)
+ } else {
+ diffStack[diffIndex].formatter.append(formatted, record.subject)
+ }
+ } else {
+ formatStack[formatIndex].formatter.append(formatted, record.subject)
+ }
+ }
+ } while (formatIndex >= 0)
+ }
+
+ do {
+ let compareResult = NOOP
+ if (lhsCircular.has(lhs)) {
+ compareResult = lhsCircular.get(lhs) === rhsCircular.get(rhs)
+ ? DEEP_EQUAL
+ : UNEQUAL
+ } else if (rhsCircular.has(rhs)) {
+ compareResult = UNEQUAL
+ }
+
+ let firstPassSymbolProperty = false
+ if (lhs.isProperty === true) {
+ compareResult = lhs.compare(rhs)
+ if (compareResult === AMBIGUOUS) {
+ const parent = lhsStack[topIndex].subject
+ firstPassSymbolProperty = parent.isSymbolPropertiesCollector !== true && parent.isSymbolPropertiesComparable !== true
+ }
+ }
+
+ let didFormat = false
+ let mustRecurse = false
+ if (compareResult !== DEEP_EQUAL && !firstPassSymbolProperty && typeof lhs.prepareDiff === 'function') {
+ const lhsRecursor = topIndex === -1 ? null : lhsStack[topIndex].recursor
+ const rhsRecursor = topIndex === -1 ? null : rhsStack[topIndex].recursor
+
+ const instructions = lhs.prepareDiff(
+ rhs,
+ lhsRecursor,
+ rhsRecursor,
+ compareComplexShape,
+ isCircular)
+
+ if (instructions !== null) {
+ if (topIndex >= 0) {
+ if (typeof instructions.lhsRecursor === 'function') {
+ lhsStack[topIndex].recursor = instructions.lhsRecursor
+ }
+ if (typeof instructions.rhsRecursor === 'function') {
+ rhsStack[topIndex].recursor = instructions.rhsRecursor
+ }
+ }
+
+ if (instructions.compareResult) {
+ compareResult = instructions.compareResult
+ }
+ if (instructions.mustRecurse === true) {
+ mustRecurse = true
+ } else {
+ if (instructions.actualIsExtraneous === true) {
+ format(lineBuilder.actual, lhs, lhsCircular)
+ didFormat = true
+ } else if (instructions.multipleAreExtraneous === true) {
+ for (const extraneous of instructions.descriptors) {
+ format(lineBuilder.actual, extraneous, lhsCircular)
+ }
+ didFormat = true
+ } else if (instructions.expectedIsMissing === true) {
+ format(lineBuilder.expected, rhs, rhsCircular)
+ didFormat = true
+ } else if (instructions.multipleAreMissing === true) {
+ for (const missing of instructions.descriptors) {
+ format(lineBuilder.expected, missing, rhsCircular)
+ }
+ didFormat = true
+ } else if (instructions.isUnequal === true) {
+ format(lineBuilder.actual, lhs, lhsCircular)
+ format(lineBuilder.expected, rhs, rhsCircular)
+ didFormat = true
+ } else if (!instructions.compareResult) {
+ // TODO: Throw a useful, custom error
+ throw new Error('Illegal result of prepareDiff()')
+ }
+ }
+ }
+ }
+
+ if (!didFormat) {
+ if (compareResult === NOOP) {
+ compareResult = lhs.compare(rhs)
+ }
+
+ if (!mustRecurse) {
+ mustRecurse = shouldCompareDeep(compareResult, lhs, rhs)
+ }
+
+ if (compareResult === DEEP_EQUAL) {
+ format(lineBuilder, lhs, lhsCircular)
+ } else if (mustRecurse) {
+ if (compareResult === AMBIGUOUS && lhs.isProperty === true) {
+ // Replace both sides by a pseudo-descriptor which collects symbol
+ // properties instead.
+ lhs = new symbolProperties.Collector(lhs, lhsStack[topIndex].recursor)
+ rhs = new symbolProperties.Collector(rhs, rhsStack[topIndex].recursor)
+ // Replace the current recursors so they can continue correctly after
+ // the collectors have been "compared". This is necessary since the
+ // collectors eat the first value after the last symbol property.
+ lhsStack[topIndex].recursor = recursorUtils.unshift(lhsStack[topIndex].recursor, lhs.collectAll())
+ rhsStack[topIndex].recursor = recursorUtils.unshift(rhsStack[topIndex].recursor, rhs.collectAll())
+ }
+
+ if (typeof lhs.diffShallow === 'function') {
+ const formatter = lhs.diffShallow(rhs, themeUtils.applyModifiers(lhs, theme), indent)
+ diffStack.push({
+ formatter,
+ origin: lhs,
+ decreaseIndent: formatter.increaseIndent,
+ exceedsMaxDepth: formatter.increaseIndent && maxDepth > 0 && indent.level >= maxDepth,
+ shouldFormat: formatter.shouldFormat || alwaysFormat
+ })
+ diffIndex++
+
+ if (formatter.increaseIndent) indent = indent.increase()
+ } else if (typeof lhs.formatShallow === 'function') {
+ const formatter = lhs.formatShallow(themeUtils.applyModifiers(lhs, theme), indent)
+ diffStack.push({
+ formatter,
+ decreaseIndent: formatter.increaseIndent,
+ exceedsMaxDepth: formatter.increaseIndent && maxDepth > 0 && indent.level >= maxDepth,
+ shouldFormat: formatter.shouldFormat || alwaysFormat,
+ subject: lhs
+ })
+ diffIndex++
+
+ if (formatter.increaseIndent) indent = indent.increase()
+ }
+
+ lhsCircular.add(lhs)
+ rhsCircular.add(rhs)
+
+ lhsStack.push({ diffIndex, subject: lhs, recursor: lhs.createRecursor() })
+ rhsStack.push({ diffIndex, subject: rhs, recursor: rhs.createRecursor() })
+ topIndex++
+ } else {
+ const diffed = typeof lhs.diffDeep === 'function'
+ ? lhs.diffDeep(rhs, themeUtils.applyModifiers(lhs, theme), indent)
+ : null
+
+ if (diffed === null) {
+ format(lineBuilder.actual, lhs, lhsCircular)
+ format(lineBuilder.expected, rhs, rhsCircular)
+ } else {
+ if (diffIndex === -1) {
+ buffer.append(diffed)
+ } else {
+ diffStack[diffIndex].formatter.append(diffed, lhs)
+ }
+ }
+ }
+ }
+
+ while (topIndex >= 0) {
+ lhs = lhsStack[topIndex].recursor()
+ rhs = rhsStack[topIndex].recursor()
+
+ if (lhs !== null && rhs !== null) {
+ break
+ }
+
+ if (lhs === null && rhs === null) {
+ const lhsRecord = lhsStack.pop()
+ const rhsRecord = rhsStack.pop()
+ lhsCircular.delete(lhsRecord.subject)
+ rhsCircular.delete(rhsRecord.subject)
+ topIndex--
+
+ if (lhsRecord.diffIndex === diffIndex) {
+ const record = diffStack.pop()
+ diffIndex--
+ if (record.decreaseIndent) indent = indent.decrease()
+
+ let formatted = record.formatter.finalize()
+ if (record.exceedsMaxDepth && !formatted.hasGutter) {
+ // The record exceeds the max depth, but contains no actual diff.
+ // Discard the potentially deep formatting and format just the
+ // original subject.
+ const subject = lhsRecord.subject
+ const formatter = subject.formatShallow(themeUtils.applyModifiers(subject, theme), indent)
+ const isEmpty = subject.createRecursor()() === null
+ formatted = !isEmpty && typeof formatter.maxDepth === 'function'
+ ? formatter.maxDepth()
+ : formatter.finalize()
+ }
+
+ if (diffIndex === -1) {
+ buffer.append(formatted)
+ } else {
+ diffStack[diffIndex].formatter.append(formatted, record.subject)
+ }
+ }
+ } else {
+ let builder, circular, stack, subject
+ if (lhs === null) {
+ builder = lineBuilder.expected
+ circular = rhsCircular
+ stack = rhsStack
+ subject = rhs
+ } else {
+ builder = lineBuilder.actual
+ circular = lhsCircular
+ stack = lhsStack
+ subject = lhs
+ }
+
+ do {
+ format(builder, subject, circular)
+ subject = stack[topIndex].recursor()
+ } while (subject !== null)
+ }
+ }
+ } while (topIndex >= 0)
+
+ return buffer.toString({diff: true, invert, theme})
+}
+exports.diffDescriptors = diffDescriptors
+
+function diff (actual, expected, options) {
+ return diffDescriptors(describe(actual, options), describe(expected, options), options)
+}
+exports.diff = diff
diff --git a/node_modules/concordance/lib/encoder.js b/node_modules/concordance/lib/encoder.js
new file mode 100644
index 000000000..ea1be3855
--- /dev/null
+++ b/node_modules/concordance/lib/encoder.js
@@ -0,0 +1,293 @@
+'use strict'
+
+const flattenDeep = require('lodash.flattendeep')
+
+// Indexes are hexadecimal to make reading the binary output easier.
+const valueTypes = {
+ zero: 0x00,
+ int8: 0x01, // Note that the hex value equals the number of bytes required
+ int16: 0x02, // to store the integer.
+ int24: 0x03,
+ int32: 0x04,
+ int40: 0x05,
+ int48: 0x06,
+ // Leave room for int56 and int64
+ numberString: 0x09,
+ negativeZero: 0x0A,
+ notANumber: 0x0B,
+ infinity: 0x0C,
+ negativeInfinity: 0x0D,
+ undefined: 0x0E,
+ null: 0x0F,
+ true: 0x10,
+ false: 0x11,
+ utf8: 0x12,
+ bytes: 0x13,
+ list: 0x14,
+ descriptor: 0x15
+}
+
+const descriptorSymbol = Symbol('descriptor')
+exports.descriptorSymbol = descriptorSymbol
+
+function encodeInteger (type, value) {
+ const encoded = Buffer.alloc(type)
+ encoded.writeIntLE(value, 0, type)
+ return [type, encoded]
+}
+
+function encodeValue (value) {
+ if (Object.is(value, 0)) return valueTypes.zero
+ if (Object.is(value, -0)) return valueTypes.negativeZero
+ if (Object.is(value, NaN)) return valueTypes.notANumber
+ if (value === Infinity) return valueTypes.infinity
+ if (value === -Infinity) return valueTypes.negativeInfinity
+ if (value === undefined) return valueTypes.undefined
+ if (value === null) return valueTypes.null
+ if (value === true) return valueTypes.true
+ if (value === false) return valueTypes.false
+
+ const type = typeof value
+ if (type === 'number') {
+ if (Number.isInteger(value)) {
+ // The integer types are signed, so int8 can only store 7 bits, int16
+ // only 15, etc.
+ if (value >= -0x80 && value < 0x80) return encodeInteger(valueTypes.int8, value)
+ if (value >= -0x8000 && value < 0x8000) return encodeInteger(valueTypes.int16, value)
+ if (value >= -0x800000 && value < 0x800000) return encodeInteger(valueTypes.int24, value)
+ if (value >= -0x80000000 && value < 0x80000000) return encodeInteger(valueTypes.int32, value)
+ if (value >= -0x8000000000 && value < 0x8000000000) return encodeInteger(valueTypes.int40, value)
+ if (value >= -0x800000000000 && value < 0x800000000000) return encodeInteger(valueTypes.int48, value)
+ // Fall through to encoding the value as a number string.
+ }
+
+ const encoded = Buffer.from(String(value), 'utf8')
+ return [valueTypes.numberString, encodeValue(encoded.length), encoded]
+ }
+
+ if (type === 'string') {
+ const encoded = Buffer.from(value, 'utf8')
+ return [valueTypes.utf8, encodeValue(encoded.length), encoded]
+ }
+
+ if (Buffer.isBuffer(value)) {
+ return [valueTypes.bytes, encodeValue(value.byteLength), value]
+ }
+
+ if (Array.isArray(value)) {
+ return [
+ value[descriptorSymbol] === true ? valueTypes.descriptor : valueTypes.list,
+ encodeValue(value.length),
+ value.map(encodeValue)
+ ]
+ }
+
+ const hex = `0x${type.toString(16).toUpperCase()}`
+ throw new TypeError(`Unexpected value with type ${hex}`)
+}
+
+function decodeValue (buffer, byteOffset) {
+ const type = buffer.readUInt8(byteOffset)
+ byteOffset += 1
+
+ if (type === valueTypes.zero) return { byteOffset, value: 0 }
+ if (type === valueTypes.negativeZero) return { byteOffset, value: -0 }
+ if (type === valueTypes.notANumber) return { byteOffset, value: NaN }
+ if (type === valueTypes.infinity) return { byteOffset, value: Infinity }
+ if (type === valueTypes.negativeInfinity) return { byteOffset, value: -Infinity }
+ if (type === valueTypes.undefined) return { byteOffset, value: undefined }
+ if (type === valueTypes.null) return { byteOffset, value: null }
+ if (type === valueTypes.true) return { byteOffset, value: true }
+ if (type === valueTypes.false) return { byteOffset, value: false }
+
+ if (
+ type === valueTypes.int8 || type === valueTypes.int16 || type === valueTypes.int24 ||
+ type === valueTypes.int32 || type === valueTypes.int40 || type === valueTypes.int48
+ ) {
+ const value = buffer.readIntLE(byteOffset, type)
+ byteOffset += type
+ return { byteOffset, value }
+ }
+
+ if (type === valueTypes.numberString || type === valueTypes.utf8 || type === valueTypes.bytes) {
+ const length = decodeValue(buffer, byteOffset)
+ const start = length.byteOffset
+ const end = start + length.value
+
+ if (type === valueTypes.numberString) {
+ const value = Number(buffer.toString('utf8', start, end))
+ return { byteOffset: end, value }
+ }
+
+ if (type === valueTypes.utf8) {
+ const value = buffer.toString('utf8', start, end)
+ return { byteOffset: end, value }
+ }
+
+ const value = buffer.slice(start, end)
+ return { byteOffset: end, value }
+ }
+
+ if (type === valueTypes.list || type === valueTypes.descriptor) {
+ const length = decodeValue(buffer, byteOffset)
+ byteOffset = length.byteOffset
+
+ const value = new Array(length.value)
+ if (type === valueTypes.descriptor) {
+ value[descriptorSymbol] = true
+ }
+
+ for (let index = 0; index < length.value; index++) {
+ const item = decodeValue(buffer, byteOffset)
+ byteOffset = item.byteOffset
+ value[index] = item.value
+ }
+
+ return { byteOffset, value }
+ }
+
+ const hex = `0x${type.toString(16).toUpperCase()}`
+ throw new TypeError(`Could not decode type ${hex}`)
+}
+
+function buildBuffer (numberOrArray) {
+ if (typeof numberOrArray === 'number') {
+ const byte = Buffer.alloc(1)
+ byte.writeUInt8(numberOrArray)
+ return byte
+ }
+
+ const array = flattenDeep(numberOrArray)
+ const buffers = new Array(array.length)
+ let byteLength = 0
+ for (let index = 0; index < array.length; index++) {
+ if (typeof array[index] === 'number') {
+ byteLength += 1
+ const byte = Buffer.alloc(1)
+ byte.writeUInt8(array[index])
+ buffers[index] = byte
+ } else {
+ byteLength += array[index].byteLength
+ buffers[index] = array[index]
+ }
+ }
+ return Buffer.concat(buffers, byteLength)
+}
+
+function encode (serializerVersion, rootRecord, usedPlugins) {
+ const buffers = []
+ let byteOffset = 0
+
+ const versionHeader = Buffer.alloc(2)
+ versionHeader.writeUInt16LE(serializerVersion)
+ buffers.push(versionHeader)
+ byteOffset += versionHeader.byteLength
+
+ const rootOffset = Buffer.alloc(4)
+ buffers.push(rootOffset)
+ byteOffset += rootOffset.byteLength
+
+ const numPlugins = buildBuffer(encodeValue(usedPlugins.size))
+ buffers.push(numPlugins)
+ byteOffset += numPlugins.byteLength
+
+ for (const name of usedPlugins.keys()) {
+ const plugin = usedPlugins.get(name)
+ const record = buildBuffer([
+ encodeValue(name),
+ encodeValue(plugin.serializerVersion)
+ ])
+ buffers.push(record)
+ byteOffset += record.byteLength
+ }
+
+ const queue = [rootRecord]
+ const pointers = [rootOffset]
+ while (queue.length > 0) {
+ pointers.shift().writeUInt32LE(byteOffset, 0)
+
+ const record = queue.shift()
+ const recordHeader = buildBuffer([
+ encodeValue(record.pluginIndex),
+ encodeValue(record.id),
+ encodeValue(record.children.length)
+ ])
+ buffers.push(recordHeader)
+ byteOffset += recordHeader.byteLength
+
+ // Add pointers before encoding the state. This allows, if it ever becomes
+ // necessary, for records to be extracted from a buffer without having to
+ // parse the (variable length) state field.
+ for (const child of record.children) {
+ queue.push(child)
+
+ const pointer = Buffer.alloc(4)
+ pointers.push(pointer)
+ buffers.push(pointer)
+ byteOffset += 4
+ }
+
+ const state = buildBuffer(encodeValue(record.state))
+ buffers.push(state)
+ byteOffset += state.byteLength
+ }
+
+ return Buffer.concat(buffers, byteOffset)
+}
+exports.encode = encode
+
+function decodePlugins (buffer) {
+ const $numPlugins = decodeValue(buffer, 0)
+ let byteOffset = $numPlugins.byteOffset
+
+ const usedPlugins = new Map()
+ const lastIndex = $numPlugins.value
+ for (let index = 1; index <= lastIndex; index++) {
+ const $name = decodeValue(buffer, byteOffset)
+ const name = $name.value
+ byteOffset = $name.byteOffset
+
+ const serializerVersion = decodeValue(buffer, byteOffset).value
+ usedPlugins.set(index, {name, serializerVersion})
+ }
+
+ return usedPlugins
+}
+exports.decodePlugins = decodePlugins
+
+function decodeRecord (buffer, byteOffset) {
+ const $pluginIndex = decodeValue(buffer, byteOffset)
+ const pluginIndex = $pluginIndex.value
+ byteOffset = $pluginIndex.byteOffset
+
+ const $id = decodeValue(buffer, byteOffset)
+ const id = $id.value
+ byteOffset = $id.byteOffset
+
+ const $numPointers = decodeValue(buffer, byteOffset)
+ const numPointers = $numPointers.value
+ byteOffset = $numPointers.byteOffset
+
+ const pointerAddresses = new Array(numPointers)
+ for (let index = 0; index < numPointers; index++) {
+ pointerAddresses[index] = buffer.readUInt32LE(byteOffset)
+ byteOffset += 4
+ }
+
+ const state = decodeValue(buffer, byteOffset).value
+ return {id, pluginIndex, state, pointerAddresses}
+}
+exports.decodeRecord = decodeRecord
+
+function extractVersion (buffer) {
+ return buffer.readUInt16LE(0)
+}
+exports.extractVersion = extractVersion
+
+function decode (buffer) {
+ const rootOffset = buffer.readUInt32LE(2)
+ const pluginBuffer = buffer.slice(6, rootOffset)
+ const rootRecord = decodeRecord(buffer, rootOffset)
+ return {pluginBuffer, rootRecord}
+}
+exports.decode = decode
diff --git a/node_modules/concordance/lib/format.js b/node_modules/concordance/lib/format.js
new file mode 100644
index 000000000..7629579d7
--- /dev/null
+++ b/node_modules/concordance/lib/format.js
@@ -0,0 +1,101 @@
+'use strict'
+
+const describe = require('./describe')
+const lineBuilder = require('./lineBuilder')
+const themeUtils = require('./themeUtils')
+const Circular = require('./Circular')
+const Indenter = require('./Indenter')
+
+const alwaysFormat = () => true
+const fixedIndent = new Indenter(0, ' ')
+
+function formatDescriptor (subject, options) {
+ const theme = themeUtils.normalize(options)
+ if (subject.isPrimitive === true) {
+ const formatted = subject.formatDeep(themeUtils.applyModifiers(subject, theme), fixedIndent)
+ return formatted.toString({diff: false})
+ }
+
+ const circular = new Circular()
+ const maxDepth = (options && options.maxDepth) || 0
+
+ let indent = fixedIndent
+
+ const buffer = lineBuilder.buffer()
+ const stack = []
+ let topIndex = -1
+
+ do {
+ if (circular.has(subject)) {
+ stack[topIndex].formatter.append(lineBuilder.single(theme.circular), subject)
+ } else {
+ let didFormat = false
+ if (typeof subject.formatDeep === 'function') {
+ const formatted = subject.formatDeep(themeUtils.applyModifiers(subject, theme), indent)
+ if (formatted !== null) {
+ didFormat = true
+ if (topIndex === -1) {
+ buffer.append(formatted)
+ } else {
+ stack[topIndex].formatter.append(formatted, subject)
+ }
+ }
+ }
+
+ if (!didFormat && typeof subject.formatShallow === 'function') {
+ const formatter = subject.formatShallow(themeUtils.applyModifiers(subject, theme), indent)
+ const recursor = subject.createRecursor()
+
+ if (formatter.increaseIndent && maxDepth > 0 && indent.level === maxDepth) {
+ const isEmpty = recursor() === null
+ const formatted = !isEmpty && typeof formatter.maxDepth === 'function'
+ ? formatter.maxDepth()
+ : formatter.finalize()
+ stack[topIndex].formatter.append(formatted, subject)
+ } else {
+ stack.push({
+ formatter,
+ recursor,
+ decreaseIndent: formatter.increaseIndent,
+ shouldFormat: formatter.shouldFormat || alwaysFormat,
+ subject
+ })
+ topIndex++
+
+ if (formatter.increaseIndent) indent = indent.increase()
+ circular.add(subject)
+ }
+ }
+ }
+
+ while (topIndex >= 0) {
+ do {
+ subject = stack[topIndex].recursor()
+ } while (subject && !stack[topIndex].shouldFormat(subject))
+
+ if (subject) {
+ break
+ }
+
+ const record = stack.pop()
+ topIndex--
+ if (record.decreaseIndent) indent = indent.decrease()
+ circular.delete(record.subject)
+
+ const formatted = record.formatter.finalize()
+ if (topIndex === -1) {
+ buffer.append(formatted)
+ } else {
+ stack[topIndex].formatter.append(formatted, record.subject)
+ }
+ }
+ } while (topIndex >= 0)
+
+ return buffer.toString({diff: false})
+}
+exports.formatDescriptor = formatDescriptor
+
+function format (value, options) {
+ return formatDescriptor(describe(value, options), options)
+}
+exports.format = format
diff --git a/node_modules/concordance/lib/formatUtils.js b/node_modules/concordance/lib/formatUtils.js
new file mode 100644
index 000000000..07e8b2c57
--- /dev/null
+++ b/node_modules/concordance/lib/formatUtils.js
@@ -0,0 +1,123 @@
+'use strict'
+
+const lineBuilder = require('./lineBuilder')
+
+function wrap (fromTheme, value) {
+ return fromTheme.open + value + fromTheme.close
+}
+exports.wrap = wrap
+
+function formatCtorAndStringTag (theme, object) {
+ if (!object.ctor) return wrap(theme.object.stringTag, object.stringTag)
+
+ let retval = wrap(theme.object.ctor, object.ctor)
+ if (object.stringTag && object.stringTag !== object.ctor && object.stringTag !== 'Object') {
+ retval += ' ' + wrap(theme.object.secondaryStringTag, object.stringTag)
+ }
+ return retval
+}
+exports.formatCtorAndStringTag = formatCtorAndStringTag
+
+class ObjectFormatter {
+ constructor (object, theme, indent) {
+ this.object = object
+ this.theme = theme
+ this.indent = indent
+
+ this.increaseIndent = true
+
+ this.innerLines = lineBuilder.buffer()
+ this.pendingStats = null
+ }
+
+ append (formatted, origin) {
+ if (origin.isStats === true) {
+ this.pendingStats = formatted
+ } else {
+ if (this.pendingStats !== null) {
+ if (!this.innerLines.isEmpty) {
+ this.innerLines.append(this.pendingStats)
+ }
+ this.pendingStats = null
+ }
+ this.innerLines.append(formatted)
+ }
+ }
+
+ finalize () {
+ const variant = this.object.isList
+ ? this.theme.list
+ : this.theme.object
+
+ const ctor = this.object.ctor
+ const stringTag = this.object.stringTag
+ const prefix = (ctor === 'Array' || ctor === 'Object') && ctor === stringTag
+ ? ''
+ : formatCtorAndStringTag(this.theme, this.object) + ' '
+
+ if (this.innerLines.isEmpty) {
+ return lineBuilder.single(prefix + variant.openBracket + variant.closeBracket)
+ }
+
+ return lineBuilder.first(prefix + variant.openBracket)
+ .concat(this.innerLines.withFirstPrefixed(this.indent.increase()).stripFlags())
+ .append(lineBuilder.last(this.indent + variant.closeBracket))
+ }
+
+ maxDepth () {
+ const variant = this.object.isList
+ ? this.theme.list
+ : this.theme.object
+
+ return lineBuilder.single(
+ formatCtorAndStringTag(this.theme, this.object) + ' ' + variant.openBracket +
+ ' ' + this.theme.maxDepth + ' ' + variant.closeBracket)
+ }
+
+ shouldFormat () {
+ return true
+ }
+
+ customize (methods) {
+ if (methods.finalize) {
+ this.finalize = () => methods.finalize(this.innerLines)
+ }
+ if (methods.maxDepth) {
+ this.maxDepth = methods.maxDepth
+ }
+ if (methods.shouldFormat) {
+ this.shouldFormat = methods.shouldFormat
+ }
+
+ return this
+ }
+}
+exports.ObjectFormatter = ObjectFormatter
+
+class SingleValueFormatter {
+ constructor (theme, finalizeFn, increaseIndent) {
+ this.theme = theme
+ this.finalizeFn = finalizeFn
+ this.hasValue = false
+ this.increaseIndent = increaseIndent === true
+ this.value = null
+ }
+
+ append (formatted) {
+ if (this.hasValue) throw new Error('Formatter buffer can only take one formatted value.')
+
+ this.hasValue = true
+ this.value = formatted
+ }
+
+ finalize () {
+ if (!this.hasValue) throw new Error('Formatter buffer never received a formatted value.')
+
+ return this.finalizeFn(this.value)
+ }
+
+ maxDepth () {
+ return this.finalizeFn(lineBuilder.single(this.theme.maxDepth))
+ }
+}
+exports.SingleValueFormatter = SingleValueFormatter
diff --git a/node_modules/concordance/lib/getCtor.js b/node_modules/concordance/lib/getCtor.js
new file mode 100644
index 000000000..4bbdfd158
--- /dev/null
+++ b/node_modules/concordance/lib/getCtor.js
@@ -0,0 +1,43 @@
+'use strict'
+
+const hop = Object.prototype.hasOwnProperty
+
+function getCtor (stringTag, value) {
+ if (value.constructor) {
+ const name = value.constructor.name
+ return typeof name === 'string' && name !== ''
+ ? name
+ : null
+ }
+
+ if (value.constructor === undefined) {
+ if (stringTag !== 'Object' || value instanceof Object) return null
+
+ // Values without a constructor, that do not inherit from `Object`, but are
+ // tagged as objects, may come from `Object.create(null)`. Or they can come
+ // from a different realm, e.g.:
+ //
+ // ```
+ // require('vm').runInNewContext(`
+ // const Foo = function () {}
+ // Foo.prototype.constructor = undefined
+ // return new Foo()
+ // `)
+ // ```
+ //
+ // Treat such objects as if they came from `Object.create(null)` (in the
+ // current realm) only if they do not have inherited properties. This allows
+ // these objects to be compared with object literals.
+ //
+ // This means `Object.create(null)` is not differentiated from `{}`.
+
+ // Using `const` prevents Crankshaft optimizations
+ for (var p in value) { // eslint-disable-line no-var
+ if (!hop.call(value, p)) return null
+ }
+ return stringTag
+ }
+
+ return null
+}
+module.exports = getCtor
diff --git a/node_modules/concordance/lib/getObjectKeys.js b/node_modules/concordance/lib/getObjectKeys.js
new file mode 100644
index 000000000..4b0c3e368
--- /dev/null
+++ b/node_modules/concordance/lib/getObjectKeys.js
@@ -0,0 +1,36 @@
+'use strict'
+
+function getObjectKeys (obj, excludeListItemAccessorsBelowLength) {
+ const keys = []
+ let size = 0
+
+ // Sort property names, they should never be order-sensitive
+ const nameCandidates = Object.getOwnPropertyNames(obj).sort()
+ // Comparators should verify symbols in an order-insensitive manner if
+ // possible.
+ const symbolCandidates = Object.getOwnPropertySymbols(obj)
+
+ for (let i = 0; i < nameCandidates.length; i++) {
+ const name = nameCandidates[i]
+
+ let accept = true
+ if (excludeListItemAccessorsBelowLength > 0) {
+ const index = Number(name)
+ accept = (index % 1 !== 0) || index >= excludeListItemAccessorsBelowLength
+ }
+
+ if (accept && Object.getOwnPropertyDescriptor(obj, name).enumerable) {
+ keys[size++] = name
+ }
+ }
+
+ for (let i = 0; i < symbolCandidates.length; i++) {
+ const symbol = symbolCandidates[i]
+ if (Object.getOwnPropertyDescriptor(obj, symbol).enumerable) {
+ keys[size++] = symbol
+ }
+ }
+
+ return { keys, size }
+}
+module.exports = getObjectKeys
diff --git a/node_modules/concordance/lib/getStringTag.js b/node_modules/concordance/lib/getStringTag.js
new file mode 100644
index 000000000..994ec4bec
--- /dev/null
+++ b/node_modules/concordance/lib/getStringTag.js
@@ -0,0 +1,30 @@
+'use strict'
+
+const ts = Object.prototype.toString
+function getStringTag (value) {
+ return ts.call(value).slice(8, -1)
+}
+
+const fts = Function.prototype.toString
+const promiseCtorString = fts.call(Promise)
+const isPromise = value => {
+ if (!value.constructor) return false
+
+ try {
+ return fts.call(value.constructor) === promiseCtorString
+ } catch (err) {
+ return false
+ }
+}
+
+if (getStringTag(Promise.resolve()) === 'Promise') {
+ module.exports = getStringTag
+} else {
+ const getStringTagWithPromiseWorkaround = value => {
+ const stringTag = getStringTag(value)
+ return stringTag === 'Object' && isPromise(value)
+ ? 'Promise'
+ : stringTag
+ }
+ module.exports = getStringTagWithPromiseWorkaround
+}
diff --git a/node_modules/concordance/lib/hasLength.js b/node_modules/concordance/lib/hasLength.js
new file mode 100644
index 000000000..18df0d369
--- /dev/null
+++ b/node_modules/concordance/lib/hasLength.js
@@ -0,0 +1,7 @@
+'use strict'
+
+const hop = Object.prototype.hasOwnProperty
+function hasLength (obj) {
+ return Array.isArray(obj) || (hop.call(obj, 'length') && typeof obj.length === 'number')
+}
+module.exports = hasLength
diff --git a/node_modules/concordance/lib/isEnumerable.js b/node_modules/concordance/lib/isEnumerable.js
new file mode 100644
index 000000000..90c2df1e1
--- /dev/null
+++ b/node_modules/concordance/lib/isEnumerable.js
@@ -0,0 +1,7 @@
+'use strict'
+
+function isEnumerable (obj, key) {
+ const desc = Object.getOwnPropertyDescriptor(obj, key)
+ return desc && desc.enumerable
+}
+module.exports = isEnumerable
diff --git a/node_modules/concordance/lib/lineBuilder.js b/node_modules/concordance/lib/lineBuilder.js
new file mode 100644
index 000000000..934ac0095
--- /dev/null
+++ b/node_modules/concordance/lib/lineBuilder.js
@@ -0,0 +1,309 @@
+'use strict'
+
+const ACTUAL = Symbol('lineBuilder.gutters.ACTUAL')
+const EXPECTED = Symbol('lineBuilder.gutters.EXPECTED')
+
+function translateGutter (theme, invert, gutter) {
+ if (invert) {
+ if (gutter === ACTUAL) return theme.diffGutters.expected
+ if (gutter === EXPECTED) return theme.diffGutters.actual
+ } else {
+ if (gutter === ACTUAL) return theme.diffGutters.actual
+ if (gutter === EXPECTED) return theme.diffGutters.expected
+ }
+ return theme.diffGutters.padding
+}
+
+class Line {
+ constructor (isFirst, isLast, gutter, stringValue) {
+ this.isFirst = isFirst
+ this.isLast = isLast
+ this.gutter = gutter
+ this.stringValue = stringValue
+ }
+
+ * [Symbol.iterator] () {
+ yield this
+ }
+
+ get isEmpty () {
+ return false
+ }
+
+ get hasGutter () {
+ return this.gutter !== null
+ }
+
+ get isSingle () {
+ return this.isFirst && this.isLast
+ }
+
+ append (other) {
+ return this.concat(other)
+ }
+
+ concat (other) {
+ return new Collection()
+ .append(this)
+ .append(other)
+ }
+
+ toString (options) {
+ if (options.diff === false) return this.stringValue
+
+ return translateGutter(options.theme, options.invert, this.gutter) + this.stringValue
+ }
+
+ mergeWithInfix (infix, other) {
+ if (other.isLine !== true) {
+ return new Collection()
+ .append(this)
+ .mergeWithInfix(infix, other)
+ }
+
+ return new Line(this.isFirst, other.isLast, other.gutter, this.stringValue + infix + other.stringValue)
+ }
+
+ withFirstPrefixed (prefix) {
+ if (!this.isFirst) return this
+
+ return new Line(true, this.isLast, this.gutter, prefix + this.stringValue)
+ }
+
+ withLastPostfixed (postfix) {
+ if (!this.isLast) return this
+
+ return new Line(this.isFirst, true, this.gutter, this.stringValue + postfix)
+ }
+
+ stripFlags () {
+ return new Line(false, false, this.gutter, this.stringValue)
+ }
+
+ decompose () {
+ return new Collection()
+ .append(this)
+ .decompose()
+ }
+}
+Object.defineProperty(Line.prototype, 'isLine', {value: true})
+
+class Collection {
+ constructor () {
+ this.buffer = []
+ }
+
+ * [Symbol.iterator] () {
+ for (const appended of this.buffer) {
+ for (const line of appended) yield line
+ }
+ }
+
+ get isEmpty () {
+ return this.buffer.length === 0
+ }
+
+ get hasGutter () {
+ for (const line of this) {
+ if (line.hasGutter) return true
+ }
+ return false
+ }
+
+ get isSingle () {
+ const iterator = this[Symbol.iterator]()
+ iterator.next()
+ return iterator.next().done === true
+ }
+
+ append (lineOrLines) {
+ if (!lineOrLines.isEmpty) this.buffer.push(lineOrLines)
+ return this
+ }
+
+ concat (other) {
+ return new Collection()
+ .append(this)
+ .append(other)
+ }
+
+ toString (options) {
+ let lines = this
+
+ if (options.invert) {
+ lines = new Collection()
+ let buffer = new Collection()
+
+ let prev = null
+ for (const line of this) {
+ if (line.gutter === ACTUAL) {
+ if (prev !== null && prev.gutter !== ACTUAL && !buffer.isEmpty) {
+ lines.append(buffer)
+ buffer = new Collection()
+ }
+
+ buffer.append(line)
+ } else if (line.gutter === EXPECTED) {
+ lines.append(line)
+ } else {
+ if (!buffer.isEmpty) {
+ lines.append(buffer)
+ buffer = new Collection()
+ }
+
+ lines.append(line)
+ }
+
+ prev = line
+ }
+ lines.append(buffer)
+ }
+
+ return Array.from(lines, line => line.toString(options)).join('\n')
+ }
+
+ mergeWithInfix (infix, from) {
+ if (from.isEmpty) throw new Error('Cannot merge, `from` is empty.')
+
+ const otherLines = Array.from(from)
+ if (!otherLines[0].isFirst) throw new Error('Cannot merge, `from` has no first line.')
+
+ const merged = new Collection()
+ let seenLast = false
+ for (const line of this) {
+ if (seenLast) throw new Error('Cannot merge line, the last line has already been seen.')
+
+ if (!line.isLast) {
+ merged.append(line)
+ continue
+ }
+
+ seenLast = true
+ for (const other of otherLines) {
+ if (other.isFirst) {
+ merged.append(line.mergeWithInfix(infix, other))
+ } else {
+ merged.append(other)
+ }
+ }
+ }
+ return merged
+ }
+
+ withFirstPrefixed (prefix) {
+ return new Collection()
+ .append(Array.from(this, line => line.withFirstPrefixed(prefix)))
+ }
+
+ withLastPostfixed (postfix) {
+ return new Collection()
+ .append(Array.from(this, line => line.withLastPostfixed(postfix)))
+ }
+
+ stripFlags () {
+ return new Collection()
+ .append(Array.from(this, line => line.stripFlags()))
+ }
+
+ decompose () {
+ const first = {actual: new Collection(), expected: new Collection()}
+ const last = {actual: new Collection(), expected: new Collection()}
+ const remaining = new Collection()
+
+ for (const line of this) {
+ if (line.isFirst && line.gutter === ACTUAL) {
+ first.actual.append(line)
+ } else if (line.isFirst && line.gutter === EXPECTED) {
+ first.expected.append(line)
+ } else if (line.isLast && line.gutter === ACTUAL) {
+ last.actual.append(line)
+ } else if (line.isLast && line.gutter === EXPECTED) {
+ last.expected.append(line)
+ } else {
+ remaining.append(line)
+ }
+ }
+
+ return {first, last, remaining}
+ }
+}
+Object.defineProperty(Collection.prototype, 'isCollection', {value: true})
+
+function setDefaultGutter (iterable, gutter) {
+ return new Collection()
+ .append(Array.from(iterable, line => {
+ return line.gutter === null
+ ? new Line(line.isFirst, line.isLast, gutter, line.stringValue)
+ : line
+ }))
+}
+
+module.exports = {
+ buffer () {
+ return new Collection()
+ },
+
+ first (stringValue) {
+ return new Line(true, false, null, stringValue)
+ },
+
+ last (stringValue) {
+ return new Line(false, true, null, stringValue)
+ },
+
+ line (stringValue) {
+ return new Line(false, false, null, stringValue)
+ },
+
+ single (stringValue) {
+ return new Line(true, true, null, stringValue)
+ },
+
+ setDefaultGutter (lineOrCollection) {
+ return lineOrCollection
+ },
+
+ actual: {
+ first (stringValue) {
+ return new Line(true, false, ACTUAL, stringValue)
+ },
+
+ last (stringValue) {
+ return new Line(false, true, ACTUAL, stringValue)
+ },
+
+ line (stringValue) {
+ return new Line(false, false, ACTUAL, stringValue)
+ },
+
+ single (stringValue) {
+ return new Line(true, true, ACTUAL, stringValue)
+ },
+
+ setDefaultGutter (lineOrCollection) {
+ return setDefaultGutter(lineOrCollection, ACTUAL)
+ }
+ },
+
+ expected: {
+ first (stringValue) {
+ return new Line(true, false, EXPECTED, stringValue)
+ },
+
+ last (stringValue) {
+ return new Line(false, true, EXPECTED, stringValue)
+ },
+
+ line (stringValue) {
+ return new Line(false, false, EXPECTED, stringValue)
+ },
+
+ single (stringValue) {
+ return new Line(true, true, EXPECTED, stringValue)
+ },
+
+ setDefaultGutter (lineOrCollection) {
+ return setDefaultGutter(lineOrCollection, EXPECTED)
+ }
+ }
+}
diff --git a/node_modules/concordance/lib/metaDescriptors/item.js b/node_modules/concordance/lib/metaDescriptors/item.js
new file mode 100644
index 000000000..1539b624a
--- /dev/null
+++ b/node_modules/concordance/lib/metaDescriptors/item.js
@@ -0,0 +1,254 @@
+'use strict'
+
+const constants = require('../constants')
+const formatUtils = require('../formatUtils')
+const recursorUtils = require('../recursorUtils')
+
+const DEEP_EQUAL = constants.DEEP_EQUAL
+const UNEQUAL = constants.UNEQUAL
+
+function describeComplex (index, value) {
+ return new ComplexItem(index, value)
+}
+exports.describeComplex = describeComplex
+
+function deserializeComplex (index, recursor) {
+ const value = recursor()
+ return new ComplexItem(index, value)
+}
+exports.deserializeComplex = deserializeComplex
+
+function describePrimitive (index, value) {
+ return new PrimitiveItem(index, value)
+}
+exports.describePrimitive = describePrimitive
+
+function deserializePrimitive (state) {
+ const index = state[0]
+ const value = state[1]
+ return new PrimitiveItem(index, value)
+}
+exports.deserializePrimitive = deserializePrimitive
+
+const complexTag = Symbol('ComplexItem')
+exports.complexTag = complexTag
+
+const primitiveTag = Symbol('PrimitiveItem')
+exports.primitiveTag = primitiveTag
+
+class ComplexItem {
+ constructor (index, value) {
+ this.index = index
+ this.value = value
+ }
+
+ createRecursor () {
+ return recursorUtils.singleValue(this.value)
+ }
+
+ compare (expected) {
+ return expected.tag === complexTag && this.index === expected.index
+ ? this.value.compare(expected.value)
+ : UNEQUAL
+ }
+
+ formatShallow (theme, indent) {
+ const increaseValueIndent = theme.item.increaseValueIndent === true
+ return new formatUtils.SingleValueFormatter(theme, value => {
+ if (typeof theme.item.customFormat === 'function') {
+ return theme.item.customFormat(theme, indent, value)
+ }
+
+ return value.withLastPostfixed(theme.item.after)
+ }, increaseValueIndent)
+ }
+
+ prepareDiff (expected, lhsRecursor, rhsRecursor, compareComplexShape, isCircular) {
+ // Circular values cannot be compared. They must be treated as being unequal when diffing.
+ if (isCircular(this.value) || isCircular(expected.value)) return {compareResult: UNEQUAL}
+
+ // Try to line up this or remaining items with the expected items.
+ const lhsFork = recursorUtils.fork(lhsRecursor)
+ const rhsFork = recursorUtils.fork(rhsRecursor)
+ const initialExpected = expected
+
+ let expectedIsMissing = false
+ while (!expectedIsMissing && expected !== null && expected.isItem === true) {
+ if (expected.tag === complexTag) {
+ expectedIsMissing = compareComplexShape(this.value, expected.value) !== UNEQUAL
+ }
+
+ expected = rhsFork.shared()
+ }
+
+ let actualIsExtraneous = false
+ if (initialExpected.tag === complexTag) {
+ let actual = this
+ while (!actualIsExtraneous && actual !== null && actual.isItem === true) {
+ if (actual.tag === complexTag) {
+ actualIsExtraneous = compareComplexShape(actual.value, initialExpected.value) !== UNEQUAL
+ }
+
+ actual = lhsFork.shared()
+ }
+ } else if (initialExpected.tag === primitiveTag) {
+ let actual = this
+ while (!actualIsExtraneous && actual !== null && actual.isItem === true) {
+ if (actual.tag === primitiveTag) {
+ actualIsExtraneous = initialExpected.value.compare(actual.value) === DEEP_EQUAL
+ }
+
+ actual = lhsFork.shared()
+ }
+ }
+
+ if (actualIsExtraneous && !expectedIsMissing) {
+ return {
+ actualIsExtraneous: true,
+ lhsRecursor: lhsFork.recursor,
+ rhsRecursor: recursorUtils.map(
+ recursorUtils.unshift(rhsFork.recursor, initialExpected),
+ next => {
+ if (next.isItem !== true) return next
+
+ next.index++
+ return next
+ })
+ }
+ }
+
+ if (expectedIsMissing && !actualIsExtraneous) {
+ return {
+ expectedIsMissing: true,
+ lhsRecursor: recursorUtils.map(
+ recursorUtils.unshift(lhsFork.recursor, this),
+ next => {
+ if (next.isItem !== true) return next
+
+ next.index++
+ return next
+ }),
+ rhsRecursor: rhsFork.recursor
+ }
+ }
+
+ const mustRecurse = this.tag === complexTag && initialExpected.tag === complexTag &&
+ this.value.compare(initialExpected.value) !== UNEQUAL
+ return {
+ mustRecurse,
+ isUnequal: !mustRecurse,
+ lhsRecursor: lhsFork.recursor,
+ rhsRecursor: rhsFork.recursor
+ }
+ }
+
+ serialize () {
+ return this.index
+ }
+}
+Object.defineProperty(ComplexItem.prototype, 'isItem', { value: true })
+Object.defineProperty(ComplexItem.prototype, 'tag', { value: complexTag })
+
+class PrimitiveItem {
+ constructor (index, value) {
+ this.index = index
+ this.value = value
+ }
+
+ compare (expected) {
+ return expected.tag === primitiveTag && this.index === expected.index
+ ? this.value.compare(expected.value)
+ : UNEQUAL
+ }
+
+ formatDeep (theme, indent) {
+ const increaseValueIndent = theme.item.increaseValueIndent === true
+ const valueIndent = increaseValueIndent ? indent.increase() : indent
+
+ // Since the value is formatted directly, modifiers are not applied. Apply
+ // modifiers to the item descriptor instead.
+ const formatted = this.value.formatDeep(theme, valueIndent)
+
+ if (typeof theme.item.customFormat === 'function') {
+ return theme.item.customFormat(theme, indent, formatted)
+ }
+
+ return formatted.withLastPostfixed(theme.item.after)
+ }
+
+ prepareDiff (expected, lhsRecursor, rhsRecursor, compareComplexShape, isCircular) {
+ const compareResult = this.compare(expected)
+ // Short-circuit when values are deeply equal.
+ if (compareResult === DEEP_EQUAL) return {compareResult}
+
+ // Short-circut when values can be diffed directly.
+ if (
+ expected.tag === primitiveTag &&
+ this.value.tag === expected.value.tag && typeof this.value.diffDeep === 'function'
+ ) {
+ return {compareResult}
+ }
+
+ // Try to line up this or remaining items with the expected items.
+ const rhsFork = recursorUtils.fork(rhsRecursor)
+ const initialExpected = expected
+
+ do {
+ if (expected === null || expected.isItem !== true) {
+ return {
+ actualIsExtraneous: true,
+ rhsRecursor: recursorUtils.map(
+ recursorUtils.unshift(rhsFork.recursor, initialExpected),
+ next => {
+ if (next.isItem !== true) return next
+
+ next.index++
+ return next
+ })
+ }
+ }
+
+ if (this.value.compare(expected.value) === DEEP_EQUAL) {
+ return {
+ expectedIsMissing: true,
+ lhsRecursor: recursorUtils.map(
+ recursorUtils.unshift(lhsRecursor, this),
+ next => {
+ if (next.isItem !== true) return next
+
+ next.index++
+ return next
+ }),
+ rhsRecursor: rhsFork.recursor
+ }
+ }
+
+ expected = rhsFork.shared()
+ } while (true)
+ }
+
+ diffDeep (expected, theme, indent) {
+ // Verify a diff can be returned.
+ if (this.tag !== expected.tag || typeof this.value.diffDeep !== 'function') return null
+
+ const increaseValueIndent = theme.property.increaseValueIndent === true
+ const valueIndent = increaseValueIndent ? indent.increase() : indent
+
+ // Since the value is diffed directly, modifiers are not applied. Apply
+ // modifiers to the item descriptor instead.
+ const diff = this.value.diffDeep(expected.value, theme, valueIndent)
+ if (diff === null) return null
+
+ if (typeof theme.item.customFormat === 'function') {
+ return theme.item.customFormat(theme, indent, diff)
+ }
+
+ return diff.withLastPostfixed(theme.item.after)
+ }
+
+ serialize () {
+ return [this.index, this.value]
+ }
+}
+Object.defineProperty(PrimitiveItem.prototype, 'isItem', { value: true })
+Object.defineProperty(PrimitiveItem.prototype, 'tag', { value: primitiveTag })
diff --git a/node_modules/concordance/lib/metaDescriptors/mapEntry.js b/node_modules/concordance/lib/metaDescriptors/mapEntry.js
new file mode 100644
index 000000000..f1c100e4e
--- /dev/null
+++ b/node_modules/concordance/lib/metaDescriptors/mapEntry.js
@@ -0,0 +1,223 @@
+'use strict'
+
+const constants = require('../constants')
+const lineBuilder = require('../lineBuilder')
+const recursorUtils = require('../recursorUtils')
+const themeUtils = require('../themeUtils')
+
+const DEEP_EQUAL = constants.DEEP_EQUAL
+const UNEQUAL = constants.UNEQUAL
+const SHALLOW_EQUAL = constants.SHALLOW_EQUAL
+
+function describe (keyDescriptor, valueDescriptor) {
+ const keyIsPrimitive = keyDescriptor.isPrimitive === true
+ const valueIsPrimitive = valueDescriptor.isPrimitive === true
+
+ return new MapEntry(keyDescriptor, valueDescriptor, keyIsPrimitive, valueIsPrimitive)
+}
+exports.describe = describe
+
+function deserialize (state, recursor) {
+ const keyIsPrimitive = state[0]
+ const valueIsPrimitive = state[1]
+ const keyDescriptor = recursor()
+ const valueDescriptor = recursor()
+
+ return new MapEntry(keyDescriptor, valueDescriptor, keyIsPrimitive, valueIsPrimitive)
+}
+exports.deserialize = deserialize
+
+const tag = Symbol('MapEntry')
+exports.tag = tag
+
+function mergeWithKey (theme, key, values) {
+ const lines = lineBuilder.buffer()
+ const keyRemainder = lineBuilder.buffer()
+ for (const line of key) {
+ if (!line.isLast && !line.hasGutter) {
+ lines.append(line)
+ } else {
+ keyRemainder.append(line)
+ }
+ }
+ for (const value of values) {
+ lines.append(keyRemainder.mergeWithInfix(theme.mapEntry.separator, value).withLastPostfixed(theme.mapEntry.after))
+ }
+ return lines
+}
+
+class MapEntry {
+ constructor (key, value, keyIsPrimitive, valueIsPrimitive) {
+ this.key = key
+ this.value = value
+ this.keyIsPrimitive = keyIsPrimitive
+ this.valueIsPrimitive = valueIsPrimitive
+ }
+
+ createRecursor () {
+ let emitKey = true
+ let emitValue = true
+
+ return () => {
+ if (emitKey) {
+ emitKey = false
+ return this.key
+ }
+
+ if (emitValue) {
+ emitValue = false
+ return this.value
+ }
+
+ return null
+ }
+ }
+
+ compare (expected) {
+ if (this.tag !== expected.tag) return UNEQUAL
+ if (this.keyIsPrimitive !== expected.keyIsPrimitive) return UNEQUAL
+ if (this.valueIsPrimitive !== expected.valueIsPrimitive) return UNEQUAL
+
+ if (!this.keyIsPrimitive) return SHALLOW_EQUAL
+
+ const keyResult = this.key.compare(expected.key)
+ if (keyResult !== DEEP_EQUAL) return keyResult
+
+ if (!this.valueIsPrimitive) return SHALLOW_EQUAL
+ return this.value.compare(expected.value)
+ }
+
+ formatDeep (theme, indent) {
+ // Verify the map entry can be formatted directly.
+ if (!this.keyIsPrimitive || typeof this.value.formatDeep !== 'function') return null
+
+ // Since formatShallow() would result in theme modifiers being applied
+ // before the key and value are formatted, do the same here.
+ const value = this.value.formatDeep(themeUtils.applyModifiersToOriginal(this.value, theme), indent)
+ if (value === null) return null
+
+ const key = this.key.formatDeep(themeUtils.applyModifiersToOriginal(this.key, theme), indent)
+ return mergeWithKey(theme, key, [value])
+ }
+
+ formatShallow (theme, indent) {
+ let key = null
+ const values = []
+ return {
+ append: (formatted, origin) => {
+ if (this.key === origin) {
+ key = formatted
+ } else {
+ values.push(formatted)
+ }
+ },
+ finalize () {
+ return mergeWithKey(theme, key, values)
+ }
+ }
+ }
+
+ diffDeep (expected, theme, indent) {
+ // Verify a diff can be returned.
+ if (this.tag !== expected.tag || typeof this.value.diffDeep !== 'function') return null
+ // Only use this logic to format value diffs when the keys are primitive and equal.
+ if (!this.keyIsPrimitive || !expected.keyIsPrimitive || this.key.compare(expected.key) !== DEEP_EQUAL) {
+ return null
+ }
+
+ // Since formatShallow() would result in theme modifiers being applied
+ // before the key and value are formatted, do the same here.
+ const diff = this.value.diffDeep(expected.value, themeUtils.applyModifiersToOriginal(this.value, theme), indent)
+ if (diff === null) return null
+
+ const key = this.key.formatDeep(themeUtils.applyModifiersToOriginal(this.key, theme), indent, '')
+ return mergeWithKey(theme, key, [diff])
+ }
+
+ prepareDiff (expected, lhsRecursor, rhsRecursor, compareComplexShape, isCircular) {
+ // Circular values cannot be compared. They must be treated as being unequal when diffing.
+ if (isCircular(this.value) || isCircular(expected.value)) return {compareResult: UNEQUAL}
+
+ const compareResult = this.compare(expected)
+ const keysAreEqual = this.tag === expected.tag && this.key.compare(expected.key) === DEEP_EQUAL
+ // Short-circuit when keys and/or values are deeply equal.
+ if (compareResult === DEEP_EQUAL || keysAreEqual) return {compareResult}
+
+ // Try to line up this or remaining map entries with the expected entries.
+ const lhsFork = recursorUtils.fork(lhsRecursor)
+ const rhsFork = recursorUtils.fork(rhsRecursor)
+ const initialExpected = expected
+
+ let expectedIsMissing = false
+ while (!expectedIsMissing && expected !== null && this.tag === expected.tag) {
+ if (expected.keyIsPrimitive) {
+ expectedIsMissing = this.key.compare(expected.key) !== UNEQUAL
+ } else {
+ expectedIsMissing = compareComplexShape(this.key, expected.key) !== UNEQUAL
+ }
+
+ expected = rhsFork.shared()
+ }
+
+ let actualIsExtraneous = false
+ if (this.tag === initialExpected.tag) {
+ if (initialExpected.keyIsPrimitive) {
+ let actual = this
+ while (!actualIsExtraneous && actual !== null && this.tag === actual.tag) {
+ if (actual.keyIsPrimitive) {
+ actualIsExtraneous = initialExpected.key.compare(actual.key) === DEEP_EQUAL
+ }
+
+ actual = lhsFork.shared()
+ }
+ } else {
+ let actual = this
+ while (!actualIsExtraneous && actual !== null && this.tag === actual.tag) {
+ if (!actual.keyIsPrimitive) {
+ actualIsExtraneous = compareComplexShape(actual.key, initialExpected.key) !== UNEQUAL
+ }
+
+ actual = lhsFork.shared()
+ }
+ }
+ }
+
+ if (actualIsExtraneous && !expectedIsMissing) {
+ return {
+ actualIsExtraneous: true,
+ lhsRecursor: lhsFork.recursor,
+ rhsRecursor: recursorUtils.unshift(rhsFork.recursor, initialExpected)
+ }
+ }
+
+ if (expectedIsMissing && !actualIsExtraneous) {
+ return {
+ expectedIsMissing: true,
+ lhsRecursor: recursorUtils.unshift(lhsFork.recursor, this),
+ rhsRecursor: rhsFork.recursor
+ }
+ }
+
+ let mustRecurse = false
+ if (!this.keyIsPrimitive && !initialExpected.keyIsPrimitive) {
+ if (this.valueIsPrimitive || initialExpected.valueIsPrimitive) {
+ mustRecurse = this.value.compare(initialExpected.value) !== UNEQUAL
+ } else {
+ mustRecurse = compareComplexShape(this.value, initialExpected.value) !== UNEQUAL
+ }
+ }
+
+ return {
+ mustRecurse,
+ isUnequal: !mustRecurse,
+ lhsRecursor: lhsFork.recursor,
+ rhsRecursor: rhsFork.recursor
+ }
+ }
+
+ serialize () {
+ return [this.keyIsPrimitive, this.valueIsPrimitive]
+ }
+}
+Object.defineProperty(MapEntry.prototype, 'isMapEntry', { value: true })
+Object.defineProperty(MapEntry.prototype, 'tag', { value: tag })
diff --git a/node_modules/concordance/lib/metaDescriptors/pointer.js b/node_modules/concordance/lib/metaDescriptors/pointer.js
new file mode 100644
index 000000000..f569d2891
--- /dev/null
+++ b/node_modules/concordance/lib/metaDescriptors/pointer.js
@@ -0,0 +1,31 @@
+'use strict'
+
+const UNEQUAL = require('../constants').UNEQUAL
+
+function describe (index) {
+ return new Pointer(index)
+}
+exports.describe = describe
+
+exports.deserialize = describe
+
+const tag = Symbol('Pointer')
+exports.tag = tag
+
+class Pointer {
+ constructor (index) {
+ this.index = index
+ }
+
+ // Pointers cannot be compared, and are not expected to be part of the
+ // comparisons.
+ compare (expected) {
+ return UNEQUAL
+ }
+
+ serialize () {
+ return this.index
+ }
+}
+Object.defineProperty(Pointer.prototype, 'isPointer', { value: true })
+Object.defineProperty(Pointer.prototype, 'tag', { value: tag })
diff --git a/node_modules/concordance/lib/metaDescriptors/property.js b/node_modules/concordance/lib/metaDescriptors/property.js
new file mode 100644
index 000000000..b5d34271d
--- /dev/null
+++ b/node_modules/concordance/lib/metaDescriptors/property.js
@@ -0,0 +1,190 @@
+'use strict'
+
+const constants = require('../constants')
+const formatUtils = require('../formatUtils')
+const recursorUtils = require('../recursorUtils')
+const symbolPrimitive = require('../primitiveValues/symbol').tag
+
+const AMBIGUOUS = constants.AMBIGUOUS
+const DEEP_EQUAL = constants.DEEP_EQUAL
+const UNEQUAL = constants.UNEQUAL
+
+function describeComplex (key, value) {
+ return new ComplexProperty(key, value)
+}
+exports.describeComplex = describeComplex
+
+function deserializeComplex (key, recursor) {
+ const value = recursor()
+ return new ComplexProperty(key, value)
+}
+exports.deserializeComplex = deserializeComplex
+
+function describePrimitive (key, value) {
+ return new PrimitiveProperty(key, value)
+}
+exports.describePrimitive = describePrimitive
+
+function deserializePrimitive (state) {
+ const key = state[0]
+ const value = state[1]
+ return new PrimitiveProperty(key, value)
+}
+exports.deserializePrimitive = deserializePrimitive
+
+const complexTag = Symbol('ComplexProperty')
+exports.complexTag = complexTag
+
+const primitiveTag = Symbol('PrimitiveProperty')
+exports.primitiveTag = primitiveTag
+
+class Property {
+ constructor (key) {
+ this.key = key
+ }
+
+ compareKeys (expected) {
+ const result = this.key.compare(expected.key)
+ // Return AMBIGUOUS if symbol keys are unequal. It's likely that properties
+ // are compared in order of declaration, which is not the desired strategy.
+ // Returning AMBIGUOUS allows compare() and diff() to recognize this
+ // situation and sort the symbol properties before comparing them.
+ return result === UNEQUAL && this.key.tag === symbolPrimitive && expected.key.tag === symbolPrimitive
+ ? AMBIGUOUS
+ : result
+ }
+
+ prepareDiff (expected, lhsRecursor, rhsRecursor, compareComplexShape, isCircular) {
+ // Circular values cannot be compared. They must be treated as being unequal when diffing.
+ if (isCircular(this.value) || isCircular(expected.value)) return {compareResult: UNEQUAL}
+
+ // Try to line up this or remaining properties with the expected properties.
+ const rhsFork = recursorUtils.fork(rhsRecursor)
+ const initialExpected = expected
+
+ do {
+ if (expected === null || expected.isProperty !== true) {
+ return {
+ actualIsExtraneous: true,
+ rhsRecursor: recursorUtils.unshift(rhsFork.recursor, initialExpected)
+ }
+ } else if (this.key.compare(expected.key) === DEEP_EQUAL) {
+ if (expected === initialExpected) {
+ return null
+ } else {
+ return {
+ expectedIsMissing: true,
+ lhsRecursor: recursorUtils.unshift(lhsRecursor, this),
+ rhsRecursor: rhsFork.recursor
+ }
+ }
+ }
+
+ expected = rhsFork.shared()
+ } while (true)
+ }
+}
+Object.defineProperty(Property.prototype, 'isProperty', { value: true })
+
+class ComplexProperty extends Property {
+ constructor (key, value) {
+ super(key)
+ this.value = value
+ }
+
+ createRecursor () {
+ return recursorUtils.singleValue(this.value)
+ }
+
+ compare (expected) {
+ if (expected.isProperty !== true) return UNEQUAL
+
+ const keyResult = this.compareKeys(expected)
+ if (keyResult !== DEEP_EQUAL) return keyResult
+
+ return this.tag === expected.tag
+ ? this.value.compare(expected.value)
+ : UNEQUAL
+ }
+
+ formatShallow (theme, indent) {
+ const increaseValueIndent = theme.property.increaseValueIndent === true
+ return new formatUtils.SingleValueFormatter(theme, value => {
+ if (typeof theme.property.customFormat === 'function') {
+ return theme.property.customFormat(theme, indent, this.key, value)
+ }
+
+ return value
+ .withFirstPrefixed(this.key.formatAsKey(theme) + theme.property.separator)
+ .withLastPostfixed(theme.property.after)
+ }, increaseValueIndent)
+ }
+
+ serialize () {
+ return this.key
+ }
+}
+Object.defineProperty(ComplexProperty.prototype, 'tag', { value: complexTag })
+
+class PrimitiveProperty extends Property {
+ constructor (key, value) {
+ super(key)
+ this.value = value
+ }
+
+ compare (expected) {
+ if (expected.isProperty !== true) return UNEQUAL
+
+ const keyResult = this.compareKeys(expected)
+ if (keyResult !== DEEP_EQUAL) return keyResult
+
+ return this.tag !== expected.tag
+ ? UNEQUAL
+ : this.value.compare(expected.value)
+ }
+
+ formatDeep (theme, indent) {
+ const increaseValueIndent = theme.property.increaseValueIndent === true
+ const valueIndent = increaseValueIndent ? indent.increase() : indent
+
+ // Since the key and value are formatted directly, modifiers are not
+ // applied. Apply modifiers to the property descriptor instead.
+ const formatted = this.value.formatDeep(theme, valueIndent)
+
+ if (typeof theme.property.customFormat === 'function') {
+ return theme.property.customFormat(theme, indent, this.key, formatted)
+ }
+
+ return formatted
+ .withFirstPrefixed(this.key.formatAsKey(theme) + theme.property.separator)
+ .withLastPostfixed(theme.property.after)
+ }
+
+ diffDeep (expected, theme, indent) {
+ // Verify a diff can be returned.
+ if (this.tag !== expected.tag || typeof this.value.diffDeep !== 'function') return null
+ // Only use this logic to diff values when the keys are the same.
+ if (this.key.compare(expected.key) !== DEEP_EQUAL) return null
+
+ const increaseValueIndent = theme.property.increaseValueIndent === true
+ const valueIndent = increaseValueIndent ? indent.increase() : indent
+
+ // Since the key and value are diffed directly, modifiers are not
+ // applied. Apply modifiers to the property descriptor instead.
+ const diff = this.value.diffDeep(expected.value, theme, valueIndent)
+ if (diff === null) return null
+
+ if (typeof theme.property.customFormat === 'function') {
+ return theme.property.customFormat(theme, indent, this.key, diff)
+ }
+
+ return diff
+ .withFirstPrefixed(this.key.formatAsKey(theme) + theme.property.separator)
+ .withLastPostfixed(theme.property.after)
+ }
+
+ serialize () {
+ return [this.key, this.value]
+ }
+}
+Object.defineProperty(PrimitiveProperty.prototype, 'tag', { value: primitiveTag })
diff --git a/node_modules/concordance/lib/metaDescriptors/stats.js b/node_modules/concordance/lib/metaDescriptors/stats.js
new file mode 100644
index 000000000..93bde9d1e
--- /dev/null
+++ b/node_modules/concordance/lib/metaDescriptors/stats.js
@@ -0,0 +1,136 @@
+'use strict'
+
+const constants = require('../constants')
+const lineBuilder = require('../lineBuilder')
+const recursorUtils = require('../recursorUtils')
+
+const DEEP_EQUAL = constants.DEEP_EQUAL
+const UNEQUAL = constants.UNEQUAL
+
+function describeIterableRecursor (recursor) {
+ return new IterableStats(recursor.size)
+}
+exports.describeIterableRecursor = describeIterableRecursor
+
+function describeListRecursor (recursor) {
+ return new ListStats(recursor.size)
+}
+exports.describeListRecursor = describeListRecursor
+
+function describePropertyRecursor (recursor) {
+ return new PropertyStats(recursor.size)
+}
+exports.describePropertyRecursor = describePropertyRecursor
+
+function deserializeIterableStats (size) {
+ return new IterableStats(size)
+}
+exports.deserializeIterableStats = deserializeIterableStats
+
+function deserializeListStats (size) {
+ return new ListStats(size)
+}
+exports.deserializeListStats = deserializeListStats
+
+function deserializePropertyStats (size) {
+ return new PropertyStats(size)
+}
+exports.deserializePropertyStats = deserializePropertyStats
+
+const iterableTag = Symbol('IterableStats')
+exports.iterableTag = iterableTag
+
+const listTag = Symbol('ListStats')
+exports.listTag = listTag
+
+const propertyTag = Symbol('PropertyStats')
+exports.propertyTag = propertyTag
+
+class Stats {
+ constructor (size) {
+ this.size = size
+ }
+
+ formatDeep (theme) {
+ return lineBuilder.single(theme.stats.separator)
+ }
+
+ prepareDiff (expected, lhsRecursor, rhsRecursor, compareComplexShape) {
+ if (expected.isStats !== true || expected.tag === this.tag) return null
+
+ // Try to line up stats descriptors with the same tag.
+ const rhsFork = recursorUtils.fork(rhsRecursor)
+ const initialExpected = expected
+
+ const missing = []
+ while (expected !== null && this.tag !== expected.tag) {
+ missing.push(expected)
+ expected = rhsFork.shared()
+ }
+
+ if (expected !== null && missing.length > 0) {
+ return {
+ multipleAreMissing: true,
+ descriptors: missing,
+ lhsRecursor: recursorUtils.unshift(lhsRecursor, this),
+ // Use original `rhsRecursor`, not `rhsFork`, since the consumed
+ // descriptors are returned with the `missing` array.
+ rhsRecursor: recursorUtils.unshift(rhsRecursor, expected)
+ }
+ }
+
+ const lhsFork = recursorUtils.fork(lhsRecursor)
+ let actual = this
+
+ const extraneous = []
+ while (actual !== null && actual.tag !== initialExpected.tag) {
+ extraneous.push(actual)
+ actual = lhsFork.shared()
+ }
+
+ if (actual !== null && extraneous.length > 0) {
+ return {
+ multipleAreExtraneous: true,
+ descriptors: extraneous,
+ // Use original `lhsRecursor`, not `lhsFork`, since the consumed
+ // descriptors are returned with the `extraneous` array.
+ lhsRecursor: recursorUtils.unshift(lhsRecursor, actual),
+ rhsRecursor: recursorUtils.unshift(rhsFork.recursor, initialExpected)
+ }
+ }
+
+ return null
+ }
+
+ serialize () {
+ return this.size
+ }
+}
+Object.defineProperty(Stats.prototype, 'isStats', { value: true })
+
+class IterableStats extends Stats {
+ compare (expected) {
+ return expected.tag === iterableTag && this.size === expected.size
+ ? DEEP_EQUAL
+ : UNEQUAL
+ }
+}
+Object.defineProperty(IterableStats.prototype, 'tag', { value: iterableTag })
+
+class ListStats extends Stats {
+ compare (expected) {
+ return expected.tag === listTag && this.size === expected.size
+ ? DEEP_EQUAL
+ : UNEQUAL
+ }
+}
+Object.defineProperty(ListStats.prototype, 'tag', { value: listTag })
+
+class PropertyStats extends Stats {
+ compare (expected) {
+ return expected.tag === propertyTag && this.size === expected.size
+ ? DEEP_EQUAL
+ : UNEQUAL
+ }
+}
+Object.defineProperty(PropertyStats.prototype, 'tag', { value: propertyTag })
diff --git a/node_modules/concordance/lib/pluginRegistry.js b/node_modules/concordance/lib/pluginRegistry.js
new file mode 100644
index 000000000..b4fb76b37
--- /dev/null
+++ b/node_modules/concordance/lib/pluginRegistry.js
@@ -0,0 +1,222 @@
+'use strict'
+
+const semver = require('semver')
+
+const pkg = require('../package.json')
+const constants = require('./constants')
+const object = require('./complexValues/object')
+const lineBuilder = require('./lineBuilder')
+const formatUtils = require('./formatUtils')
+const itemDescriptor = require('./metaDescriptors/item')
+const propertyDescriptor = require('./metaDescriptors/property')
+const stringDescriptor = require('./primitiveValues/string')
+const recursorUtils = require('./recursorUtils')
+const themeUtils = require('./themeUtils')
+
+const API_VERSION = 1
+const CONCORDANCE_VERSION = pkg.version
+
+const descriptorRegistry = new Map()
+const registry = new Map()
+
+class PluginError extends Error {
+ constructor (message, plugin) {
+ super(message)
+ this.name = 'PluginError'
+ this.plugin = plugin
+ }
+}
+
+class PluginTypeError extends TypeError {
+ constructor (message, plugin) {
+ super(message)
+ this.name = 'PluginTypeError'
+ this.plugin = plugin
+ }
+}
+
+class UnsupportedApiError extends PluginError {
+ constructor (plugin) {
+ super('Plugin requires an unsupported API version', plugin)
+ this.name = 'UnsupportedApiError'
+ }
+}
+
+class UnsupportedError extends PluginError {
+ constructor (plugin) {
+ super('Plugin does not support this version of Concordance', plugin)
+ this.name = 'UnsupportedError'
+ }
+}
+
+class DuplicateDescriptorTagError extends PluginError {
+ constructor (tag, plugin) {
+ super(`Could not add descriptor: tag ${String(tag)} has already been registered`, plugin)
+ this.name = 'DuplicateDescriptorTagError'
+ this.tag = tag
+ }
+}
+
+class DuplicateDescriptorIdError extends PluginError {
+ constructor (id, plugin) {
+ const printed = typeof id === 'number'
+ ? `0x${id.toString(16).toUpperCase()}`
+ : String(id)
+ super(`Could not add descriptor: id ${printed} has already been registered`, plugin)
+ this.name = 'DuplicateDescriptorIdError'
+ this.id = id
+ }
+}
+
+function verify (plugin) {
+ if (typeof plugin.name !== 'string' || !plugin.name) {
+ throw new PluginTypeError('Plugin must have a `name`', plugin)
+ }
+
+ if (plugin.apiVersion !== API_VERSION) {
+ throw new UnsupportedApiError(plugin)
+ }
+
+ if ('minimalConcordanceVersion' in plugin) {
+ if (!semver.valid(plugin.minimalConcordanceVersion)) {
+ throw new PluginTypeError('If specified, `minimalConcordanceVersion` must be a valid SemVer version', plugin)
+ }
+
+ const range = `>=${plugin.minimalConcordanceVersion}`
+ if (!semver.satisfies(CONCORDANCE_VERSION, range)) {
+ throw new UnsupportedError(plugin)
+ }
+ }
+}
+
+// Selectively expose descriptor tags.
+const publicDescriptorTags = Object.freeze({
+ complexItem: itemDescriptor.complexTag,
+ primitiveItem: itemDescriptor.primitiveTag,
+ primitiveProperty: propertyDescriptor.primitiveTag,
+ string: stringDescriptor.tag
+})
+
+// Don't expose `setDefaultGutter()`.
+const publicLineBuilder = Object.freeze({
+ buffer: lineBuilder.buffer,
+ first: lineBuilder.first,
+ last: lineBuilder.last,
+ line: lineBuilder.line,
+ single: lineBuilder.single,
+ actual: Object.freeze({
+ buffer: lineBuilder.actual.buffer,
+ first: lineBuilder.actual.first,
+ last: lineBuilder.actual.last,
+ line: lineBuilder.actual.line,
+ single: lineBuilder.actual.single
+ }),
+ expected: Object.freeze({
+ buffer: lineBuilder.expected.buffer,
+ first: lineBuilder.expected.first,
+ last: lineBuilder.expected.last,
+ line: lineBuilder.expected.line,
+ single: lineBuilder.expected.single
+ })
+})
+
+function modifyTheme (descriptor, modifier) {
+ themeUtils.addModifier(descriptor, modifier)
+ return descriptor
+}
+
+function add (plugin) {
+ verify(plugin)
+
+ const name = plugin.name
+ if (registry.has(name)) return registry.get(name)
+
+ const id2deserialize = new Map()
+ const tag2id = new Map()
+ const addDescriptor = (id, tag, deserialize) => {
+ if (id2deserialize.has(id)) throw new DuplicateDescriptorIdError(id, plugin)
+ if (descriptorRegistry.has(tag) || tag2id.has(tag)) throw new DuplicateDescriptorTagError(tag, plugin)
+
+ id2deserialize.set(id, deserialize)
+ tag2id.set(tag, id)
+ }
+
+ const tryDescribeValue = plugin.register({
+ // Concordance makes assumptions about when AMBIGUOUS occurs. Do not expose
+ // it to plugins.
+ UNEQUAL: constants.UNEQUAL,
+ SHALLOW_EQUAL: constants.SHALLOW_EQUAL,
+ DEEP_EQUAL: constants.DEEP_EQUAL,
+
+ ObjectValue: object.ObjectValue,
+ DescribedMixin: object.DescribedMixin,
+ DeserializedMixin: object.DeserializedMixin,
+
+ addDescriptor,
+ applyThemeModifiers: themeUtils.applyModifiers,
+ descriptorTags: publicDescriptorTags,
+ lineBuilder: publicLineBuilder,
+ mapRecursor: recursorUtils.map,
+ modifyTheme,
+ wrapFromTheme: formatUtils.wrap
+ })
+
+ const registered = {
+ id2deserialize,
+ serializerVersion: plugin.serializerVersion,
+ name,
+ tag2id,
+ theme: plugin.theme || {},
+ tryDescribeValue
+ }
+
+ registry.set(name, registered)
+ for (const tag of tag2id.keys()) {
+ descriptorRegistry.set(tag, registered)
+ }
+
+ return registered
+}
+exports.add = add
+
+function getDeserializers (plugins) {
+ return plugins.map(plugin => {
+ const registered = add(plugin)
+ return {
+ id2deserialize: registered.id2deserialize,
+ name: registered.name,
+ serializerVersion: registered.serializerVersion
+ }
+ })
+}
+exports.getDeserializers = getDeserializers
+
+function getThemes (plugins) {
+ return plugins.map(plugin => {
+ const registered = add(plugin)
+ return {
+ name: registered.name,
+ theme: registered.theme
+ }
+ })
+}
+exports.getThemes = getThemes
+
+function getTryDescribeValues (plugins) {
+ return plugins.map(plugin => add(plugin).tryDescribeValue)
+}
+exports.getTryDescribeValues = getTryDescribeValues
+
+function resolveDescriptorRef (tag) {
+ if (!descriptorRegistry.has(tag)) return null
+
+ const registered = descriptorRegistry.get(tag)
+ return {
+ id: registered.tag2id.get(tag),
+ name: registered.name,
+ serialization: {
+ serializerVersion: registered.serializerVersion
+ }
+ }
+}
+exports.resolveDescriptorRef = resolveDescriptorRef
diff --git a/node_modules/concordance/lib/primitiveValues/boolean.js b/node_modules/concordance/lib/primitiveValues/boolean.js
new file mode 100644
index 000000000..7bad50408
--- /dev/null
+++ b/node_modules/concordance/lib/primitiveValues/boolean.js
@@ -0,0 +1,40 @@
+'use strict'
+
+const constants = require('../constants')
+const formatUtils = require('../formatUtils')
+const lineBuilder = require('../lineBuilder')
+
+const DEEP_EQUAL = constants.DEEP_EQUAL
+const UNEQUAL = constants.UNEQUAL
+
+function describe (value) {
+ return new BooleanValue(value)
+}
+exports.describe = describe
+
+exports.deserialize = describe
+
+const tag = Symbol('BooleanValue')
+exports.tag = tag
+
+class BooleanValue {
+ constructor (value) {
+ this.value = value
+ }
+
+ compare (expected) {
+ return this.tag === expected.tag && this.value === expected.value
+ ? DEEP_EQUAL
+ : UNEQUAL
+ }
+
+ formatDeep (theme) {
+ return lineBuilder.single(formatUtils.wrap(theme.boolean, this.value === true ? 'true' : 'false'))
+ }
+
+ serialize () {
+ return this.value
+ }
+}
+Object.defineProperty(BooleanValue.prototype, 'isPrimitive', { value: true })
+Object.defineProperty(BooleanValue.prototype, 'tag', { value: tag })
diff --git a/node_modules/concordance/lib/primitiveValues/null.js b/node_modules/concordance/lib/primitiveValues/null.js
new file mode 100644
index 000000000..9436ed9a1
--- /dev/null
+++ b/node_modules/concordance/lib/primitiveValues/null.js
@@ -0,0 +1,32 @@
+'use strict'
+
+const constants = require('../constants')
+const formatUtils = require('../formatUtils')
+const lineBuilder = require('../lineBuilder')
+
+const DEEP_EQUAL = constants.DEEP_EQUAL
+const UNEQUAL = constants.UNEQUAL
+
+function describe () {
+ return new NullValue()
+}
+exports.describe = describe
+
+exports.deserialize = describe
+
+const tag = Symbol('NullValue')
+exports.tag = tag
+
+class NullValue {
+ compare (expected) {
+ return expected.tag === tag
+ ? DEEP_EQUAL
+ : UNEQUAL
+ }
+
+ formatDeep (theme) {
+ return lineBuilder.single(formatUtils.wrap(theme.null, 'null'))
+ }
+}
+Object.defineProperty(NullValue.prototype, 'isPrimitive', { value: true })
+Object.defineProperty(NullValue.prototype, 'tag', { value: tag })
diff --git a/node_modules/concordance/lib/primitiveValues/number.js b/node_modules/concordance/lib/primitiveValues/number.js
new file mode 100644
index 000000000..d1dec8edb
--- /dev/null
+++ b/node_modules/concordance/lib/primitiveValues/number.js
@@ -0,0 +1,41 @@
+'use strict'
+
+const constants = require('../constants')
+const formatUtils = require('../formatUtils')
+const lineBuilder = require('../lineBuilder')
+
+const DEEP_EQUAL = constants.DEEP_EQUAL
+const UNEQUAL = constants.UNEQUAL
+
+function describe (value) {
+ return new NumberValue(value)
+}
+exports.describe = describe
+
+exports.deserialize = describe
+
+const tag = Symbol('NumberValue')
+exports.tag = tag
+
+class NumberValue {
+ constructor (value) {
+ this.value = value
+ }
+
+ compare (expected) {
+ return expected.tag === tag && Object.is(this.value, expected.value)
+ ? DEEP_EQUAL
+ : UNEQUAL
+ }
+
+ formatDeep (theme) {
+ const string = Object.is(this.value, -0) ? '-0' : String(this.value)
+ return lineBuilder.single(formatUtils.wrap(theme.number, string))
+ }
+
+ serialize () {
+ return this.value
+ }
+}
+Object.defineProperty(NumberValue.prototype, 'isPrimitive', { value: true })
+Object.defineProperty(NumberValue.prototype, 'tag', { value: tag })
diff --git a/node_modules/concordance/lib/primitiveValues/string.js b/node_modules/concordance/lib/primitiveValues/string.js
new file mode 100644
index 000000000..af120022f
--- /dev/null
+++ b/node_modules/concordance/lib/primitiveValues/string.js
@@ -0,0 +1,306 @@
+'use strict'
+
+const fastDiff = require('fast-diff')
+const keyword = require('esutils').keyword
+
+const constants = require('../constants')
+const formatUtils = require('../formatUtils')
+const lineBuilder = require('../lineBuilder')
+
+const DEEP_EQUAL = constants.DEEP_EQUAL
+const UNEQUAL = constants.UNEQUAL
+
+function describe (value) {
+ return new StringValue(value)
+}
+exports.describe = describe
+
+exports.deserialize = describe
+
+const tag = Symbol('StringValue')
+exports.tag = tag
+
+// TODO: Escape invisible characters (e.g. zero-width joiner, non-breaking space),
+// ambiguous characters (other kinds of spaces, combining characters). Use
+// http://graphemica.com/blocks/control-pictures where applicable.
+function basicEscape (string) {
+ return string.replace(/\\/g, '\\\\')
+}
+
+const CRLF_CONTROL_PICTURE = '\u240D\u240A'
+const LF_CONTROL_PICTURE = '\u240A'
+const CR_CONTROL_PICTURE = '\u240D'
+
+const MATCH_CONTROL_PICTURES = new RegExp(`${CR_CONTROL_PICTURE}|${LF_CONTROL_PICTURE}|${CR_CONTROL_PICTURE}`, 'g')
+
+function escapeLinebreak (string) {
+ if (string === '\r\n') return CRLF_CONTROL_PICTURE
+ if (string === '\n') return LF_CONTROL_PICTURE
+ if (string === '\r') return CR_CONTROL_PICTURE
+ return string
+}
+
+function themeControlPictures (theme, resetWrap, str) {
+ return str.replace(MATCH_CONTROL_PICTURES, picture => {
+ return resetWrap.close + formatUtils.wrap(theme.string.controlPicture, picture) + resetWrap.open
+ })
+}
+
+const MATCH_SINGLE_QUOTE = /'/g
+const MATCH_DOUBLE_QUOTE = /"/g
+const MATCH_BACKTICKS = /`/g
+function escapeQuotes (line, string) {
+ const quote = line.escapeQuote
+ if (quote === '\'') return string.replace(MATCH_SINGLE_QUOTE, "\\'")
+ if (quote === '"') return string.replace(MATCH_DOUBLE_QUOTE, '\\"')
+ if (quote === '`') return string.replace(MATCH_BACKTICKS, '\\`')
+ return string
+}
+
+function includesLinebreaks (string) {
+ return string.includes('\r') || string.includes('\n')
+}
+
+function diffLine (theme, actual, expected) {
+ const outcome = fastDiff(actual, expected)
+
+ // TODO: Compute when line is mostly unequal (80%? 90%?) and treat it as being
+ // completely unequal.
+ const isPartiallyEqual = !(
+ (outcome.length === 2 && outcome[0][1] === actual && outcome[1][1] === expected) ||
+ // Discount line ending control pictures, which will be equal even when the
+ // rest of the line isn't.
+ (
+ outcome.length === 3 &&
+ outcome[2][0] === fastDiff.EQUAL &&
+ MATCH_CONTROL_PICTURES.test(outcome[2][1]) &&
+ outcome[0][1] + outcome[2][1] === actual &&
+ outcome[1][1] + outcome[2][1] === expected
+ )
+ )
+
+ let stringActual = ''
+ let stringExpected = ''
+
+ const noopWrap = { open: '', close: '' }
+ const deleteWrap = isPartiallyEqual ? theme.string.diff.delete : noopWrap
+ const insertWrap = isPartiallyEqual ? theme.string.diff.insert : noopWrap
+ const equalWrap = isPartiallyEqual ? theme.string.diff.equal : noopWrap
+ for (const diff of outcome) {
+ if (diff[0] === fastDiff.DELETE) {
+ stringActual += formatUtils.wrap(deleteWrap, diff[1])
+ } else if (diff[0] === fastDiff.INSERT) {
+ stringExpected += formatUtils.wrap(insertWrap, diff[1])
+ } else {
+ const string = formatUtils.wrap(equalWrap, themeControlPictures(theme, equalWrap, diff[1]))
+ stringActual += string
+ stringExpected += string
+ }
+ }
+
+ if (!isPartiallyEqual) {
+ stringActual = formatUtils.wrap(theme.string.diff.deleteLine, stringActual)
+ stringExpected = formatUtils.wrap(theme.string.diff.insertLine, stringExpected)
+ }
+
+ return [stringActual, stringExpected]
+}
+
+const LINEBREAKS = /\r\n|\r|\n/g
+
+function gatherLines (string) {
+ const lines = []
+ let prevIndex = 0
+ for (let match; (match = LINEBREAKS.exec(string)); prevIndex = match.index + match[0].length) {
+ lines.push(string.slice(prevIndex, match.index) + escapeLinebreak(match[0]))
+ }
+ lines.push(string.slice(prevIndex))
+ return lines
+}
+
+class StringValue {
+ constructor (value) {
+ this.value = value
+ }
+
+ compare (expected) {
+ return expected.tag === tag && this.value === expected.value
+ ? DEEP_EQUAL
+ : UNEQUAL
+ }
+
+ get includesLinebreaks () {
+ return includesLinebreaks(this.value)
+ }
+
+ formatDeep (theme, indent) {
+ // Escape backslashes
+ let escaped = basicEscape(this.value)
+
+ if (!this.includesLinebreaks) {
+ escaped = escapeQuotes(theme.string.line, escaped)
+ return lineBuilder.single(formatUtils.wrap(theme.string.line, formatUtils.wrap(theme.string, escaped)))
+ }
+
+ escaped = escapeQuotes(theme.string.multiline, escaped)
+ const lineStrings = gatherLines(escaped).map(string => {
+ return formatUtils.wrap(theme.string, themeControlPictures(theme, theme.string, string))
+ })
+ const lastIndex = lineStrings.length - 1
+ const indentation = indent
+ return lineBuilder.buffer()
+ .append(
+ lineStrings.map((string, index) => {
+ if (index === 0) return lineBuilder.first(theme.string.multiline.start + string)
+ if (index === lastIndex) return lineBuilder.last(indentation + string + theme.string.multiline.end)
+ return lineBuilder.line(indentation + string)
+ }))
+ }
+
+ formatAsKey (theme) {
+ const key = this.value
+ if (keyword.isIdentifierNameES6(key, true) || String(parseInt(key, 10)) === key) {
+ return key
+ }
+
+ const escaped = basicEscape(key)
+ .replace(/\n/g, '\\n')
+ .replace(/\r/g, '\\r')
+ .replace(/'/g, "\\'")
+ return formatUtils.wrap(theme.string.line, formatUtils.wrap(theme.string, escaped))
+ }
+
+ diffDeep (expected, theme, indent) {
+ if (expected.tag !== tag) return null
+
+ const escapedActual = basicEscape(this.value)
+ const escapedExpected = basicEscape(expected.value)
+
+ if (!includesLinebreaks(escapedActual) && !includesLinebreaks(escapedExpected)) {
+ const result = diffLine(theme,
+ escapeQuotes(theme.string.line, escapedActual),
+ escapeQuotes(theme.string.line, escapedExpected))
+
+ return lineBuilder.actual.single(formatUtils.wrap(theme.string.line, result[0]))
+ .concat(lineBuilder.expected.single(formatUtils.wrap(theme.string.line, result[1])))
+ }
+
+ const actualLines = gatherLines(escapeQuotes(theme.string.multiline, escapedActual))
+ const expectedLines = gatherLines(escapeQuotes(theme.string.multiline, escapedExpected))
+
+ const indentation = indent
+ const lines = lineBuilder.buffer()
+ const lastActualIndex = actualLines.length - 1
+ const lastExpectedIndex = expectedLines.length - 1
+
+ let actualBuffer = []
+ let expectedBuffer = []
+ let mustOpenNextExpected = false
+ for (let actualIndex = 0, expectedIndex = 0, extraneousOffset = 0; actualIndex < actualLines.length;) {
+ if (actualLines[actualIndex] === expectedLines[expectedIndex]) {
+ lines.append(actualBuffer)
+ lines.append(expectedBuffer)
+ actualBuffer = []
+ expectedBuffer = []
+
+ let string = actualLines[actualIndex]
+ string = themeControlPictures(theme, theme.string.diff.equal, string)
+ string = formatUtils.wrap(theme.string.diff.equal, string)
+
+ if (actualIndex === 0) {
+ lines.append(lineBuilder.first(theme.string.multiline.start + string))
+ } else if (actualIndex === lastActualIndex && expectedIndex === lastExpectedIndex) {
+ lines.append(lineBuilder.last(indentation + string + theme.string.multiline.end))
+ } else {
+ lines.append(lineBuilder.line(indentation + string))
+ }
+
+ actualIndex++
+ expectedIndex++
+ continue
+ }
+
+ let expectedIsMissing = false
+ {
+ const compare = actualLines[actualIndex]
+ for (let index = expectedIndex; !expectedIsMissing && index < expectedLines.length; index++) {
+ expectedIsMissing = compare === expectedLines[index]
+ }
+ }
+
+ let actualIsExtraneous = (actualIndex - extraneousOffset) > lastExpectedIndex
+ if (!actualIsExtraneous) {
+ const compare = expectedLines[expectedIndex]
+ for (let index = actualIndex; !actualIsExtraneous && index < actualLines.length; index++) {
+ actualIsExtraneous = compare === actualLines[index]
+ }
+
+ if (!actualIsExtraneous && (actualIndex - extraneousOffset) === lastExpectedIndex && actualIndex < lastActualIndex) {
+ actualIsExtraneous = true
+ }
+ }
+
+ if (actualIsExtraneous && !expectedIsMissing) {
+ const string = formatUtils.wrap(theme.string.diff.deleteLine, actualLines[actualIndex])
+
+ if (actualIndex === 0) {
+ actualBuffer.push(lineBuilder.actual.first(theme.string.multiline.start + string))
+ mustOpenNextExpected = true
+ } else if (actualIndex === lastActualIndex) {
+ actualBuffer.push(lineBuilder.actual.last(indentation + string + theme.string.multiline.end))
+ } else {
+ actualBuffer.push(lineBuilder.actual.line(indentation + string))
+ }
+
+ actualIndex++
+ extraneousOffset++
+ } else if (expectedIsMissing && !actualIsExtraneous) {
+ const string = formatUtils.wrap(theme.string.diff.insertLine, expectedLines[expectedIndex])
+
+ if (mustOpenNextExpected) {
+ expectedBuffer.push(lineBuilder.expected.first(theme.string.multiline.start + string))
+ mustOpenNextExpected = false
+ } else if (expectedIndex === lastExpectedIndex) {
+ expectedBuffer.push(lineBuilder.expected.last(indentation + string + theme.string.multiline.end))
+ } else {
+ expectedBuffer.push(lineBuilder.expected.line(indentation + string))
+ }
+
+ expectedIndex++
+ } else {
+ const result = diffLine(theme, actualLines[actualIndex], expectedLines[expectedIndex])
+
+ if (actualIndex === 0) {
+ actualBuffer.push(lineBuilder.actual.first(theme.string.multiline.start + result[0]))
+ mustOpenNextExpected = true
+ } else if (actualIndex === lastActualIndex) {
+ actualBuffer.push(lineBuilder.actual.last(indentation + result[0] + theme.string.multiline.end))
+ } else {
+ actualBuffer.push(lineBuilder.actual.line(indentation + result[0]))
+ }
+
+ if (mustOpenNextExpected) {
+ expectedBuffer.push(lineBuilder.expected.first(theme.string.multiline.start + result[1]))
+ mustOpenNextExpected = false
+ } else if (expectedIndex === lastExpectedIndex) {
+ expectedBuffer.push(lineBuilder.expected.last(indentation + result[1] + theme.string.multiline.end))
+ } else {
+ expectedBuffer.push(lineBuilder.expected.line(indentation + result[1]))
+ }
+
+ actualIndex++
+ expectedIndex++
+ }
+ }
+
+ lines.append(actualBuffer)
+ lines.append(expectedBuffer)
+ return lines
+ }
+
+ serialize () {
+ return this.value
+ }
+}
+Object.defineProperty(StringValue.prototype, 'isPrimitive', { value: true })
+Object.defineProperty(StringValue.prototype, 'tag', { value: tag })
diff --git a/node_modules/concordance/lib/primitiveValues/symbol.js b/node_modules/concordance/lib/primitiveValues/symbol.js
new file mode 100644
index 000000000..3778b4118
--- /dev/null
+++ b/node_modules/concordance/lib/primitiveValues/symbol.js
@@ -0,0 +1,114 @@
+'use strict'
+
+const stringEscape = require('js-string-escape')
+const wellKnownSymbols = require('well-known-symbols')
+
+const constants = require('../constants')
+const formatUtils = require('../formatUtils')
+const lineBuilder = require('../lineBuilder')
+
+const DEEP_EQUAL = constants.DEEP_EQUAL
+const UNEQUAL = constants.UNEQUAL
+
+function describe (value) {
+ let stringCompare = null
+
+ const key = Symbol.keyFor(value)
+ if (key !== undefined) {
+ stringCompare = `Symbol.for(${stringEscape(key)})`
+ } else if (wellKnownSymbols.isWellKnown(value)) {
+ stringCompare = wellKnownSymbols.getLabel(value)
+ }
+
+ return new SymbolValue({
+ stringCompare,
+ value
+ })
+}
+exports.describe = describe
+
+function deserialize (state) {
+ const stringCompare = state[0]
+ const string = state[1] || state[0]
+
+ return new DeserializedSymbolValue({
+ string,
+ stringCompare,
+ value: null
+ })
+}
+exports.deserialize = deserialize
+
+const tag = Symbol('SymbolValue')
+exports.tag = tag
+
+class SymbolValue {
+ constructor (props) {
+ this.stringCompare = props.stringCompare
+ this.value = props.value
+ }
+
+ compare (expected) {
+ if (expected.tag !== tag) return UNEQUAL
+
+ if (this.stringCompare !== null) {
+ return this.stringCompare === expected.stringCompare
+ ? DEEP_EQUAL
+ : UNEQUAL
+ }
+
+ return this.value === expected.value
+ ? DEEP_EQUAL
+ : UNEQUAL
+ }
+
+ formatString () {
+ if (this.stringCompare !== null) return this.stringCompare
+ return stringEscape(this.value.toString())
+ }
+
+ formatDeep (theme) {
+ return lineBuilder.single(formatUtils.wrap(theme.symbol, this.formatString()))
+ }
+
+ formatAsKey (theme) {
+ return formatUtils.wrap(theme.property.keyBracket, formatUtils.wrap(theme.symbol, this.formatString()))
+ }
+
+ serialize () {
+ const string = this.formatString()
+ return this.stringCompare === string
+ ? [this.stringCompare]
+ : [this.stringCompare, string]
+ }
+}
+Object.defineProperty(SymbolValue.prototype, 'isPrimitive', { value: true })
+Object.defineProperty(SymbolValue.prototype, 'tag', { value: tag })
+
+class DeserializedSymbolValue extends SymbolValue {
+ constructor (props) {
+ super(props)
+ this.string = props.string
+ }
+
+ compare (expected) {
+ if (expected.tag !== tag) return UNEQUAL
+
+ if (this.stringCompare !== null) {
+ return this.stringCompare === expected.stringCompare
+ ? DEEP_EQUAL
+ : UNEQUAL
+ }
+
+ // Symbols that are not in the global symbol registry, and are not
+ // well-known, cannot be compared when deserialized. Treat symbols
+ // as equal if they are formatted the same.
+ return this.string === expected.formatString()
+ ? DEEP_EQUAL
+ : UNEQUAL
+ }
+
+ formatString () {
+ return this.string
+ }
+}
diff --git a/node_modules/concordance/lib/primitiveValues/undefined.js b/node_modules/concordance/lib/primitiveValues/undefined.js
new file mode 100644
index 000000000..507556e61
--- /dev/null
+++ b/node_modules/concordance/lib/primitiveValues/undefined.js
@@ -0,0 +1,32 @@
+'use strict'
+
+const constants = require('../constants')
+const formatUtils = require('../formatUtils')
+const lineBuilder = require('../lineBuilder')
+
+const DEEP_EQUAL = constants.DEEP_EQUAL
+const UNEQUAL = constants.UNEQUAL
+
+function describe () {
+ return new UndefinedValue()
+}
+exports.describe = describe
+
+exports.deserialize = describe
+
+const tag = Symbol('UndefinedValue')
+exports.tag = tag
+
+class UndefinedValue {
+ compare (expected) {
+ return expected.tag === tag
+ ? DEEP_EQUAL
+ : UNEQUAL
+ }
+
+ formatDeep (theme) {
+ return lineBuilder.single(formatUtils.wrap(theme.undefined, 'undefined'))
+ }
+}
+Object.defineProperty(UndefinedValue.prototype, 'isPrimitive', { value: true })
+Object.defineProperty(UndefinedValue.prototype, 'tag', { value: tag })
diff --git a/node_modules/concordance/lib/recursorUtils.js b/node_modules/concordance/lib/recursorUtils.js
new file mode 100644
index 000000000..ff639b5b8
--- /dev/null
+++ b/node_modules/concordance/lib/recursorUtils.js
@@ -0,0 +1,110 @@
+'use strict'
+
+const NOOP_RECURSOR = {
+ size: 0,
+ next () { return null }
+}
+exports.NOOP_RECURSOR = NOOP_RECURSOR
+
+function fork (recursor) {
+ const buffer = []
+
+ return {
+ shared () {
+ const next = recursor()
+ if (next !== null) buffer.push(next)
+ return next
+ },
+
+ recursor () {
+ if (buffer.length > 0) return buffer.shift()
+ return recursor()
+ }
+ }
+}
+exports.fork = fork
+
+function map (recursor, mapFn) {
+ return () => {
+ const next = recursor()
+ if (next === null) return null
+
+ return mapFn(next)
+ }
+}
+exports.map = map
+
+function replay (state, create) {
+ if (!state) {
+ const recursor = create()
+ if (recursor === NOOP_RECURSOR) {
+ state = recursor
+ } else {
+ state = Object.assign({
+ buffer: [],
+ done: false
+ }, recursor)
+ }
+ }
+
+ if (state === NOOP_RECURSOR) return {state, recursor: state}
+
+ let done = false
+ let index = 0
+ const next = () => {
+ if (done) return null
+
+ let retval = state.buffer[index]
+ if (retval === undefined) {
+ retval = state.buffer[index] = state.next()
+ }
+
+ index++
+ if (retval === null) {
+ done = true
+ }
+ return retval
+ }
+
+ return {state, recursor: {next, size: state.size}}
+}
+exports.replay = replay
+
+function sequence (first, second) {
+ let fromFirst = true
+ return () => {
+ if (fromFirst) {
+ const next = first()
+ if (next !== null) return next
+
+ fromFirst = false
+ }
+
+ return second()
+ }
+}
+exports.sequence = sequence
+
+function singleValue (value) {
+ let done = false
+ return () => {
+ if (done) return null
+
+ done = true
+ return value
+ }
+}
+exports.singleValue = singleValue
+
+function unshift (recursor, value) {
+ return () => {
+ if (value !== null) {
+ const next = value
+ value = null
+ return next
+ }
+
+ return recursor()
+ }
+}
+exports.unshift = unshift
diff --git a/node_modules/concordance/lib/serialize.js b/node_modules/concordance/lib/serialize.js
new file mode 100644
index 000000000..07f568415
--- /dev/null
+++ b/node_modules/concordance/lib/serialize.js
@@ -0,0 +1,339 @@
+'use strict'
+
+const md5hex = require('md5-hex')
+
+const encoder = require('./encoder')
+const pluginRegistry = require('./pluginRegistry')
+
+const argumentsValue = require('./complexValues/arguments')
+const arrayBufferValue = require('./complexValues/arrayBuffer')
+const boxedValue = require('./complexValues/boxed')
+const dataViewValue = require('./complexValues/dataView')
+const dateValue = require('./complexValues/date')
+const errorValue = require('./complexValues/error')
+const functionValue = require('./complexValues/function')
+const globalValue = require('./complexValues/global')
+const mapValue = require('./complexValues/map')
+const objectValue = require('./complexValues/object')
+const promiseValue = require('./complexValues/promise')
+const regexpValue = require('./complexValues/regexp')
+const setValue = require('./complexValues/set')
+const typedArrayValue = require('./complexValues/typedArray')
+
+const itemDescriptor = require('./metaDescriptors/item')
+const mapEntryDescriptor = require('./metaDescriptors/mapEntry')
+const pointerDescriptor = require('./metaDescriptors/pointer')
+const propertyDescriptor = require('./metaDescriptors/property')
+const statsDescriptors = require('./metaDescriptors/stats')
+
+const booleanValue = require('./primitiveValues/boolean')
+const nullValue = require('./primitiveValues/null')
+const numberValue = require('./primitiveValues/number')
+const stringValue = require('./primitiveValues/string')
+const symbolValue = require('./primitiveValues/symbol')
+const undefinedValue = require('./primitiveValues/undefined')
+
+// Increment if encoding layout, descriptor IDs, or value types change. Previous
+// Concordance versions will not be able to decode buffers generated by a newer
+// version, so changing this value will require a major version bump of
+// Concordance itself. The version is encoded as an unsigned 16 bit integer.
+const VERSION = 2
+
+// Adding or removing mappings or changing an index requires the version in
+// encoder.js to be bumped, which necessitates a major version bump of
+// Concordance itself. Indexes are hexadecimal to make reading the binary
+// output easier.
+const mappings = [
+ [0x01, booleanValue.tag, booleanValue.deserialize],
+ [0x02, nullValue.tag, nullValue.deserialize],
+ [0x03, numberValue.tag, numberValue.deserialize],
+ [0x04, stringValue.tag, stringValue.deserialize],
+ [0x05, symbolValue.tag, symbolValue.deserialize],
+ [0x06, undefinedValue.tag, undefinedValue.deserialize],
+
+ [0x07, objectValue.tag, objectValue.deserialize],
+ [0x08, statsDescriptors.iterableTag, statsDescriptors.deserializeIterableStats],
+ [0x09, statsDescriptors.listTag, statsDescriptors.deserializeListStats],
+ [0x0A, itemDescriptor.complexTag, itemDescriptor.deserializeComplex],
+ [0x0B, itemDescriptor.primitiveTag, itemDescriptor.deserializePrimitive],
+ [0x0C, statsDescriptors.propertyTag, statsDescriptors.deserializePropertyStats],
+ [0x0D, propertyDescriptor.complexTag, propertyDescriptor.deserializeComplex],
+ [0x0E, propertyDescriptor.primitiveTag, propertyDescriptor.deserializePrimitive],
+ [0x0F, pointerDescriptor.tag, pointerDescriptor.deserialize],
+
+ [0x10, mapValue.tag, mapValue.deserialize],
+ [0x11, mapEntryDescriptor.tag, mapEntryDescriptor.deserialize],
+
+ [0x12, argumentsValue.tag, argumentsValue.deserialize],
+ [0x13, arrayBufferValue.tag, arrayBufferValue.deserialize],
+ [0x14, boxedValue.tag, boxedValue.deserialize],
+ [0x15, dataViewValue.tag, dataViewValue.deserialize],
+ [0x16, dateValue.tag, dateValue.deserialize],
+ [0x17, errorValue.tag, errorValue.deserialize],
+ [0x18, functionValue.tag, functionValue.deserialize],
+ [0x19, globalValue.tag, globalValue.deserialize],
+ [0x1A, promiseValue.tag, promiseValue.deserialize],
+ [0x1B, regexpValue.tag, regexpValue.deserialize],
+ [0x1C, setValue.tag, setValue.deserialize],
+ [0x1D, typedArrayValue.tag, typedArrayValue.deserialize],
+ [0x1E, typedArrayValue.bytesTag, typedArrayValue.deserializeBytes]
+]
+const tag2id = new Map(mappings.map(mapping => [mapping[1], mapping[0]]))
+const id2deserialize = new Map(mappings.map(mapping => [mapping[0], mapping[2]]))
+
+class DescriptorSerializationError extends Error {
+ constructor (descriptor) {
+ super('Could not serialize descriptor')
+ this.name = 'DescriptorSerializationError'
+ this.descriptor = descriptor
+ }
+}
+
+class MissingPluginError extends Error {
+ constructor (pluginName) {
+ super(`Could not deserialize buffer: missing plugin ${JSON.stringify(pluginName)}`)
+ this.name = 'MissingPluginError'
+ this.pluginName = pluginName
+ }
+}
+
+class PointerLookupError extends Error {
+ constructor (index) {
+ super(`Could not deserialize buffer: pointer ${index} could not be resolved`)
+ this.name = 'PointerLookupError'
+ this.index = index
+ }
+}
+
+class UnsupportedPluginError extends Error {
+ constructor (pluginName, serializerVersion) {
+ super(`Could not deserialize buffer: plugin ${JSON.stringify(pluginName)} expects a different serialization`)
+ this.name = 'UnsupportedPluginError'
+ this.pluginName = pluginName
+ this.serializerVersion = serializerVersion
+ }
+}
+
+class UnsupportedVersion extends Error {
+ constructor (serializerVersion) {
+ super('Could not deserialize buffer: a different serialization was expected')
+ this.name = 'UnsupportedVersion'
+ this.serializerVersion = serializerVersion
+ }
+}
+
+function shallowSerializeDescriptor (descriptor, resolvePluginRef) {
+ if (!descriptor.serialize) return undefined
+
+ return serializeState(descriptor.serialize(), resolvePluginRef)
+}
+
+function serializeState (state, resolvePluginRef) {
+ if (Array.isArray(state)) return state.map(serializeState)
+
+ if (state && state.tag) {
+ let id, pluginIndex
+ if (tag2id.has(state.tag)) {
+ id = tag2id.get(state.tag)
+ pluginIndex = 0
+ } else {
+ const ref = resolvePluginRef(state.tag)
+ if (ref) {
+ id = ref.id
+ pluginIndex = ref.pluginIndex
+ }
+ }
+
+ if (id !== undefined) {
+ const serialized = [pluginIndex, id, shallowSerializeDescriptor(state, resolvePluginRef)]
+ serialized[encoder.descriptorSymbol] = true
+ return serialized
+ }
+ }
+
+ return state
+}
+
+function serialize (descriptor) {
+ const usedPlugins = new Map()
+ const resolvePluginRef = tag => {
+ const ref = pluginRegistry.resolveDescriptorRef(tag)
+ if (!ref) return null
+
+ if (!usedPlugins.has(ref.name)) {
+ // Start at 1, since 0 is reserved for Concordance's descriptors.
+ const index = usedPlugins.size + 1
+ usedPlugins.set(ref.name, Object.assign({index}, ref.serialization))
+ }
+
+ return {
+ id: ref.id,
+ pluginIndex: usedPlugins.get(ref.name).index
+ }
+ }
+
+ const seen = new Set()
+
+ const stack = []
+ let topIndex = -1
+
+ let rootRecord
+ do {
+ if (descriptor.isComplex === true) {
+ if (seen.has(descriptor.pointer)) {
+ descriptor = pointerDescriptor.describe(descriptor.pointer)
+ } else {
+ seen.add(descriptor.pointer)
+ }
+ }
+
+ let id
+ let pluginIndex = 0
+ if (tag2id.has(descriptor.tag)) {
+ id = tag2id.get(descriptor.tag)
+ } else {
+ const ref = resolvePluginRef(descriptor.tag)
+ if (!ref) throw new DescriptorSerializationError(descriptor)
+
+ id = ref.id
+ pluginIndex = ref.pluginIndex
+ }
+
+ const record = {
+ id,
+ pluginIndex,
+ children: [],
+ state: shallowSerializeDescriptor(descriptor, resolvePluginRef)
+ }
+ if (!rootRecord) {
+ rootRecord = record
+ } else {
+ stack[topIndex].children.push(record)
+ }
+
+ if (descriptor.createRecursor) {
+ stack.push({ recursor: descriptor.createRecursor(), children: record.children })
+ topIndex++
+ }
+
+ while (topIndex >= 0) {
+ descriptor = stack[topIndex].recursor()
+ if (descriptor === null) {
+ stack.pop()
+ topIndex--
+ } else {
+ break
+ }
+ }
+ } while (topIndex >= 0)
+
+ return encoder.encode(VERSION, rootRecord, usedPlugins)
+}
+exports.serialize = serialize
+
+function deserializeState (state, getDescriptorDeserializer) {
+ if (state && state[encoder.descriptorSymbol] === true) {
+ return shallowDeserializeDescriptor(state, getDescriptorDeserializer)
+ }
+
+ return Array.isArray(state)
+ ? state.map(item => deserializeState(item, getDescriptorDeserializer))
+ : state
+}
+
+function shallowDeserializeDescriptor (entry, getDescriptorDeserializer) {
+ const deserializeDescriptor = getDescriptorDeserializer(entry[0], entry[1])
+ return deserializeDescriptor(entry[2])
+}
+
+function deserializeRecord (record, getDescriptorDeserializer, buffer) {
+ const deserializeDescriptor = getDescriptorDeserializer(record.pluginIndex, record.id)
+ const state = deserializeState(record.state, getDescriptorDeserializer)
+
+ if (record.pointerAddresses.length === 0) {
+ return deserializeDescriptor(state)
+ }
+
+ const endIndex = record.pointerAddresses.length
+ let index = 0
+ const recursor = () => {
+ if (index === endIndex) return null
+
+ const recursorRecord = encoder.decodeRecord(buffer, record.pointerAddresses[index++])
+ return deserializeRecord(recursorRecord, getDescriptorDeserializer, buffer)
+ }
+
+ return deserializeDescriptor(state, recursor)
+}
+
+function buildPluginMap (buffer, options) {
+ const cache = options && options.deserializedPluginsCache
+ const cacheKey = md5hex(buffer)
+ if (cache && cache.has(cacheKey)) return cache.get(cacheKey)
+
+ const decodedPlugins = encoder.decodePlugins(buffer)
+ if (decodedPlugins.size === 0) {
+ const pluginMap = new Map()
+ if (cache) cache.set(cacheKey, pluginMap)
+ return pluginMap
+ }
+
+ const deserializerLookup = new Map()
+ if (Array.isArray(options && options.plugins)) {
+ for (const deserializer of pluginRegistry.getDeserializers(options.plugins)) {
+ deserializerLookup.set(deserializer.name, deserializer)
+ }
+ }
+
+ const pluginMap = new Map()
+ for (const index of decodedPlugins.keys()) {
+ const used = decodedPlugins.get(index)
+ const pluginName = used.name
+ const serializerVersion = used.serializerVersion
+
+ // TODO: Allow plugin author to encode a helpful message in its serialization
+ if (!deserializerLookup.has(pluginName)) {
+ throw new MissingPluginError(pluginName)
+ }
+ if (serializerVersion !== deserializerLookup.get(pluginName).serializerVersion) {
+ throw new UnsupportedPluginError(pluginName, serializerVersion)
+ }
+
+ pluginMap.set(index, deserializerLookup.get(pluginName).id2deserialize)
+ }
+
+ if (cache) cache.set(cacheKey, pluginMap)
+ return pluginMap
+}
+
+function deserialize (buffer, options) {
+ const version = encoder.extractVersion(buffer)
+ if (version !== VERSION) throw new UnsupportedVersion(version)
+
+ const decoded = encoder.decode(buffer)
+ const pluginMap = buildPluginMap(decoded.pluginBuffer, options)
+
+ const descriptorsByPointerIndex = new Map()
+ const mapPointerDescriptor = descriptor => {
+ if (descriptor.isPointer === true) {
+ if (!descriptorsByPointerIndex.has(descriptor.index)) throw new PointerLookupError(descriptor.index)
+
+ return descriptorsByPointerIndex.get(descriptor.index)
+ } else if (descriptor.isComplex === true) {
+ descriptorsByPointerIndex.set(descriptor.pointer, descriptor)
+ }
+ return descriptor
+ }
+
+ const getDescriptorDeserializer = (pluginIndex, id) => {
+ return (state, recursor) => {
+ const deserializeDescriptor = pluginIndex === 0
+ ? id2deserialize.get(id)
+ : pluginMap.get(pluginIndex).get(id)
+
+ return mapPointerDescriptor(deserializeDescriptor(state, recursor))
+ }
+ }
+ return deserializeRecord(decoded.rootRecord, getDescriptorDeserializer, buffer)
+}
+exports.deserialize = deserialize
diff --git a/node_modules/concordance/lib/shouldCompareDeep.js b/node_modules/concordance/lib/shouldCompareDeep.js
new file mode 100644
index 000000000..a59135507
--- /dev/null
+++ b/node_modules/concordance/lib/shouldCompareDeep.js
@@ -0,0 +1,17 @@
+'use strict'
+
+const argumentsObject = require('./complexValues/arguments').tag
+const constants = require('./constants')
+
+const AMBIGUOUS = constants.AMBIGUOUS
+const SHALLOW_EQUAL = constants.SHALLOW_EQUAL
+
+function shouldCompareDeep (result, lhs, rhs) {
+ if (result === SHALLOW_EQUAL) return true
+ if (result !== AMBIGUOUS) return false
+
+ // Properties are only ambiguous if they have symbol keys. These properties
+ // must be compared in an order-insensitive manner.
+ return lhs.tag === argumentsObject || lhs.isProperty === true
+}
+module.exports = shouldCompareDeep
diff --git a/node_modules/concordance/lib/symbolProperties.js b/node_modules/concordance/lib/symbolProperties.js
new file mode 100644
index 000000000..623a428fc
--- /dev/null
+++ b/node_modules/concordance/lib/symbolProperties.js
@@ -0,0 +1,106 @@
+'use strict'
+
+const constants = require('./constants')
+const recursorUtils = require('./recursorUtils')
+
+const DEEP_EQUAL = constants.DEEP_EQUAL
+const SHALLOW_EQUAL = constants.SHALLOW_EQUAL
+const UNEQUAL = constants.UNEQUAL
+
+class Comparable {
+ constructor (properties) {
+ this.properties = properties
+ this.ordered = properties.slice()
+ }
+
+ createRecursor () {
+ const length = this.ordered.length
+ let index = 0
+ return () => {
+ if (index === length) return null
+
+ return this.ordered[index++]
+ }
+ }
+
+ compare (expected) {
+ if (this.properties.length !== expected.properties.length) return UNEQUAL
+
+ // Compare property keys, reordering the expected properties in the process
+ // so values can be compared if all keys are equal.
+ const ordered = []
+ const processed = new Set()
+ for (const property of this.properties) {
+ let extraneous = true
+ for (const other of expected.properties) {
+ if (processed.has(other.key)) continue
+
+ if (property.key.compare(other.key) === DEEP_EQUAL) {
+ extraneous = false
+ processed.add(other.key)
+ ordered.push(other)
+ break
+ }
+ }
+
+ if (extraneous) return UNEQUAL
+ }
+ expected.ordered = ordered
+
+ return SHALLOW_EQUAL
+ }
+
+ prepareDiff (expected) {
+ // Reorder the expected properties before recursion starts.
+ const missingProperties = []
+ const ordered = []
+ const processed = new Set()
+ for (const other of expected.properties) {
+ let missing = true
+ for (const property of this.properties) {
+ if (processed.has(property.key)) continue
+
+ if (property.key.compare(other.key) === DEEP_EQUAL) {
+ missing = false
+ processed.add(property.key)
+ ordered.push(other)
+ break
+ }
+ }
+
+ if (missing) {
+ missingProperties.push(other)
+ }
+ }
+ expected.ordered = ordered.concat(missingProperties)
+
+ return {mustRecurse: true}
+ }
+}
+Object.defineProperty(Comparable.prototype, 'isSymbolPropertiesComparable', { value: true })
+exports.Comparable = Comparable
+
+class Collector {
+ constructor (firstProperty, recursor) {
+ this.properties = [firstProperty]
+ this.recursor = recursor
+ this.remainder = null
+ }
+
+ collectAll () {
+ do {
+ const next = this.recursor()
+ if (next && next.isProperty === true) { // All properties will have symbol keys
+ this.properties.push(next)
+ } else {
+ return next
+ }
+ } while (true)
+ }
+
+ createRecursor () {
+ return recursorUtils.singleValue(new Comparable(this.properties))
+ }
+}
+Object.defineProperty(Collector.prototype, 'isSymbolPropertiesCollector', { value: true })
+exports.Collector = Collector
diff --git a/node_modules/concordance/lib/themeUtils.js b/node_modules/concordance/lib/themeUtils.js
new file mode 100644
index 000000000..a0a64b586
--- /dev/null
+++ b/node_modules/concordance/lib/themeUtils.js
@@ -0,0 +1,195 @@
+'use strict'
+
+const cloneDeep = require('lodash.clonedeep')
+const merge = require('lodash.merge')
+
+const pluginRegistry = require('./pluginRegistry')
+
+function freezeTheme (theme) {
+ const queue = [theme]
+ while (queue.length > 0) {
+ const object = queue.shift()
+ Object.freeze(object)
+
+ for (const key of Object.keys(object)) {
+ const value = object[key]
+ if (value !== null && typeof value === 'object') {
+ queue.push(value)
+ }
+ }
+ }
+
+ return theme
+}
+
+const defaultTheme = freezeTheme({
+ boolean: { open: '', close: '' },
+ circular: '[Circular]',
+ date: {
+ invalid: 'invalid',
+ value: { open: '', close: '' }
+ },
+ diffGutters: {
+ actual: '- ',
+ expected: '+ ',
+ padding: ' '
+ },
+ error: {
+ ctor: { open: '(', close: ')' },
+ name: { open: '', close: '' }
+ },
+ function: {
+ name: { open: '', close: '' },
+ stringTag: { open: '', close: '' }
+ },
+ global: { open: '', close: '' },
+ item: {
+ after: ',',
+ customFormat: null,
+ increaseValueIndent: false
+ },
+ list: { openBracket: '[', closeBracket: ']' },
+ mapEntry: {
+ after: ',',
+ separator: ' => '
+ },
+ maxDepth: '…',
+ null: { open: '', close: '' },
+ number: { open: '', close: '' },
+ object: {
+ openBracket: '{',
+ closeBracket: '}',
+ ctor: { open: '', close: '' },
+ stringTag: { open: '@', close: '' },
+ secondaryStringTag: { open: '@', close: '' }
+ },
+ property: {
+ after: ',',
+ customFormat: null,
+ keyBracket: { open: '[', close: ']' },
+ separator: ': ',
+ increaseValueIndent: false
+ },
+ regexp: {
+ source: { open: '/', close: '/' },
+ flags: { open: '', close: '' },
+ separator: '---'
+ },
+ stats: { separator: '---' },
+ string: {
+ open: '',
+ close: '',
+ line: { open: "'", close: "'", escapeQuote: "'" },
+ multiline: { start: '`', end: '`', escapeQuote: '``' },
+ controlPicture: { open: '', close: '' },
+ diff: {
+ insert: { open: '', close: '' },
+ delete: { open: '', close: '' },
+ equal: { open: '', close: '' },
+ insertLine: { open: '', close: '' },
+ deleteLine: { open: '', close: '' }
+ }
+ },
+ symbol: { open: '', close: '' },
+ typedArray: {
+ bytes: { open: '', close: '' }
+ },
+ undefined: { open: '', close: '' }
+})
+
+const pluginRefs = new Map()
+pluginRefs.count = 0
+const normalizedPluginThemes = new Map()
+function normalizePlugins (plugins) {
+ if (!Array.isArray(plugins) || plugins.length === 0) return null
+
+ const refs = []
+ const themes = []
+ for (const fromPlugin of pluginRegistry.getThemes(plugins)) {
+ if (!pluginRefs.has(fromPlugin.name)) {
+ pluginRefs.set(fromPlugin.name, pluginRefs.count++)
+ }
+
+ refs.push(pluginRefs.get(fromPlugin.name))
+ themes.push(fromPlugin.theme)
+ }
+
+ const ref = refs.join('.')
+ if (normalizedPluginThemes.has(ref)) {
+ return {
+ ref,
+ theme: normalizedPluginThemes.get(ref)
+ }
+ }
+
+ const theme = freezeTheme(themes.reduce((acc, pluginTheme) => {
+ return merge(acc, pluginTheme)
+ }, cloneDeep(defaultTheme)))
+ normalizedPluginThemes.set(ref, theme)
+ return {ref, theme}
+}
+
+const normalizedCache = new WeakMap()
+function normalize (options) {
+ options = Object.assign({plugins: [], theme: null}, options)
+
+ const normalizedPlugins = normalizePlugins(options.plugins)
+ if (!options.theme) {
+ return normalizedPlugins ? normalizedPlugins.theme : defaultTheme
+ }
+
+ const entry = normalizedCache.get(options.theme) || {theme: null, withPlugins: new Map()}
+ if (!normalizedCache.has(options.theme)) normalizedCache.set(options.theme, entry)
+
+ if (normalizedPlugins) {
+ if (entry.withPlugins.has(normalizedPlugins.ref)) {
+ return entry.withPlugins.get(normalizedPlugins.ref)
+ }
+
+ const theme = freezeTheme(merge(cloneDeep(normalizedPlugins.theme), options.theme))
+ entry.withPlugins.set(normalizedPlugins.ref, theme)
+ return theme
+ }
+
+ if (!entry.theme) {
+ entry.theme = freezeTheme(merge(cloneDeep(defaultTheme), options.theme))
+ }
+ return entry.theme
+}
+exports.normalize = normalize
+
+const modifiers = new WeakMap()
+function addModifier (descriptor, modifier) {
+ if (modifiers.has(descriptor)) {
+ modifiers.get(descriptor).add(modifier)
+ } else {
+ modifiers.set(descriptor, new Set([modifier]))
+ }
+}
+exports.addModifier = addModifier
+
+const modifierCache = new WeakMap()
+const originalCache = new WeakMap()
+function applyModifiers (descriptor, theme) {
+ if (!modifiers.has(descriptor)) return theme
+
+ return Array.from(modifiers.get(descriptor)).reduce((prev, modifier) => {
+ const cache = modifierCache.get(modifier) || new WeakMap()
+ if (!modifierCache.has(modifier)) modifierCache.set(modifier, cache)
+
+ if (cache.has(prev)) return cache.get(prev)
+
+ const modifiedTheme = cloneDeep(prev)
+ modifier(modifiedTheme)
+ freezeTheme(modifiedTheme)
+ cache.set(prev, modifiedTheme)
+ originalCache.set(modifiedTheme, theme)
+ return modifiedTheme
+ }, theme)
+}
+exports.applyModifiers = applyModifiers
+
+function applyModifiersToOriginal (descriptor, theme) {
+ return applyModifiers(descriptor, originalCache.get(theme) || theme)
+}
+exports.applyModifiersToOriginal = applyModifiersToOriginal