diff --git a/src/utils/OrderedMap.js b/src/utils/OrderedMap.js index 8c139263d7..57cfa54383 100644 --- a/src/utils/OrderedMap.js +++ b/src/utils/OrderedMap.js @@ -85,8 +85,6 @@ function OrderedMapImpl(normalizedObj, computedLength) { * Validates a "public" key - that is, one that the public facing API supplies. * The key is then normalized for internal storage. In order to be considered * valid, all keys must be non-empty, defined, non-null strings or numbers. - * Since this already costs a function invocation, will avoid additional call to - * `throwIf`. * * @param {string?} key Validates that the key is suitable for use in a * `OrderedMap`. diff --git a/src/utils/Transaction.js b/src/utils/Transaction.js index 8e630d88ab..861919d3d1 100644 --- a/src/utils/Transaction.js +++ b/src/utils/Transaction.js @@ -18,23 +18,7 @@ "use strict"; -var throwIf = require('throwIf'); - -var DUAL_TRANSACTION = 'DUAL_TRANSACTION'; -var MISSING_TRANSACTION = 'MISSING_TRANSACTION'; -if (__DEV__) { - DUAL_TRANSACTION = - 'Cannot initialize transaction when there is already an outstanding ' + - 'transaction. Common causes of this are trying to render a component ' + - 'when you are already rendering a component or attempting a state ' + - 'transition while in a render function. Another possibility is that ' + - 'you are rendering new content (or state transitioning) in a ' + - 'componentDidRender callback. If this is not the case, please report the ' + - 'issue immediately.'; - - MISSING_TRANSACTION = - 'Cannot close transaction when there is none open.'; -} +var invariant = require('invariant'); /** * `Transaction` creates a black box that is able to wrap any method such that @@ -156,26 +140,33 @@ var Mixin = { * @return Return value from `method`. */ perform: function(method, scope, a, b, c, d, e, f) { - throwIf(this.isInTransaction(), DUAL_TRANSACTION); + invariant( + !this.isInTransaction(), + 'Transaction.perform(...): Cannot initialize a transaction when there ' + + 'is already an outstanding transaction.' + ); var memberStart = Date.now(); - var err = null; + var errorToThrow = null; var ret; try { this.initializeAll(); ret = method.call(scope, a, b, c, d, e, f); - } catch (ie_requires_catch) { - err = ie_requires_catch; + } catch (error) { + // IE8 requires `catch` in order to use `finally`. + errorToThrow = error; } finally { var memberEnd = Date.now(); this.methodInvocationTime += (memberEnd - memberStart); try { this.closeAll(); - } catch (closeAllErr) { - err = err || closeAllErr; + } catch (closeError) { + // If `method` throws, prefer to show that stack trace over any thrown + // by invoking `closeAll`. + errorToThrow = errorToThrow || closeError; } } - if (err) { - throw err; + if (errorToThrow) { + throw errorToThrow; } return ret; }, @@ -184,15 +175,17 @@ var Mixin = { this._isInTransaction = true; var transactionWrappers = this.transactionWrappers; var wrapperInitTimes = this.timingMetrics.wrapperInitTimes; - var err = null; + var errorToThrow = null; for (var i = 0; i < transactionWrappers.length; i++) { var initStart = Date.now(); var wrapper = transactionWrappers[i]; try { - this.wrapperInitData[i] = - wrapper.initialize ? wrapper.initialize.call(this) : null; - } catch (initErr) { - err = err || initErr; // Remember the first error. + this.wrapperInitData[i] = wrapper.initialize ? + wrapper.initialize.call(this) : + null; + } catch (initError) { + // Prefer to show the stack trace of the first error. + errorToThrow = errorToThrow || initError; this.wrapperInitData[i] = Transaction.OBSERVED_ERROR; } finally { var curInitTime = wrapperInitTimes[i]; @@ -200,8 +193,8 @@ var Mixin = { wrapperInitTimes[i] = (curInitTime || 0) + (initEnd - initStart); } } - if (err) { - throw err; + if (errorToThrow) { + throw errorToThrow; } }, @@ -212,10 +205,13 @@ var Mixin = { * invoked). */ closeAll: function() { - throwIf(!this.isInTransaction(), MISSING_TRANSACTION); + invariant( + this.isInTransaction(), + 'Transaction.closeAll(): Cannot close transaction when none are open.' + ); var transactionWrappers = this.transactionWrappers; var wrapperCloseTimes = this.timingMetrics.wrapperCloseTimes; - var err = null; + var errorToThrow = null; for (var i = 0; i < transactionWrappers.length; i++) { var wrapper = transactionWrappers[i]; var closeStart = Date.now(); @@ -224,8 +220,9 @@ var Mixin = { if (initData !== Transaction.OBSERVED_ERROR) { wrapper.close && wrapper.close.call(this, initData); } - } catch (closeErr) { - err = err || closeErr; // Remember the first error. + } catch (closeError) { + // Prefer to show the stack trace of the first error. + errorToThrow = errorToThrow || closeError; } finally { var closeEnd = Date.now(); var curCloseTime = wrapperCloseTimes[i]; @@ -234,14 +231,16 @@ var Mixin = { } this.wrapperInitData.length = 0; this._isInTransaction = false; - if (err) { - throw err; + if (errorToThrow) { + throw errorToThrow; } } }; var Transaction = { + Mixin: Mixin, + /** * Token to look for to determine if an error occured. */ diff --git a/src/utils/accumulate.js b/src/utils/accumulate.js index 86f58b966a..7905f2bf9c 100644 --- a/src/utils/accumulate.js +++ b/src/utils/accumulate.js @@ -18,50 +18,34 @@ "use strict"; -var throwIf = require('throwIf'); - -var INVALID_ARGS = 'INVALID_ACCUM_ARGS'; - -if (__DEV__) { - INVALID_ARGS = - 'accumulate requires non empty (non-null, defined) next ' + - 'values. All arrays accumulated must not contain any empty items.'; -} +var invariant = require('invariant'); /** - * Accumulates items that must never be empty, into a result in a manner that - * conserves memory - avoiding allocation of arrays until they are needed. The - * accumulation may start and/or end up being a single element or an array - * depending on the total count (if greater than one, an array is allocated). - * Handles most common case first (starting with an empty current value and - * acquiring one). - * @return {Accumulation} An accumulation which is either a single item or an - * Array of items. + * Accumulates items that must not be null or undefined. + * + * This is used to conserve memory by avoiding array allocations. + * + * @return {*|array<*>} An accumulation of items. */ -function accumulate(cur, next) { - var curValIsEmpty = cur == null; // Will test for emptiness (null/undef) - var nextValIsEmpty = next === null; - if (__DEV__) { - throwIf(nextValIsEmpty, INVALID_ARGS); - } - if (nextValIsEmpty) { - return cur; +function accumulate(current, next) { + invariant( + next != null, + 'accumulate(...): Accumulated items must be not be null or undefined.' + ); + if (current == null) { + return next; } else { - if (curValIsEmpty) { - return next; + // Both are not empty. Warning: Never call x.concat(y) when you are not + // certain that x is an Array (x could be a string with concat method). + var currentIsArray = Array.isArray(current); + var nextIsArray = Array.isArray(next); + if (currentIsArray) { + return current.concat(next); } else { - // Both are not empty. Warning: Never call x.concat(y) when you are not - // certain that x is an Array (x could be a string with concat method). - var curIsArray = Array.isArray(cur); - var nextIsArray = Array.isArray(next); - if (curIsArray) { - return cur.concat(next); + if (nextIsArray) { + return [current].concat(next); } else { - if (nextIsArray) { - return [cur].concat(next); - } else { - return [cur, next]; - } + return [current, next]; } } } diff --git a/src/utils/escapeTextForBrowser.js b/src/utils/escapeTextForBrowser.js index eba906b9ab..8e49d31e9e 100644 --- a/src/utils/escapeTextForBrowser.js +++ b/src/utils/escapeTextForBrowser.js @@ -14,19 +14,12 @@ * limitations under the License. * * @providesModule escapeTextForBrowser + * @typechecks static-only */ "use strict"; -var throwIf = require('throwIf'); - -var ESCAPE_TYPE_ERR; - -if (__DEV__) { - ESCAPE_TYPE_ERR = - 'The React core has attempted to escape content that is of a ' + - 'mysterious type (object etc) Escaping only works on numbers and strings'; -} +var invariant = require('invariant'); var ESCAPE_LOOKUP = { "&": "&", @@ -41,13 +34,19 @@ function escaper(match) { return ESCAPE_LOOKUP[match]; } -var escapeTextForBrowser = function (text) { +/** + * Escapes text to prevent scripting attacks. + * + * @param {number|string} text Text value to escape. + * @return {string} An escaped string. + */ +function escapeTextForBrowser(text) { var type = typeof text; - var invalid = type === 'object'; - if (__DEV__) { - throwIf(invalid, ESCAPE_TYPE_ERR); - } - if (text === '' || invalid) { + invariant( + type !== 'object', + 'escapeTextForBrowser(...): Attempted to escape an object.' + ); + if (text === '') { return ''; } else { if (type === 'string') { @@ -56,6 +55,6 @@ var escapeTextForBrowser = function (text) { return (''+text).replace(/[&><"'\/]/g, escaper); } } -}; +} module.exports = escapeTextForBrowser; diff --git a/src/utils/flattenChildren.js b/src/utils/flattenChildren.js index ba2d6bdf77..17b24ef220 100644 --- a/src/utils/flattenChildren.js +++ b/src/utils/flattenChildren.js @@ -18,7 +18,7 @@ "use strict"; -var throwIf = require('throwIf'); +var invariant = require('invariant'); var traverseAllChildren = require('traverseAllChildren'); /** @@ -27,14 +27,14 @@ var traverseAllChildren = require('traverseAllChildren'); * @param {!string} name String name of key path to child. */ function flattenSingleChildIntoContext(traverseContext, child, name) { - // We found a component instance + // We found a component instance. var result = traverseContext; - if (__DEV__) { - throwIf( - result.hasOwnProperty(name), - traverseAllChildren.DUPLICATE_KEY_ERROR - ); - } + invariant( + !result.hasOwnProperty(name), + 'flattenChildren(...): Encountered two children with the same key, `%s`. ' + + 'Children keys must be unique.', + name + ); result[name] = child; } @@ -43,7 +43,7 @@ function flattenSingleChildIntoContext(traverseContext, child, name) { * @return {!object} flattened children keyed by name. */ function flattenChildren(children) { - if (children === null || children === undefined) { + if (children == null) { return children; } var result = {}; diff --git a/src/utils/keyMirror.js b/src/utils/keyMirror.js index bc64cabd9d..4e078e85a4 100644 --- a/src/utils/keyMirror.js +++ b/src/utils/keyMirror.js @@ -14,36 +14,38 @@ * limitations under the License. * * @providesModule keyMirror + * @typechecks static-only */ "use strict"; -var throwIf = require('throwIf'); - -var NOT_OBJECT_ERROR = 'NOT_OBJECT_ERROR'; -if (__DEV__) { - NOT_OBJECT_ERROR = 'keyMirror only works on objects'; -} +var invariant = require('invariant'); /** - * Utility for constructing enums with keys being equal to the associated - * values, even when using advanced key crushing. This is useful for debugging, - * but also for using the values themselves as lookups into the enum. - * Example: - * var COLORS = keyMirror({blue: null, red: null}); - * var myColor = COLORS.blue; - * var isColorValid = !!COLORS[myColor] + * Constructs an enumeration with keys equal to their value. + * + * For example: + * + * var COLORS = keyMirror({blue: null, red: null}); + * var myColor = COLORS.blue; + * var isColorValid = !!COLORS[myColor]; + * * The last line could not be performed if the values of the generated enum were * not equal to their keys. - * Input: {key1: val1, key2: val2} - * Output: {key1: key1, key2: key2} + * + * Input: {key1: val1, key2: val2} + * Output: {key1: key1, key2: key2} + * + * @param {object} obj + * @return {object} */ var keyMirror = function(obj) { var ret = {}; var key; - - throwIf(!(obj instanceof Object) || Array.isArray(obj), NOT_OBJECT_ERROR); - + invariant( + obj instanceof Object && !Array.isArray(obj), + 'keyMirror(...): Argument must be an object.' + ); for (key in obj) { if (!obj.hasOwnProperty(key)) { continue; diff --git a/src/utils/mapAllChildren.js b/src/utils/mapAllChildren.js index c035d9797d..9eff484798 100644 --- a/src/utils/mapAllChildren.js +++ b/src/utils/mapAllChildren.js @@ -20,7 +20,7 @@ var PooledClass = require('PooledClass'); -var throwIf = require('throwIf'); +var invariant = require('invariant'); var traverseAllChildren = require('traverseAllChildren'); var threeArgumentPooler = PooledClass.threeArgumentPooler; @@ -50,13 +50,13 @@ function mapSingleChildIntoContext(traverseContext, child, name, i) { var mapFunction = mapBookKeeping.mapFunction; var mapContext = mapBookKeeping.mapContext; var mappedChild = mapFunction.call(mapContext, child, name, i); - // We found a component instance - if (__DEV__) { - throwIf( - mapResult.hasOwnProperty(name), - traverseAllChildren.DUPLICATE_KEY_ERROR - ); - } + // We found a component instance. + invariant( + !mapResult.hasOwnProperty(name), + 'mapAllChildren(...): Encountered two children with the same key, `%s`. ' + + 'Children keys must be unique.', + name + ); mapResult[name] = mappedChild; } diff --git a/src/utils/mergeDeepInto.js b/src/utils/mergeDeepInto.js index 36131bb320..e86c95729f 100644 --- a/src/utils/mergeDeepInto.js +++ b/src/utils/mergeDeepInto.js @@ -21,9 +21,8 @@ "use strict"; -var keyMirror = require('keyMirror'); +var invariant = require('invariant'); var mergeHelpers = require('mergeHelpers'); -var throwIf = require('throwIf'); var ArrayStrategies = mergeHelpers.ArrayStrategies; var checkArrayStrategy = mergeHelpers.checkArrayStrategy; @@ -33,20 +32,6 @@ var checkMergeObjectArgs = mergeHelpers.checkMergeObjectArgs; var isTerminal = mergeHelpers.isTerminal; var normalizeMergeArg = mergeHelpers.normalizeMergeArg; -var ERRORS = keyMirror({ - RUN_TIME_ARRAY_MERGE_FAIL: null -}); - -if (__DEV__) { - ERRORS = { - RUN_TIME_ARRAY_MERGE_FAIL: - "The caller has not supplied an ArrayStrategy. This is supported as " + - "long as the data structures being merged do not contain two Arrays " + - "that must be merged together, which is exactly what has just " + - "happened. Change the call site to supply an Array merge resolver." - }; -} - /** * Every deep merge function must handle merging in each of the following cases * at every level. We may refer to letters below in implementations. For each @@ -161,9 +146,10 @@ var mergeSingleFieldDeep = function(one, two, key, arrayStrategy, level) { if (twoValIsTerminal) { // [E] one[key] = twoVal; } else if (twoValIsArray) { // [F] - throwIf( - !ArrayStrategies[arrayStrategy], - ERRORS.RUN_TIME_ARRAY_MERGE_FAIL + invariant( + ArrayStrategies[arrayStrategy], + 'mergeDeepInto(...): Attempted to merge two arrays, but a valid ' + + 'ArrayStrategy was not specified.' ); // Else: At this point, the only other valid option is `IndexByIndex` if (arrayStrategy === ArrayStrategies.Clobber) { @@ -234,7 +220,7 @@ var mergeSingleFieldDeep = function(one, two, key, arrayStrategy, level) { */ var mergeDeepInto = function(one, twoParam, arrayStrategy) { var two = normalizeMergeArg(twoParam); - checkArrayStrategy(arrayStrategy); // Will be checked twice, for now + checkArrayStrategy(arrayStrategy); // Will be checked twice, for now. mergeDeepIntoObjects(one, two, arrayStrategy, 0); }; diff --git a/src/utils/mergeHelpers.js b/src/utils/mergeHelpers.js index 5b46ff5d8e..d25cfcff87 100644 --- a/src/utils/mergeHelpers.js +++ b/src/utils/mergeHelpers.js @@ -20,8 +20,8 @@ "use strict"; +var invariant = require('invariant'); var keyMirror = require('keyMirror'); -var throwIf = require('throwIf'); /** * Maximum number of levels to traverse. Will catch circular structures. @@ -29,41 +29,6 @@ var throwIf = require('throwIf'); */ var MAX_MERGE_DEPTH = 36; -var ERRORS = keyMirror({ - MERGE_ARRAY_FAIL: null, - MERGE_CORE_FAILURE: null, - MERGE_TYPE_USAGE_FAILURE: null, - MERGE_DEEP_MAX_LEVELS: null, - MERGE_DEEP_NO_ARR_STRATEGY: null -}); - -if (__DEV__) { - ERRORS = { - MERGE_ARRAY_FAIL: - 'Unsupported type passed to a merge function. You may have passed a ' + - 'structure that contains an array and the merge function does not know ' + - 'how to merge arrays. ', - - MERGE_CORE_FAILURE: - 'Critical assumptions about the merge functions have been violated. ' + - 'This is the fault of the merge functions themselves, not necessarily ' + - 'the callers.', - - MERGE_TYPE_USAGE_FAILURE: - 'Calling merge function with invalid types. You may call merge ' + - 'functions (non-array non-terminal) OR (null/undefined) arguments. ' + - 'mergeInto functions have the same requirements but with an added ' + - 'restriction that the first parameter must not be null/undefined.', - - MERGE_DEEP_MAX_LEVELS: - 'Maximum deep merge depth exceeded. You may attempting to merge ' + - 'circular structures in an unsupported way.', - MERGE_DEEP_NO_ARR_STRATEGY: - 'You must provide an array strategy to deep merge functions to ' + - 'instruct the deep merge how to resolve merging two arrays.' - }; -} - /** * We won't worry about edge cases like new String('x') or new Boolean(true). * Functions are considered terminals, and arrays are not. @@ -99,9 +64,11 @@ var mergeHelpers = { * @param {*} two Array to merge from. */ checkMergeArrayArgs: function(one, two) { - throwIf( - !Array.isArray(one) || !Array.isArray(two), - ERRORS.MERGE_CORE_FAILURE + invariant( + Array.isArray(one) && Array.isArray(two), + 'Critical assumptions about the merge functions have been violated. ' + + 'This is the fault of the merge functions themselves, not necessarily ' + + 'the callers.' ); }, @@ -118,7 +85,12 @@ var mergeHelpers = { * @param {*} arg */ checkMergeObjectArg: function(arg) { - throwIf(isTerminal(arg) || Array.isArray(arg), ERRORS.MERGE_CORE_FAILURE); + invariant( + !isTerminal(arg) && !Array.isArray(arg), + 'Critical assumptions about the merge functions have been violated. ' + + 'This is the fault of the merge functions themselves, not necessarily ' + + 'the callers.' + ); }, /** @@ -128,19 +100,23 @@ var mergeHelpers = { * @param {number} Level of recursion to validate against maximum. */ checkMergeLevel: function(level) { - throwIf(level >= MAX_MERGE_DEPTH, ERRORS.MERGE_DEEP_MAX_LEVELS); + invariant( + level < MAX_MERGE_DEPTH, + 'Maximum deep merge depth exceeded. You may be attempting to merge ' + + 'circular structures in an unsupported way.' + ); }, /** - * Checks that a merge was not given a circular object or an object that had - * too great of depth. + * Checks that the supplied merge strategy is valid. * - * @param {number} Level of recursion to validate against maximum. + * @param {string} Array merge strategy. */ checkArrayStrategy: function(strategy) { - throwIf( - strategy !== undefined && !(strategy in mergeHelpers.ArrayStrategies), - ERRORS.MERGE_DEEP_NO_ARR_STRATEGY + invariant( + strategy === undefined || strategy in mergeHelpers.ArrayStrategies, + 'You must provide an array strategy to deep merge functions to ' + + 'instruct the deep merge how to resolve merging two arrays.' ); }, @@ -154,9 +130,8 @@ var mergeHelpers = { ArrayStrategies: keyMirror({ Clobber: true, IndexByIndex: true - }), + }) - ERRORS: ERRORS }; module.exports = mergeHelpers; diff --git a/src/utils/traverseAllChildren.js b/src/utils/traverseAllChildren.js index 9fa6f1881f..21dd506d84 100644 --- a/src/utils/traverseAllChildren.js +++ b/src/utils/traverseAllChildren.js @@ -21,22 +21,7 @@ var ReactComponent = require('ReactComponent'); var ReactTextComponent = require('ReactTextComponent'); -var throwIf = require('throwIf'); - -/** - * @polyFill Array.isArray - */ -var DUPLICATE_KEY_ERROR = 'DUPLICATE_KEY_ERROR'; -var INVALID_CHILD = 'INVALID_CHILD'; -if (__DEV__) { - INVALID_CHILD = - 'You may not pass a child of that type to a React component. It ' + - 'is a common mistake to try to pass a standard browser DOM element ' + - 'as a child of a React component.'; - DUPLICATE_KEY_ERROR = - 'You have two children with identical keys. Make sure that you set the ' + - '"key" property to a unique value such as a row ID.'; -} +var invariant = require('invariant'); /** * TODO: Test that: @@ -88,7 +73,11 @@ var traverseAllChildrenImpl = subtreeCount = 1; } else { if (type === 'object') { - throwIf(children && children.nodeType === 1, INVALID_CHILD); + invariant( + children || children.nodeType !== 1, + 'traverseAllChildren(...): Encountered an invalid child; DOM ' + + 'elements are not valid children of React components.' + ); for (var key in children) { if (children.hasOwnProperty(key)) { subtreeCount += traverseAllChildrenImpl( @@ -135,10 +124,4 @@ function traverseAllChildren(children, callback, traverseContext) { } } -/** - * Export the error code for use in other walking/mapping code. - */ -traverseAllChildren.DUPLICATE_KEY_ERROR = DUPLICATE_KEY_ERROR; - - module.exports = traverseAllChildren;