diff options
Diffstat (limited to 'node_modules/concordance/lib/complexValues')
-rw-r--r-- | node_modules/concordance/lib/complexValues/arguments.js | 48 | ||||
-rw-r--r-- | node_modules/concordance/lib/complexValues/arrayBuffer.js | 30 | ||||
-rw-r--r-- | node_modules/concordance/lib/complexValues/boxed.js | 51 | ||||
-rw-r--r-- | node_modules/concordance/lib/complexValues/dataView.js | 29 | ||||
-rw-r--r-- | node_modules/concordance/lib/complexValues/date.js | 89 | ||||
-rw-r--r-- | node_modules/concordance/lib/complexValues/error.js | 133 | ||||
-rw-r--r-- | node_modules/concordance/lib/complexValues/function.js | 159 | ||||
-rw-r--r-- | node_modules/concordance/lib/complexValues/global.js | 33 | ||||
-rw-r--r-- | node_modules/concordance/lib/complexValues/map.js | 78 | ||||
-rw-r--r-- | node_modules/concordance/lib/complexValues/object.js | 283 | ||||
-rw-r--r-- | node_modules/concordance/lib/complexValues/promise.js | 40 | ||||
-rw-r--r-- | node_modules/concordance/lib/complexValues/regexp.js | 90 | ||||
-rw-r--r-- | node_modules/concordance/lib/complexValues/set.js | 78 | ||||
-rw-r--r-- | node_modules/concordance/lib/complexValues/typedArray.js | 162 |
14 files changed, 1303 insertions, 0 deletions
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) |