diff options
Diffstat (limited to 'node_modules/concordance/lib/primitiveValues')
6 files changed, 565 insertions, 0 deletions
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 }) |