diff options
author | Florian Dold <florian.dold@gmail.com> | 2017-08-14 05:01:11 +0200 |
---|---|---|
committer | Florian Dold <florian.dold@gmail.com> | 2017-08-14 05:02:09 +0200 |
commit | 363723fc84f7b8477592e0105aeb331ec9a017af (patch) | |
tree | 29f92724f34131bac64d6a318dd7e30612e631c7 /node_modules/concordance/lib/diff.js | |
parent | 5634e77ad96bfe1818f6b6ee70b7379652e5487f (diff) |
node_modules
Diffstat (limited to 'node_modules/concordance/lib/diff.js')
-rw-r--r-- | node_modules/concordance/lib/diff.js | 391 |
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 |