aboutsummaryrefslogtreecommitdiff
path: root/node_modules/concordance/lib/diff.js
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/diff.js
parent5634e77ad96bfe1818f6b6ee70b7379652e5487f (diff)
node_modules
Diffstat (limited to 'node_modules/concordance/lib/diff.js')
-rw-r--r--node_modules/concordance/lib/diff.js391
1 files changed, 391 insertions, 0 deletions
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