UNPKG

deep-equal-diagnostics

Version:

Ultra-fast robust diagnostic testing for strict deep equality

349 lines (304 loc) 13.3 kB
/* ISC License Copyright (c) 2022 Mark Albert Willis Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ const {dtype$} = require('type-quickly'); function propertyIsEnumerable(x,p) { return Object.prototype.propertyIsEnumerable.call(x,p) } (function() { let wmX let wmY const stopPropagation = true const COMPLETION_CODE_2 = 2 let getProperties module.exports.deepStrictEqual = deepStrictEqual function deepStrictEqual(x,y) { wmX = new WeakMap() wmY = new WeakMap() getProperties = Object.getOwnPropertyNames deepStrictEqual.message = 'Source and target are deeply strictly equal' return deepStrictEqual$(x,y,'') } function failure(x, y, path, errorNumber, message) { deepStrictEqual.pair = {source:x, target:y}; if(path === '') { deepStrictEqual.message = `Error #${errorNumber} Error-path = top, source-dtype = ${dtype$(x)}, target-dtype = ${dtype$(y)} ${message}` } else deepStrictEqual.message = ` Error# ${errorNumber} Error-path = ${path.substring(1)}, source-dtype = ${dtype$(x)}, target-dtype = ${dtype$(y)} ${message}` return false } function deepStrictEqual$(x,y,path) { if(x === null || y === null) { if(x !== null || y !== null) return failure(x,y,path,0, 'only one of source and target is null') return true } const tx = typeof x if(tx !== 'function' && tx !== 'object') { if(x !== x) { if(y===y) return failure(x,y,path,1, `source is NaN but target is not`) } else if(x === 0 && !(y === 0 && 1/x === 1/y)) return failure(x,y,path,2, 'source is -0, 0, or +0 but target is not equal to it') else if(x !== y) return failure(x,y,path,3, `source is ${tx==='symbol'?'symbol':x}, but target is not equal to it`) return stopPropagation } const ty = typeof y if(ty !== 'function' && ty !== 'object') { if(y !== y) { if(x===x) return failure(x,y,path,4, `target is NaN but target is not`) } else if(y === 0 && !(x === 0 && 1/x === 1/y)) return failure(x,y,path,5, 'target is -0, 0, or +0 but source is not') else if(x !== y) return failure(x,y,path,6, `target is ${ty==='symbol'?'symbol':y}, but source is not equal to it`) return stopPropagation } if(wmX.has(x) && wmY.has(y)) { const xPath = wmX.get(x) const yPath = wmY.get(y) if(xPath !== yPath) return failure(x,y,path,7, `source was previous seen at path ${xPath} but target was previously seen at ${yPath}`) return stopPropagation } wmX.set(x, path) wmY.set(y, path) const protoX = Object.getPrototypeOf(x) if(x === y) { return stopPropagation } const ntX = Object.prototype.toString.call(x).charAt(8) const ntY = Object.prototype.toString.call(y).charAt(8) if(ntX !== ntY) return failure(x,y,path,8, 'source and target have different native types') const protoY = Object.getPrototypeOf(y) if(protoX !== protoY) return failure(x,y,path,9, 'source and target have different internal prototypes') if(ntX !== 'O' && protoX !== null && protoX[check]) { switch(protoX[check](x,y,path)) { case false: wmX.delete(x) wmY.delete(y) return false case COMPLETION_CODE_2: wmX.delete(x) wmY.delete(y) return true } } const xProps = getProperties(x); const yProps = getProperties(y); const xLength = xProps.length; const yLength = yProps.length; if(xLength !== yLength) return failure(x,y,path,10, 'source and target have a different number of properties') for(const p of xProps) { if(!Object.prototype.hasOwnProperty.call(y, p)) return failure(x,y,path,11, `source has property ${p} but target doesn't`) if(!deepStrictEqual$(x[p],y[p],path + '.' + p)) return false if(propertyIsEnumerable(x,p) !== propertyIsEnumerable(y,p)) return failure(x,y,path,12, `property ${p} has different enumerability in source and target`) } wmX.delete(x) wmY.delete(y) return true } const check = Symbol('check') Boolean.prototype[check] = function(x,y,path) { if(x.valueOf() !== y.valueOf()) return failure(x,y,path,13, `source ${x.constructor.name} is ${x.valueOf()} but target ${x.constructor.name} is ${y.valueOf()}`) return true } Number.prototype[check] = Boolean.prototype[check] String.prototype[check] = Boolean.prototype[check] Date.prototype[check] = Boolean.prototype[check] RegExp.prototype[check] = function(x,y,path) { if(x.toString() !== y.toString()) return failure(x,y,path,14, `source RegExp is ${x.toString()} but target RegExp is ${y.toString()}`) return true } Function.prototype[check] = function(x,y,path) { if(x !== y) return failure(x,y,path,15, `source and target Functions must be reference equal`) return true } WeakSet.prototype[check] = function(x,y, path) { let xIsEdge = false let yIsEdge = false try{WeakSet.prototype.has.call(x,"value")}catch(e){xIsEdge = true} try{WeakSet.prototype.has.call(y,"value")}catch(e){yIsEdge = true} if(xIsEdge) { if(!yIsEdge) return failure(x,y,path,16, 'source is WeakSet[Object] but target is WeakSet') return true } if(yIsEdge) return failure(x,y,path,17, 'target is WeakSet[Object] but source is WeakSet') return true } WeakMap.prototype[check] = function(x,y, path) { let xIsEdge = false let yIsEdge = false try{WeakMap.prototype.has.call(x,"key")}catch(e){xIsEdge = true} try{WeakMap.prototype.has.call(y,"key")}catch(e){yIsEdge = true} if(xIsEdge) { if(!yIsEdge) return failure(x,y,path,18, 'source is WeakMap[Object] but target is WeakMap') return true } if(yIsEdge) return failure(x,y,path,19, 'target is WeakMap[Object] but source is WeakMap') return true } Set.prototype[check] = function(x,y,path) { let xIsEdge = false let yIsEdge = false try{Set.prototype.has.call(x,"key")}catch(e){xIsEdge = true} try{Set.prototype.has.call(y,"key")}catch(e){yIsEdge = true} if(xIsEdge) { if(!yIsEdge) return failure(x,y,path,20, 'source is Set[Object] but target is Set') return true } if(yIsEdge) return failure(x,y,path,21,`source and target d-types differ`) const xKeyIterator = x.keys(); const yKeyIterator = y.keys(); let n, p, q; for(n = 0, p = xKeyIterator.next(), q = yKeyIterator.next(); !p.done && !q.done; p = xKeyIterator.next(), q = yKeyIterator.next(), n++) { if(!deepStrictEqual$(p.value, q.value, `${path}[setKey[${n}]]`)) return false } if(!p.done) return failure(x,y,path,22,'source Set has more members than target Set') if(!q.done) return failure(x,y,path,23, 'source Set has fewer members than target Set') return true } Map.prototype[check] = function(x,y,path) { let xIsEdge = false let yIsEdge = false try{Map.prototype.has.call(x, {})}catch(e){xIsEdge = true} try{Map.prototype.has.call(y,{})}catch(e){yIsEdge = true} if(xIsEdge) { if(!yIsEdge) return failure(x,y,path,24, 'source is Map[Object] but target is Map') return true } if(yIsEdge) return failure(x,y,path,25,`source and target d-types differ`) const xKeyIterator = x.keys(); const yKeyIterator = y.keys(); let n, p, q; for(n = 0, p = xKeyIterator.next(), q = yKeyIterator.next(); !p.done && !q.done; p = xKeyIterator.next(), q = yKeyIterator.next(), n++) { if(!deepStrictEqual$(p.value, q.value, `$path[mapKey[${n}]]`)) return false if(!deepStrictEqual$(x.get(p.value), y.get(q.value), `$path[mapValue[${n}]]`)) return false } if(!p.done) return failure(x,y,path,26,'source Map has more key-value pairs than target Map') if(!q.done) return failure(x,y,path,27, 'source Map has fewer key-value pairs than target Map') return true } ArrayBuffer.prototype[check] = function(x,y,path) { let xIsEdge = false let yIsEdge = false try{ArrayBuffer.prototype.slice.call(x,0,0)}catch(e){xIsEdge = true} try{ArrayBuffer.prototype.slice.call(y,0,0)}catch(e){yIsEdge = true} if(xIsEdge) { if(!yIsEdge) return failure(x,y,path,28, 'source is ArrayBuffer[Object] but target is ArrayBuffer') return true } if(yIsEdge) return failure(x,y,path,29,`source and target d-types differ`) if(x.byteLength !== y.byteLength) return failure(x,y,path,30, `source.byteLength = ${x.byteLength} but target.byteLength = ${y.byteLength}`) const xView = new Uint8Array(x) const yView = new Uint8Array(y) for(let i = 0; i < xView.byteLength; i++) { if(xView[i] !== yView[i]) return failure(x,y,path,31, `source[${i}] = ${xView[i]} but target[${i}] = ${yView[i]}`) } return true } DataView.prototype[check] = function(x,y,path) { let xIsEdge = false let yIsEdge = false try{DataView.prototype.getUint8.call(x,0)}catch(e){xIsEdge = true} try{DataView.prototype.getUint8.call(y,0)}catch(e){yIsEdge = true} if(xIsEdge) { if(!yIsEdge) return failure(x,y,path,32, 'source is DataView[Object] but target is DataView') return true } if(yIsEdge) return failure(x,y,path,33,`source and target d-types differ`) if(!deepStrictEqual$(x.buffer, y.buffer, `${path}.buffer`)) { return false } if(x.byteOffset !== y.byteOffset) return failure(x,y,path,34, `source.byteOffset = ${x.byteOffset} but target.byteOffset = ${y.byteOffset}`) if(x.byteLength !== y.byteLength) return failure(x,y,path,35, `source.byteLength = ${x.byteLength} but target.byteLength = ${y.byteLength}`) return true } Error.prototype[check] = function(x,y,path) { const xProps = getProperties(x); const yProps = getProperties(y); const xLength = xProps.length; const yLength = yProps.length; if(xLength !== yLength) return failure(x,y,path,36, 'source and target have a different number of properties') for(const p of xProps) { if(p !== 'stack') { if(!Object.prototype.hasOwnProperty.call(y, p)) return failure(x,y,path,14, `source has property ${p} but target doesn't`) if(!deepStrictEqual$(x[p],y[p],path + '.' + p)) return false if(x.propertyIsEnumerable(p) !== y.propertyIsEnumerable(p)) return failure(x,y,path,37, `property ${p} has different enumerability in source and target`) } } return COMPLETION_CODE_2 } EvalError.prototype[check] = Error.prototype[check] RangeError.prototype[check] = Error.prototype[check] ReferenceError.prototype[check] = Error.prototype[check] SyntaxError.prototype[check] = Error.prototype[check] TypeError.prototype[check] = Error.prototype[check] URIError.prototype[check] = Error.prototype[check] })();