Skip to content
Permalink
9bfb9ba527
Switch branches/tags

Name already in use

A tag already exists with the provided branch name. Many Git commands accept both tag and branch names, so creating this branch may cause unexpected behavior. Are you sure you want to create this branch?
Go to file
 
 
Cannot retrieve contributors at this time
190 lines (154 sloc) 6.02 KB
'use strict'
const constants = require('../constants')
const formatUtils = require('../formatUtils')
const symbolPrimitive = require('../primitiveValues/symbol').tag
const recursorUtils = require('../recursorUtils')
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, invert) {
// 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, invert)
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 })