main.js (2898B)
1 /** 2 * @license Apache-2.0 3 * 4 * Copyright (c) 2018 The Stdlib Authors. 5 * 6 * Licensed under the Apache License, Version 2.0 (the "License"); 7 * you may not use this file except in compliance with the License. 8 * You may obtain a copy of the License at 9 * 10 * http://www.apache.org/licenses/LICENSE-2.0 11 * 12 * Unless required by applicable law or agreed to in writing, software 13 * distributed under the License is distributed on an "AS IS" BASIS, 14 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 * See the License for the specific language governing permissions and 16 * limitations under the License. 17 */ 18 19 'use strict'; 20 21 // MODULES // 22 23 var objectKeys = require( '@stdlib/utils/keys' ); 24 25 26 // FUNCTIONS // 27 28 /** 29 * Tests if a value is an object. 30 * 31 * ## Notes 32 * 33 * - The function excludes `null`. 34 * 35 * @private 36 * @param {*} value - value to test 37 * @returns {boolean} boolean indicating if a value is an object 38 */ 39 function isObject( value ) { 40 var type = typeof value; 41 return ( value !== null && ( type === 'object' || type === 'function' ) ); 42 } 43 44 /** 45 * Tests if the seen array contains a search value. 46 * 47 * @private 48 * @param {Array} seen - array of seen objects 49 * @param {*} searchValue - search value 50 * @returns {boolean} boolean indicating whether array contains search value 51 */ 52 function contains( seen, searchValue ) { 53 var i; 54 for ( i = 0; i < seen.length; i++ ) { 55 if ( seen[ i ] === searchValue ) { 56 return true; 57 } 58 } 59 return false; 60 } 61 62 /** 63 * Tests if an object contains a circular reference by recursively traversing object keys. 64 * 65 * @private 66 * @param {Object} obj - object to test 67 * @param {Array} seen - array of seen objects 68 * @returns {boolean} boolean indicating whether object contains a circular reference 69 */ 70 function isCircObj( obj, seen ) { 71 var keys; 72 var val; 73 var i; 74 75 seen.push( obj ); 76 keys = objectKeys( obj ); 77 if ( keys.length === 0 ) { 78 return false; 79 } 80 for ( i = 0; i < keys.length; i++ ) { 81 val = obj[ keys[ i ] ]; 82 if ( isObject( val ) && ( contains( seen, val ) || isCircObj( val, seen ) ) ) { // eslint-disable-line max-len 83 return true; 84 } 85 } 86 seen.pop( obj ); 87 return false; 88 } 89 90 91 // MAIN // 92 93 /** 94 * Tests if an object-like value contains a circular reference. 95 * 96 * @param {*} value - value to test 97 * @returns {boolean} boolean indicating whether value is object-like and contains a circular reference 98 * 99 * @example 100 * var obj = { 101 * 'a': 'beep', 102 * 'b': { 103 * 'c': 'boop' 104 * } 105 * }; 106 * obj.b.self = obj; 107 * var bool = isCircular( obj ); 108 * // returns true 109 * 110 * @example 111 * var arr = [ 1, 2, 3 ]; 112 * arr.push( arr ); 113 * var bool = isCircular( arr ); 114 * // returns true 115 * 116 * @example 117 * var bool = isCircular( {} ); 118 * // returns false 119 * 120 * @example 121 * var bool = isCircular( null ); 122 * // returns false 123 */ 124 function isCircular( value ) { 125 if ( !isObject( value ) ) { 126 return false; 127 } 128 return isCircObj( value, [] ); 129 } 130 131 132 // EXPORTS // 133 134 module.exports = isCircular;