Gitter migration: Point people to app.gitter.im (rollout pt. 1)
[gitter.git] / public / js / utils / underscore-wrapper.js
blob0f039dcffed2aa841e9cc3da0eab8c3ad8b1b0f2
1 /* eslint complexity: ["error", 31], max-depth: ["error", 5] */
2 'use strict';
4 /* This require looks HORRIBLE, but it's a way to use the non-aliased underscore */
5 /* Webpack config will alias all usages of underscore to this module */
6 /* we've globally replaced underscore with lodash v3 */
7 var _ = require('../../../node_modules/lodash');
9 var ObjProto = Object.prototype;
10 var toString = ObjProto.toString;
11 var objValueOf = ObjProto.valueOf;
13 function customValueOfFunction(f) {
14   return _.isFunction(f) && f !== objValueOf;
17 // Internal recursive comparison function for `isEqual`.
18 // eslint-disable-next-line complexity, max-statements
19 function eq(a, b, aStack, bStack) {
20   // Identical objects are equal. `0 === -0`, but they aren't identical.
21   // See the [Harmony `egal` proposal](http://wiki.ecmascript.org/doku.php?id=harmony:egal).
22   if (a === b) return a !== 0 || 1 / a === 1 / b;
23   // A strict comparison is necessary because `null == undefined`.
24   if (a == null || b == null) return a === b;
26   // Unwrap any wrapped objects.
27   if (a instanceof _) a = a._wrapped;
28   if (b instanceof _) b = b._wrapped;
30   // Compare `[[Class]]` names.
31   var className = toString.call(a);
32   if (className !== toString.call(b)) return false;
33   switch (className) {
34     // RegExps are coerced to strings for comparison.
35     case '[object RegExp]': // Strings, numbers, dates, and booleans are compared by value.
36     case '[object String]':
37       // Primitives and their corresponding object wrappers are equivalent; thus, `"5"` is
38       // equivalent to `String("5")`.
39       return '' + a === '' + b;
40     case '[object Number]':
41       // `NaN`s are equivalent, but non-reflexive.
42       if (a != +a) return b != +b;
43       // An `egal` comparison is performed for other numeric values.
44       return a == 0 ? 1 / a == 1 / b : a == +b;
45     case '[object Date]':
46     case '[object Boolean]':
47       // Coerce dates and booleans to numeric primitive values. Dates are compared by their
48       // millisecond representations. Note that invalid dates with millisecond representations
49       // of `NaN` are not equivalent.
50       return +a === +b;
51   }
53   if (typeof a !== 'object' || typeof b !== 'object') return false;
54   // Assume equality for cyclic structures. The algorithm for detecting cyclic
55   // structures is adapted from ES 5.1 section 15.12.3, abstract operation `JO`.
56   var length = aStack.length;
57   while (length--) {
58     // Linear search. Performance is inversely proportional to the number of
59     // unique nested structures.
60     if (aStack[length] === a) return bStack[length] === b;
61   }
62   // Objects with different constructors are not equivalent, but `Object`s
63   // from different frames are.
64   var aCtor = a.constructor,
65     bCtor = b.constructor;
66   if (
67     aCtor !== bCtor &&
68     'constructor' in a &&
69     'constructor' in b &&
70     !(
71       _.isFunction(aCtor) &&
72       aCtor instanceof aCtor &&
73       _.isFunction(bCtor) &&
74       bCtor instanceof bCtor
75     )
76   ) {
77     return false;
78   }
79   // Add the first object to the stack of traversed objects.
80   aStack.push(a);
81   bStack.push(b);
82   var size = 0,
83     result = true;
84   // Recursively compare objects and arrays.
85   if (className === '[object Array]') {
86     // Compare array lengths to determine if a deep comparison is necessary.
87     size = a.length;
88     result = size === b.length;
89     if (result) {
90       // Deep compare the contents, ignoring non-numeric properties.
91       while (size--) {
92         if (!(result = eq(a[size], b[size], aStack, bStack))) break;
93       }
94     }
95   } else {
96     if (customValueOfFunction(a.valueOf) && customValueOfFunction(b.valueOf)) {
97       var vA = a.valueOf();
98       var vB = b.valueOf();
100       result = eq(vA, vB, aStack, bStack);
101     } else {
102       // Deep compare objects.
103       for (var key in a) {
104         if (_.has(a, key)) {
105           // Count the expected number of properties.
106           size++;
107           // Deep compare each member.
108           if (!(result = _.has(b, key) && eq(a[key], b[key], aStack, bStack))) break;
109         }
110       }
111       // Ensure that both objects contain the same number of properties.
112       if (result) {
113         for (key in b) {
114           if (_.has(b, key) && !size--) break;
115         }
116         result = !size;
117       }
118     }
119   }
120   // Remove the first object from the stack of traversed objects.
121   aStack.pop();
122   bStack.pop();
123   return result;
126 // Perform a deep comparison to check if two objects are equal.
127 _.isEqual = function(a, b) {
128   return eq(a, b, [], []);
131 module.exports = _;