1// Copyright 2008 The Closure Library Authors. All Rights Reserved. 2// 3// Licensed under the Apache License, Version 2.0 (the "License"); 4// you may not use this file except in compliance with the License. 5// You may obtain a copy of the License at 6// 7// http://www.apache.org/licenses/LICENSE-2.0 8// 9// Unless required by applicable law or agreed to in writing, software 10// distributed under the License is distributed on an "AS-IS" BASIS, 11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12// See the License for the specific language governing permissions and 13// limitations under the License. 14 15/** 16 * @fileoverview Utilities to check the preconditions, postconditions and 17 * invariants runtime. 18 * 19 * Methods in this package should be given special treatment by the compiler 20 * for type-inference. For example, <code>goog.asserts.assert(foo)</code> 21 * will restrict <code>foo</code> to a truthy value. 22 * 23 * The compiler has an option to disable asserts. So code like: 24 * <code> 25 * var x = goog.asserts.assert(foo()); goog.asserts.assert(bar()); 26 * </code> 27 * will be transformed into: 28 * <code> 29 * var x = foo(); 30 * </code> 31 * The compiler will leave in foo() (because its return value is used), 32 * but it will remove bar() because it assumes it does not have side-effects. 33 * 34 */ 35 36goog.provide('goog.asserts'); 37goog.provide('goog.asserts.AssertionError'); 38 39goog.require('goog.debug.Error'); 40goog.require('goog.dom.NodeType'); 41goog.require('goog.string'); 42 43 44/** 45 * @define {boolean} Whether to strip out asserts or to leave them in. 46 */ 47goog.define('goog.asserts.ENABLE_ASSERTS', goog.DEBUG); 48 49 50 51/** 52 * Error object for failed assertions. 53 * @param {string} messagePattern The pattern that was used to form message. 54 * @param {!Array.<*>} messageArgs The items to substitute into the pattern. 55 * @constructor 56 * @extends {goog.debug.Error} 57 * @final 58 */ 59goog.asserts.AssertionError = function(messagePattern, messageArgs) { 60 messageArgs.unshift(messagePattern); 61 goog.debug.Error.call(this, goog.string.subs.apply(null, messageArgs)); 62 // Remove the messagePattern afterwards to avoid permenantly modifying the 63 // passed in array. 64 messageArgs.shift(); 65 66 /** 67 * The message pattern used to format the error message. Error handlers can 68 * use this to uniquely identify the assertion. 69 * @type {string} 70 */ 71 this.messagePattern = messagePattern; 72}; 73goog.inherits(goog.asserts.AssertionError, goog.debug.Error); 74 75 76/** @override */ 77goog.asserts.AssertionError.prototype.name = 'AssertionError'; 78 79 80/** 81 * Throws an exception with the given message and "Assertion failed" prefixed 82 * onto it. 83 * @param {string} defaultMessage The message to use if givenMessage is empty. 84 * @param {Array.<*>} defaultArgs The substitution arguments for defaultMessage. 85 * @param {string|undefined} givenMessage Message supplied by the caller. 86 * @param {Array.<*>} givenArgs The substitution arguments for givenMessage. 87 * @throws {goog.asserts.AssertionError} When the value is not a number. 88 * @private 89 */ 90goog.asserts.doAssertFailure_ = 91 function(defaultMessage, defaultArgs, givenMessage, givenArgs) { 92 var message = 'Assertion failed'; 93 if (givenMessage) { 94 message += ': ' + givenMessage; 95 var args = givenArgs; 96 } else if (defaultMessage) { 97 message += ': ' + defaultMessage; 98 args = defaultArgs; 99 } 100 // The '' + works around an Opera 10 bug in the unit tests. Without it, 101 // a stack trace is added to var message above. With this, a stack trace is 102 // not added until this line (it causes the extra garbage to be added after 103 // the assertion message instead of in the middle of it). 104 throw new goog.asserts.AssertionError('' + message, args || []); 105}; 106 107 108/** 109 * Checks if the condition evaluates to true if goog.asserts.ENABLE_ASSERTS is 110 * true. 111 * @template T 112 * @param {T} condition The condition to check. 113 * @param {string=} opt_message Error message in case of failure. 114 * @param {...*} var_args The items to substitute into the failure message. 115 * @return {T} The value of the condition. 116 * @throws {goog.asserts.AssertionError} When the condition evaluates to false. 117 */ 118goog.asserts.assert = function(condition, opt_message, var_args) { 119 if (goog.asserts.ENABLE_ASSERTS && !condition) { 120 goog.asserts.doAssertFailure_('', null, opt_message, 121 Array.prototype.slice.call(arguments, 2)); 122 } 123 return condition; 124}; 125 126 127/** 128 * Fails if goog.asserts.ENABLE_ASSERTS is true. This function is useful in case 129 * when we want to add a check in the unreachable area like switch-case 130 * statement: 131 * 132 * <pre> 133 * switch(type) { 134 * case FOO: doSomething(); break; 135 * case BAR: doSomethingElse(); break; 136 * default: goog.assert.fail('Unrecognized type: ' + type); 137 * // We have only 2 types - "default:" section is unreachable code. 138 * } 139 * </pre> 140 * 141 * @param {string=} opt_message Error message in case of failure. 142 * @param {...*} var_args The items to substitute into the failure message. 143 * @throws {goog.asserts.AssertionError} Failure. 144 */ 145goog.asserts.fail = function(opt_message, var_args) { 146 if (goog.asserts.ENABLE_ASSERTS) { 147 throw new goog.asserts.AssertionError( 148 'Failure' + (opt_message ? ': ' + opt_message : ''), 149 Array.prototype.slice.call(arguments, 1)); 150 } 151}; 152 153 154/** 155 * Checks if the value is a number if goog.asserts.ENABLE_ASSERTS is true. 156 * @param {*} value The value to check. 157 * @param {string=} opt_message Error message in case of failure. 158 * @param {...*} var_args The items to substitute into the failure message. 159 * @return {number} The value, guaranteed to be a number when asserts enabled. 160 * @throws {goog.asserts.AssertionError} When the value is not a number. 161 */ 162goog.asserts.assertNumber = function(value, opt_message, var_args) { 163 if (goog.asserts.ENABLE_ASSERTS && !goog.isNumber(value)) { 164 goog.asserts.doAssertFailure_('Expected number but got %s: %s.', 165 [goog.typeOf(value), value], opt_message, 166 Array.prototype.slice.call(arguments, 2)); 167 } 168 return /** @type {number} */ (value); 169}; 170 171 172/** 173 * Checks if the value is a string if goog.asserts.ENABLE_ASSERTS is true. 174 * @param {*} value The value to check. 175 * @param {string=} opt_message Error message in case of failure. 176 * @param {...*} var_args The items to substitute into the failure message. 177 * @return {string} The value, guaranteed to be a string when asserts enabled. 178 * @throws {goog.asserts.AssertionError} When the value is not a string. 179 */ 180goog.asserts.assertString = function(value, opt_message, var_args) { 181 if (goog.asserts.ENABLE_ASSERTS && !goog.isString(value)) { 182 goog.asserts.doAssertFailure_('Expected string but got %s: %s.', 183 [goog.typeOf(value), value], opt_message, 184 Array.prototype.slice.call(arguments, 2)); 185 } 186 return /** @type {string} */ (value); 187}; 188 189 190/** 191 * Checks if the value is a function if goog.asserts.ENABLE_ASSERTS is true. 192 * @param {*} value The value to check. 193 * @param {string=} opt_message Error message in case of failure. 194 * @param {...*} var_args The items to substitute into the failure message. 195 * @return {!Function} The value, guaranteed to be a function when asserts 196 * enabled. 197 * @throws {goog.asserts.AssertionError} When the value is not a function. 198 */ 199goog.asserts.assertFunction = function(value, opt_message, var_args) { 200 if (goog.asserts.ENABLE_ASSERTS && !goog.isFunction(value)) { 201 goog.asserts.doAssertFailure_('Expected function but got %s: %s.', 202 [goog.typeOf(value), value], opt_message, 203 Array.prototype.slice.call(arguments, 2)); 204 } 205 return /** @type {!Function} */ (value); 206}; 207 208 209/** 210 * Checks if the value is an Object if goog.asserts.ENABLE_ASSERTS is true. 211 * @param {*} value The value to check. 212 * @param {string=} opt_message Error message in case of failure. 213 * @param {...*} var_args The items to substitute into the failure message. 214 * @return {!Object} The value, guaranteed to be a non-null object. 215 * @throws {goog.asserts.AssertionError} When the value is not an object. 216 */ 217goog.asserts.assertObject = function(value, opt_message, var_args) { 218 if (goog.asserts.ENABLE_ASSERTS && !goog.isObject(value)) { 219 goog.asserts.doAssertFailure_('Expected object but got %s: %s.', 220 [goog.typeOf(value), value], 221 opt_message, Array.prototype.slice.call(arguments, 2)); 222 } 223 return /** @type {!Object} */ (value); 224}; 225 226 227/** 228 * Checks if the value is an Array if goog.asserts.ENABLE_ASSERTS is true. 229 * @param {*} value The value to check. 230 * @param {string=} opt_message Error message in case of failure. 231 * @param {...*} var_args The items to substitute into the failure message. 232 * @return {!Array} The value, guaranteed to be a non-null array. 233 * @throws {goog.asserts.AssertionError} When the value is not an array. 234 */ 235goog.asserts.assertArray = function(value, opt_message, var_args) { 236 if (goog.asserts.ENABLE_ASSERTS && !goog.isArray(value)) { 237 goog.asserts.doAssertFailure_('Expected array but got %s: %s.', 238 [goog.typeOf(value), value], opt_message, 239 Array.prototype.slice.call(arguments, 2)); 240 } 241 return /** @type {!Array} */ (value); 242}; 243 244 245/** 246 * Checks if the value is a boolean if goog.asserts.ENABLE_ASSERTS is true. 247 * @param {*} value The value to check. 248 * @param {string=} opt_message Error message in case of failure. 249 * @param {...*} var_args The items to substitute into the failure message. 250 * @return {boolean} The value, guaranteed to be a boolean when asserts are 251 * enabled. 252 * @throws {goog.asserts.AssertionError} When the value is not a boolean. 253 */ 254goog.asserts.assertBoolean = function(value, opt_message, var_args) { 255 if (goog.asserts.ENABLE_ASSERTS && !goog.isBoolean(value)) { 256 goog.asserts.doAssertFailure_('Expected boolean but got %s: %s.', 257 [goog.typeOf(value), value], opt_message, 258 Array.prototype.slice.call(arguments, 2)); 259 } 260 return /** @type {boolean} */ (value); 261}; 262 263 264/** 265 * Checks if the value is a DOM Element if goog.asserts.ENABLE_ASSERTS is true. 266 * @param {*} value The value to check. 267 * @param {string=} opt_message Error message in case of failure. 268 * @param {...*} var_args The items to substitute into the failure message. 269 * @return {!Element} The value, likely to be a DOM Element when asserts are 270 * enabled. 271 * @throws {goog.asserts.AssertionError} When the value is not a boolean. 272 */ 273goog.asserts.assertElement = function(value, opt_message, var_args) { 274 if (goog.asserts.ENABLE_ASSERTS && (!goog.isObject(value) || 275 value.nodeType != goog.dom.NodeType.ELEMENT)) { 276 goog.asserts.doAssertFailure_('Expected Element but got %s: %s.', 277 [goog.typeOf(value), value], opt_message, 278 Array.prototype.slice.call(arguments, 2)); 279 } 280 return /** @type {!Element} */ (value); 281}; 282 283 284/** 285 * Checks if the value is an instance of the user-defined type if 286 * goog.asserts.ENABLE_ASSERTS is true. 287 * 288 * The compiler may tighten the type returned by this function. 289 * 290 * @param {*} value The value to check. 291 * @param {function(new: T, ...)} type A user-defined constructor. 292 * @param {string=} opt_message Error message in case of failure. 293 * @param {...*} var_args The items to substitute into the failure message. 294 * @throws {goog.asserts.AssertionError} When the value is not an instance of 295 * type. 296 * @return {!T} 297 * @template T 298 */ 299goog.asserts.assertInstanceof = function(value, type, opt_message, var_args) { 300 if (goog.asserts.ENABLE_ASSERTS && !(value instanceof type)) { 301 goog.asserts.doAssertFailure_('instanceof check failed.', null, 302 opt_message, Array.prototype.slice.call(arguments, 3)); 303 } 304 return value; 305}; 306 307 308/** 309 * Checks that no enumerable keys are present in Object.prototype. Such keys 310 * would break most code that use {@code for (var ... in ...)} loops. 311 */ 312goog.asserts.assertObjectPrototypeIsIntact = function() { 313 for (var key in Object.prototype) { 314 goog.asserts.fail(key + ' should not be enumerable in Object.prototype.'); 315 } 316}; 317