@samitouri / QOS-React-2 / commits / ff52e3df00

Move defaultProps resolution and type validation to the descriptor

This copies the propType and contextType validation to a wrapper around the descriptor factory. By doing the validation early, we make it easier to track down bugs. It also prepares for static type checking which should be done at the usage site. This validation is not yet active and is just logged using monitorCodeUse. This will allow us to clean up callsites which would fail this new type of validation. I chose to copy the validation instead of abstracting it out to a common abstraction. This is just an intermediate step to avoid spamming consoles. The original validation in the instance will be deleted as soon as we can turn on the warnings at the callsite. Copy+Delete makes this a more a much cleaner diff review/history. Additionally, getDefaultProps are moved to become a static function which is only executed once. It should be moved to statics but we don't have a convenient way to merge mixins in statics right now. Deferring to ES6 classes. This is still a breaking change since you can return an object or array from getDefaultProps, which later gets mutated and now the shared instance is mutated. Mutating an object that is passed into you from props is highly discouraged and likely to lead to subtle bugs anyway. So I'm not too worried. The defaultProps are now resolved in the descriptor factory. This will enable a perf optimizations where we don't create an unnecessary object allocation when you use default props. It also means that ReactChildren.map has access to resolved properties which gives them consistent behavior whether or not the default prop is specified. This is a breaking change since it can affect how mapping over children and transferPropsTo works together with defaultProps.

Sebastian Markbage committed May 5, 2014 at 16:59 UTC ff52e3df00ab7ed1375f46d81c122a614e202bb9
5 files changed +386 -205
src/browser/ReactDOM.js
+7
@@ -20,6 +20,7 @@
20 "use strict";
21
22 var ReactDescriptor = require('ReactDescriptor');
23 +var ReactDescriptorValidator = require('ReactDescriptorValidator');
24 var ReactDOMComponent = require('ReactDOMComponent');
25
26 var mergeInto = require('mergeInto');
@@ -50,6 +51,12 @@ function createDOMComponentClass(omitClose, tag) {
51
52 var ConvenienceConstructor = ReactDescriptor.createFactory(Constructor);
53
54 + if (__DEV__) {
55 + return ReactDescriptorValidator.createFactory(
56 + ConvenienceConstructor
57 + );
58 + }
59 +
60 return ConvenienceConstructor;
61 }
62
src/core/ReactCompositeComponent.js
+45 -37
@@ -22,6 +22,7 @@ var ReactComponent = require('ReactComponent');
22 var ReactContext = require('ReactContext');
23 var ReactCurrentOwner = require('ReactCurrentOwner');
24 var ReactDescriptor = require('ReactDescriptor');
25 +var ReactDescriptorValidator = require('ReactDescriptorValidator');
26 var ReactEmptyComponent = require('ReactEmptyComponent');
27 var ReactErrorUtils = require('ReactErrorUtils');
28 var ReactOwner = require('ReactOwner');
@@ -328,18 +329,17 @@ var ReactCompositeComponentInterface = {
329 * which all other static methods are defined.
330 */
331 var RESERVED_SPEC_KEYS = {
331 - displayName: function(ConvenienceConstructor, displayName) {
332 - ConvenienceConstructor.type.displayName = displayName;
332 + displayName: function(Constructor, displayName) {
333 + Constructor.displayName = displayName;
334 },
334 - mixins: function(ConvenienceConstructor, mixins) {
335 + mixins: function(Constructor, mixins) {
336 if (mixins) {
337 for (var i = 0; i < mixins.length; i++) {
337 - mixSpecIntoComponent(ConvenienceConstructor, mixins[i]);
338 + mixSpecIntoComponent(Constructor, mixins[i]);
339 }
340 }
341 },
341 - childContextTypes: function(ConvenienceConstructor, childContextTypes) {
342 - var Constructor = ConvenienceConstructor.type;
342 + childContextTypes: function(Constructor, childContextTypes) {
343 validateTypeDef(
344 Constructor,
345 childContextTypes,
@@ -350,8 +350,7 @@ var RESERVED_SPEC_KEYS = {
350 childContextTypes
351 );
352 },
353 - contextTypes: function(ConvenienceConstructor, contextTypes) {
354 - var Constructor = ConvenienceConstructor.type;
353 + contextTypes: function(Constructor, contextTypes) {
354 validateTypeDef(
355 Constructor,
356 contextTypes,
@@ -359,8 +358,21 @@ var RESERVED_SPEC_KEYS = {
358 );
359 Constructor.contextTypes = merge(Constructor.contextTypes, contextTypes);
360 },
362 - propTypes: function(ConvenienceConstructor, propTypes) {
363 - var Constructor = ConvenienceConstructor.type;
361 + /**
362 + * Special case getDefaultProps which should move into statics but requires
363 + * automatic merging.
364 + */
365 + getDefaultProps: function(Constructor, getDefaultProps) {
366 + if (Constructor.getDefaultProps) {
367 + Constructor.getDefaultProps = createMergedResultFunction(
368 + Constructor.getDefaultProps,
369 + getDefaultProps
370 + );
371 + } else {
372 + Constructor.getDefaultProps = getDefaultProps;
373 + }
374 + },
375 + propTypes: function(Constructor, propTypes) {
376 validateTypeDef(
377 Constructor,
378 propTypes,
@@ -368,8 +380,8 @@ var RESERVED_SPEC_KEYS = {
380 );
381 Constructor.propTypes = merge(Constructor.propTypes, propTypes);
382 },
371 - statics: function(ConvenienceConstructor, statics) {
372 - mixStaticSpecIntoComponent(ConvenienceConstructor, statics);
383 + statics: function(Constructor, statics) {
384 + mixStaticSpecIntoComponent(Constructor, statics);
385 }
386 };
387
@@ -437,7 +449,7 @@ function validateLifeCycleOnReplaceState(instance) {
449 * Custom version of `mixInto` which handles policy validation and reserved
450 * specification keys when building `ReactCompositeComponent` classses.
451 */
440 -function mixSpecIntoComponent(ConvenienceConstructor, spec) {
452 +function mixSpecIntoComponent(Constructor, spec) {
453 invariant(
454 !ReactDescriptor.isValidFactory(spec),
455 'ReactCompositeComponent: You\'re attempting to ' +
@@ -449,7 +461,6 @@ function mixSpecIntoComponent(ConvenienceConstructor, spec) {
461 'use a component as a mixin. Instead, just use a regular object.'
462 );
463
452 - var Constructor = ConvenienceConstructor.type;
464 var proto = Constructor.prototype;
465 for (var name in spec) {
466 var property = spec[name];
@@ -460,7 +471,7 @@ function mixSpecIntoComponent(ConvenienceConstructor, spec) {
471 validateMethodOverride(proto, name);
472
473 if (RESERVED_SPEC_KEYS.hasOwnProperty(name)) {
463 - RESERVED_SPEC_KEYS[name](ConvenienceConstructor, property);
474 + RESERVED_SPEC_KEYS[name](Constructor, property);
475 } else {
476 // Setup methods on prototype:
477 // The following member methods should not be automatically bound:
@@ -521,7 +532,7 @@ function mixSpecIntoComponent(ConvenienceConstructor, spec) {
532 }
533 }
534
524 -function mixStaticSpecIntoComponent(ConvenienceConstructor, statics) {
535 +function mixStaticSpecIntoComponent(Constructor, statics) {
536 if (!statics) {
537 return;
538 }
@@ -531,10 +542,10 @@ function mixStaticSpecIntoComponent(ConvenienceConstructor, statics) {
542 continue;
543 }
544
534 - var isInherited = name in ConvenienceConstructor;
545 + var isInherited = name in Constructor;
546 var result = property;
547 if (isInherited) {
537 - var existingProperty = ConvenienceConstructor[name];
548 + var existingProperty = Constructor[name];
549 var existingType = typeof existingProperty;
550 var propertyType = typeof property;
551 invariant(
@@ -547,8 +558,7 @@ function mixStaticSpecIntoComponent(ConvenienceConstructor, statics) {
558 );
559 result = createChainedFunction(existingProperty, property);
560 }
550 - ConvenienceConstructor[name] = result;
551 - ConvenienceConstructor.type[name] = result;
561 + Constructor[name] = result;
562 }
563 }
564
@@ -723,7 +733,6 @@ var ReactCompositeComponentMixin = {
733 this._compositeLifeCycleState = CompositeLifeCycle.MOUNTING;
734
735 this.context = this._processContext(this._descriptor._context);
726 - this._defaultProps = this.getDefaultProps ? this.getDefaultProps() : null;
736 this.props = this._processProps(this.props);
737
738 if (this.__reactAutoBindMap) {
@@ -781,8 +790,6 @@ var ReactCompositeComponentMixin = {
790 }
791 this._compositeLifeCycleState = null;
792
784 - this._defaultProps = null;
785 -
793 this._renderedComponent.unmountComponent();
794 this._renderedComponent = null;
795
@@ -921,12 +928,6 @@ var ReactCompositeComponentMixin = {
928 */
929 _processProps: function(newProps) {
930 var props = merge(newProps);
924 - var defaultProps = this._defaultProps;
925 - for (var propName in defaultProps) {
926 - if (typeof props[propName] === 'undefined') {
927 - props[propName] = defaultProps[propName];
928 - }
929 - }
931 if (__DEV__) {
932 var propTypes = this.constructor.propTypes;
933 if (propTypes) {
@@ -945,6 +946,8 @@ var ReactCompositeComponentMixin = {
946 * @private
947 */
948 _checkPropTypes: function(propTypes, props, location) {
949 + // TODO: Stop validating prop types here and only use the descriptor
950 + // validation.
951 var componentName = this.constructor.displayName;
952 for (var propName in propTypes) {
953 if (propTypes.hasOwnProperty(propName)) {
@@ -1310,16 +1313,11 @@ var ReactCompositeComponent = {
1313 Constructor.prototype = new ReactCompositeComponentBase();
1314 Constructor.prototype.constructor = Constructor;
1315
1313 - var ConvenienceConstructor = ReactDescriptor.createFactory(Constructor);
1314 -
1315 - // TODO: Move statics off of the convenience constructor. That way the
1316 - // factory can be created independently from the main class.
1317 -
1316 injectedMixins.forEach(
1319 - mixSpecIntoComponent.bind(null, ConvenienceConstructor)
1317 + mixSpecIntoComponent.bind(null, Constructor)
1318 );
1319
1322 - mixSpecIntoComponent(ConvenienceConstructor, spec);
1320 + mixSpecIntoComponent(Constructor, spec);
1321
1322 invariant(
1323 Constructor.prototype.render,
@@ -1348,7 +1346,17 @@ var ReactCompositeComponent = {
1346 }
1347 }
1348
1351 - return ConvenienceConstructor;
1349 + var descriptorFactory = ReactDescriptor.createFactory(Constructor);
1350 +
1351 + if (__DEV__) {
1352 + return ReactDescriptorValidator.createFactory(
1353 + descriptorFactory,
1354 + Constructor.propTypes,
1355 + Constructor.contextTypes
1356 + );
1357 + }
1358 +
1359 + return descriptorFactory;
1360 },
1361
1362 injection: {
src/core/ReactDescriptor.js
+49 -166
@@ -21,165 +21,9 @@
21 var ReactContext = require('ReactContext');
22 var ReactCurrentOwner = require('ReactCurrentOwner');
23
24 -var monitorCodeUse = require('monitorCodeUse');
24 +var merge = require('merge');
25 var warning = require('warning');
26
27 -/**
28 - * Warn if there's no key explicitly set on dynamic arrays of children or
29 - * object keys are not valid. This allows us to keep track of children between
30 - * updates.
31 - */
32 -var ownerHasKeyUseWarning = {
33 - 'react_key_warning': {},
34 - 'react_numeric_key_warning': {}
35 -};
36 -var ownerHasMonitoredObjectMap = {};
37 -
38 -var NUMERIC_PROPERTY_REGEX = /^\d+$/;
39 -
40 -/**
41 - * Gets the current owner's displayName for use in warnings.
42 - *
43 - * @internal
44 - * @return {?string} Display name or undefined
45 - */
46 -function getCurrentOwnerDisplayName() {
47 - var current = ReactCurrentOwner.current;
48 - return current && current.constructor.displayName || undefined;
49 -}
50 -
51 -/**
52 - * Warn if the component doesn't have an explicit key assigned to it.
53 - * This component is in an array. The array could grow and shrink or be
54 - * reordered. All children that haven't already been validated are required to
55 - * have a "key" property assigned to it.
56 - *
57 - * @internal
58 - * @param {ReactComponent} component Component that requires a key.
59 - * @param {*} parentType component's parent's type.
60 - */
61 -function validateExplicitKey(component, parentType) {
62 - if (component._store.validated || component.props.key != null) {
63 - return;
64 - }
65 - component._store.validated = true;
66 -
67 - warnAndMonitorForKeyUse(
68 - 'react_key_warning',
69 - 'Each child in an array should have a unique "key" prop.',
70 - component,
71 - parentType
72 - );
73 -}
74 -
75 -/**
76 - * Warn if the key is being defined as an object property but has an incorrect
77 - * value.
78 - *
79 - * @internal
80 - * @param {string} name Property name of the key.
81 - * @param {ReactComponent} component Component that requires a key.
82 - * @param {*} parentType component's parent's type.
83 - */
84 -function validatePropertyKey(name, component, parentType) {
85 - if (!NUMERIC_PROPERTY_REGEX.test(name)) {
86 - return;
87 - }
88 - warnAndMonitorForKeyUse(
89 - 'react_numeric_key_warning',
90 - 'Child objects should have non-numeric keys so ordering is preserved.',
91 - component,
92 - parentType
93 - );
94 -}
95 -
96 -/**
97 - * Shared warning and monitoring code for the key warnings.
98 - *
99 - * @internal
100 - * @param {string} warningID The id used when logging.
101 - * @param {string} message The base warning that gets output.
102 - * @param {ReactComponent} component Component that requires a key.
103 - * @param {*} parentType component's parent's type.
104 - */
105 -function warnAndMonitorForKeyUse(warningID, message, component, parentType) {
106 - var ownerName = getCurrentOwnerDisplayName();
107 - var parentName = parentType.displayName;
108 -
109 - var useName = ownerName || parentName;
110 - var memoizer = ownerHasKeyUseWarning[warningID];
111 - if (memoizer.hasOwnProperty(useName)) {
112 - return;
113 - }
114 - memoizer[useName] = true;
115 -
116 - message += ownerName ?
117 - ` Check the render method of ${ownerName}.` :
118 - ` Check the renderComponent call using <${parentName}>.`;
119 -
120 - // Usually the current owner is the offender, but if it accepts children as a
121 - // property, it may be the creator of the child that's responsible for
122 - // assigning it a key.
123 - var childOwnerName = null;
124 - if (component._owner && component._owner !== ReactCurrentOwner.current) {
125 - // Name of the component that originally created this child.
126 - childOwnerName = component._owner.constructor.displayName;
127 -
128 - message += ` It was passed a child from ${childOwnerName}.`;
129 - }
130 -
131 - message += ' See http://fb.me/react-warning-keys for more information.';
132 - monitorCodeUse(warningID, {
133 - component: useName,
134 - componentOwner: childOwnerName
135 - });
136 - console.warn(message);
137 -}
138 -
139 -/**
140 - * Log that we're using an object map. We're considering deprecating this
141 - * feature and replace it with proper Map and ImmutableMap data structures.
142 - *
143 - * @internal
144 - */
145 -function monitorUseOfObjectMap() {
146 - var currentName = getCurrentOwnerDisplayName() || '';
147 - if (ownerHasMonitoredObjectMap.hasOwnProperty(currentName)) {
148 - return;
149 - }
150 - ownerHasMonitoredObjectMap[currentName] = true;
151 - monitorCodeUse('react_object_map_children');
152 -}
153 -
154 -/**
155 - * Ensure that every component either is passed in a static location, in an
156 - * array with an explicit keys property defined, or in an object literal
157 - * with valid key property.
158 - *
159 - * @internal
160 - * @param {*} component Statically passed child of any type.
161 - * @param {*} parentType component's parent's type.
162 - * @return {boolean}
163 - */
164 -function validateChildKeys(component, parentType) {
165 - if (Array.isArray(component)) {
166 - for (var i = 0; i < component.length; i++) {
167 - var child = component[i];
168 - if (ReactDescriptor.isValidDescriptor(child)) {
169 - validateExplicitKey(child, parentType);
170 - }
171 - }
172 - } else if (ReactDescriptor.isValidDescriptor(component)) {
173 - // This component was passed in a valid location.
174 - component._store.validated = true;
175 - } else if (component && typeof component === 'object') {
176 - monitorUseOfObjectMap();
177 - for (var name in component) {
178 - validatePropertyKey(name, component[name], parentType);
179 - }
180 - }
181 -}
182 -
27 /**
28 * Warn for mutations.
29 *
@@ -237,6 +81,26 @@ function defineMutationMembrane(prototype) {
81 }
82 }
83
84 +/**
85 + * Transfer static properties from the source to the target. Functions are
86 + * rebound to have this reflect the original source.
87 + */
88 +function proxyStaticMethods(target, source) {
89 + if (typeof source !== 'function') {
90 + return;
91 + }
92 + for (var key in source) {
93 + if (source.hasOwnProperty(key)) {
94 + var value = source[key];
95 + if (typeof value === 'function') {
96 + target[key] = value.bind(source);
97 + } else {
98 + target[key] = value;
99 + }
100 + }
101 + }
102 +}
103 +
104 /**
105 * Base constructor for all React descriptors. This is only used to make this
106 * work with a dynamic instanceof check. Nothing should live on this prototype.
@@ -254,29 +118,44 @@ ReactDescriptor.createFactory = function(type) {
118
119 var descriptorPrototype = Object.create(ReactDescriptor.prototype);
120
121 + var defaultProps = type.getDefaultProps && type.getDefaultProps();
122 +
123 var factory = function(props, children) {
124 + // For consistency we currently allocate a new object for every descriptor.
125 + // This protects the descriptor from being mutated by the original props
126 + // object being mutated. It also protects the original props object from
127 + // being mutated by children arguments and default props. This behavior
128 + // comes with a performance cost and could be deprecated in the future.
129 + // It could also be optimized with a smarter JSX transform.
130 if (props == null) {
131 props = {};
132 + } else if (typeof props === 'object') {
133 + props = merge(props);
134 }
135
262 - // Children can be more than one argument
136 + // Children can be more than one argument, and those are transferred onto
137 + // the newly allocated props object.
138 var childrenLength = arguments.length - 1;
139 if (childrenLength === 1) {
265 - if (__DEV__) {
266 - validateChildKeys(children, type);
267 - }
140 props.children = children;
141 } else if (childrenLength > 1) {
142 var childArray = Array(childrenLength);
143 for (var i = 0; i < childrenLength; i++) {
272 - if (__DEV__) {
273 - validateChildKeys(arguments[i + 1], type);
274 - }
144 childArray[i] = arguments[i + 1];
145 }
146 props.children = childArray;
147 }
148
149 + // Resolve default props
150 + if (defaultProps) {
151 + for (var propName in defaultProps) {
152 + if (typeof props[propName] === 'undefined') {
153 + props[propName] = defaultProps[propName];
154 + }
155 + }
156 + }
157 +
158 + // Initialize the descriptor object
159 var descriptor = Object.create(descriptorPrototype);
160
161 // Record the component responsible for creating this descriptor.
@@ -289,10 +168,13 @@ ReactDescriptor.createFactory = function(type) {
168 if (__DEV__) {
169 // The validation flag and props are currently mutative. We put them on
170 // an external backing store so that we can freeze the whole object.
171 + // This can be replaced with a WeakMap once they are implemented in
172 + // commonly used development environments.
173 descriptor._store = { validated: false, props: props };
174
175 // We're not allowed to set props directly on the object so we early
295 - // return and rely on the prototype membrane toward to the backing store.
176 + // return and rely on the prototype membrane to forward to the backing
177 + // store.
178 if (useMutationMembrane) {
179 Object.freeze(descriptor);
180 return descriptor;
@@ -301,7 +183,6 @@ ReactDescriptor.createFactory = function(type) {
183
184 descriptor.props = props;
185 return descriptor;
304 -
186 };
187
188 // Currently we expose the prototype of the descriptor so that
@@ -316,6 +197,8 @@ ReactDescriptor.createFactory = function(type) {
197 factory.type = type;
198 descriptorPrototype.type = type;
199
200 + proxyStaticMethods(factory, type);
201 +
202 // Expose a unique constructor on the prototype is that this works with type
203 // systems that compare constructor properties: <Foo />.constructor === Foo
204 // This may be controversial since it requires a known factory function.
src/core/ReactDescriptorValidator.js new
+283
@@ -0,0 +1,283 @@
1 +/**
2 + * Copyright 2014 Facebook, Inc.
3 + *
4 + * Licensed under the Apache License, Version 2.0 (the "License");
5 + * you may not use this file except in compliance with the License.
6 + * You may obtain a copy of the License at
7 + *
8 + * http://www.apache.org/licenses/LICENSE-2.0
9 + *
10 + * Unless required by applicable law or agreed to in writing, software
11 + * distributed under the License is distributed on an "AS IS" BASIS,
12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 + * See the License for the specific language governing permissions and
14 + * limitations under the License.
15 + *
16 + * @providesModule ReactDescriptorValidator
17 + */
18 +
19 +/**
20 + * ReactDescriptorValidator provides a wrapper around a descriptor factory
21 + * which validates the props passed to the descriptor. This is intended to be
22 + * used only in DEV and could be replaced by a static type checker for languages
23 + * that support it.
24 + */
25 +
26 +"use strict";
27 +
28 +var ReactDescriptor = require('ReactDescriptor');
29 +var ReactPropTypeLocations = require('ReactPropTypeLocations');
30 +var ReactCurrentOwner = require('ReactCurrentOwner');
31 +
32 +var monitorCodeUse = require('monitorCodeUse');
33 +
34 +/**
35 + * Warn if there's no key explicitly set on dynamic arrays of children or
36 + * object keys are not valid. This allows us to keep track of children between
37 + * updates.
38 + */
39 +var ownerHasKeyUseWarning = {
40 + 'react_key_warning': {},
41 + 'react_numeric_key_warning': {}
42 +};
43 +var ownerHasMonitoredObjectMap = {};
44 +
45 +var loggedTypeFailures = {};
46 +
47 +var NUMERIC_PROPERTY_REGEX = /^\d+$/;
48 +
49 +/**
50 + * Gets the current owner's displayName for use in warnings.
51 + *
52 + * @internal
53 + * @return {?string} Display name or undefined
54 + */
55 +function getCurrentOwnerDisplayName() {
56 + var current = ReactCurrentOwner.current;
57 + return current && current.constructor.displayName || undefined;
58 +}
59 +
60 +/**
61 + * Warn if the component doesn't have an explicit key assigned to it.
62 + * This component is in an array. The array could grow and shrink or be
63 + * reordered. All children that haven't already been validated are required to
64 + * have a "key" property assigned to it.
65 + *
66 + * @internal
67 + * @param {ReactComponent} component Component that requires a key.
68 + * @param {*} parentType component's parent's type.
69 + */
70 +function validateExplicitKey(component, parentType) {
71 + if (component._store.validated || component.props.key != null) {
72 + return;
73 + }
74 + component._store.validated = true;
75 +
76 + warnAndMonitorForKeyUse(
77 + 'react_key_warning',
78 + 'Each child in an array should have a unique "key" prop.',
79 + component,
80 + parentType
81 + );
82 +}
83 +
84 +/**
85 + * Warn if the key is being defined as an object property but has an incorrect
86 + * value.
87 + *
88 + * @internal
89 + * @param {string} name Property name of the key.
90 + * @param {ReactComponent} component Component that requires a key.
91 + * @param {*} parentType component's parent's type.
92 + */
93 +function validatePropertyKey(name, component, parentType) {
94 + if (!NUMERIC_PROPERTY_REGEX.test(name)) {
95 + return;
96 + }
97 + warnAndMonitorForKeyUse(
98 + 'react_numeric_key_warning',
99 + 'Child objects should have non-numeric keys so ordering is preserved.',
100 + component,
101 + parentType
102 + );
103 +}
104 +
105 +/**
106 + * Shared warning and monitoring code for the key warnings.
107 + *
108 + * @internal
109 + * @param {string} warningID The id used when logging.
110 + * @param {string} message The base warning that gets output.
111 + * @param {ReactComponent} component Component that requires a key.
112 + * @param {*} parentType component's parent's type.
113 + */
114 +function warnAndMonitorForKeyUse(warningID, message, component, parentType) {
115 + var ownerName = getCurrentOwnerDisplayName();
116 + var parentName = parentType.displayName;
117 +
118 + var useName = ownerName || parentName;
119 + var memoizer = ownerHasKeyUseWarning[warningID];
120 + if (memoizer.hasOwnProperty(useName)) {
121 + return;
122 + }
123 + memoizer[useName] = true;
124 +
125 + message += ownerName ?
126 + ` Check the render method of ${ownerName}.` :
127 + ` Check the renderComponent call using <${parentName}>.`;
128 +
129 + // Usually the current owner is the offender, but if it accepts children as a
130 + // property, it may be the creator of the child that's responsible for
131 + // assigning it a key.
132 + var childOwnerName = null;
133 + if (component._owner && component._owner !== ReactCurrentOwner.current) {
134 + // Name of the component that originally created this child.
135 + childOwnerName = component._owner.constructor.displayName;
136 +
137 + message += ` It was passed a child from ${childOwnerName}.`;
138 + }
139 +
140 + message += ' See http://fb.me/react-warning-keys for more information.';
141 + monitorCodeUse(warningID, {
142 + component: useName,
143 + componentOwner: childOwnerName
144 + });
145 + console.warn(message);
146 +}
147 +
148 +/**
149 + * Log that we're using an object map. We're considering deprecating this
150 + * feature and replace it with proper Map and ImmutableMap data structures.
151 + *
152 + * @internal
153 + */
154 +function monitorUseOfObjectMap() {
155 + var currentName = getCurrentOwnerDisplayName() || '';
156 + if (ownerHasMonitoredObjectMap.hasOwnProperty(currentName)) {
157 + return;
158 + }
159 + ownerHasMonitoredObjectMap[currentName] = true;
160 + monitorCodeUse('react_object_map_children');
161 +}
162 +
163 +/**
164 + * Ensure that every component either is passed in a static location, in an
165 + * array with an explicit keys property defined, or in an object literal
166 + * with valid key property.
167 + *
168 + * @internal
169 + * @param {*} component Statically passed child of any type.
170 + * @param {*} parentType component's parent's type.
171 + * @return {boolean}
172 + */
173 +function validateChildKeys(component, parentType) {
174 + if (Array.isArray(component)) {
175 + for (var i = 0; i < component.length; i++) {
176 + var child = component[i];
177 + if (ReactDescriptor.isValidDescriptor(child)) {
178 + validateExplicitKey(child, parentType);
179 + }
180 + }
181 + } else if (ReactDescriptor.isValidDescriptor(component)) {
182 + // This component was passed in a valid location.
183 + component._store.validated = true;
184 + } else if (component && typeof component === 'object') {
185 + monitorUseOfObjectMap();
186 + for (var name in component) {
187 + validatePropertyKey(name, component[name], parentType);
188 + }
189 + }
190 +}
191 +
192 +/**
193 + * Assert that the props are valid
194 + *
195 + * @param {string} componentName Name of the component for error messages.
196 + * @param {object} propTypes Map of prop name to a ReactPropType
197 + * @param {object} props
198 + * @param {string} location e.g. "prop", "context", "child context"
199 + * @private
200 + */
201 +function checkPropTypes(componentName, propTypes, props, location) {
202 + for (var propName in propTypes) {
203 + if (propTypes.hasOwnProperty(propName)) {
204 + var error;
205 + // Prop type validation may throw. In case they do, we don't want to
206 + // fail the render phase where it didn't fail before. So we log it.
207 + // After these have been cleaned up, we'll let them throw.
208 + try {
209 + error = propTypes[propName](props, propName, componentName, location);
210 + } catch (ex) {
211 + error = ex;
212 + }
213 + if (error instanceof Error && !(error.message in loggedTypeFailures)) {
214 + // Only monitor this failure once because there tends to be a lot of the
215 + // same error.
216 + loggedTypeFailures[error.message] = true;
217 + // This will soon use the warning module
218 + monitorCodeUse(
219 + 'react_failed_descriptor_type_check',
220 + { message: error.message }
221 + );
222 + }
223 + }
224 + }
225 +}
226 +
227 +var ReactDescriptorValidator = {
228 +
229 + /**
230 + * Wraps a descriptor factory function in another function which validates
231 + * the props and context of the descriptor and warns about any failed type
232 + * checks.
233 + *
234 + * @param {function} factory The original descriptor factory
235 + * @param {object?} propTypes A prop type definition set
236 + * @param {object?} contextTypes A context type definition set
237 + * @return {object} The component descriptor, which may be invalid.
238 + * @private
239 + */
240 + createFactory: function(factory, propTypes, contextTypes) {
241 + var validatedFactory = function(props, children) {
242 + var descriptor = factory.apply(this, arguments);
243 +
244 + for (var i = 1; i < arguments.length; i++) {
245 + validateChildKeys(arguments[i], descriptor.type);
246 + }
247 +
248 + var name = descriptor.type.displayName;
249 + if (propTypes) {
250 + checkPropTypes(
251 + name,
252 + propTypes,
253 + descriptor.props,
254 + ReactPropTypeLocations.prop
255 + );
256 + }
257 + if (contextTypes) {
258 + checkPropTypes(
259 + name,
260 + contextTypes,
261 + descriptor._context,
262 + ReactPropTypeLocations.context
263 + );
264 + }
265 + return descriptor;
266 + };
267 +
268 + validatedFactory.prototype = factory.prototype;
269 + validatedFactory.type = factory.type;
270 +
271 + // Copy static properties
272 + for (var key in factory) {
273 + if (factory.hasOwnProperty(key)) {
274 + validatedFactory[key] = factory[key];
275 + }
276 + }
277 +
278 + return validatedFactory;
279 + }
280 +
281 +};
282 +
283 +module.exports = ReactDescriptorValidator;
src/core/__tests__/ReactPropTypes-test.js
+2 -2
@@ -614,7 +614,7 @@ describe('Custom validator', function() {
614 var instance = <Component num={5} />;
615 instance = ReactTestUtils.renderIntoDocument(instance);
616
617 - expect(spy.argsForCall.length).toBe(1);
617 + expect(spy.argsForCall.length).toBe(2); // temp double validation
618 expect(spy.argsForCall[0][1]).toBe('num');
619 expect(spy.argsForCall[0][2]).toBe('Component');
620 });
@@ -632,7 +632,7 @@ describe('Custom validator', function() {
632 var instance = <Component bla={5} />;
633 instance = ReactTestUtils.renderIntoDocument(instance);
634
635 - expect(spy.argsForCall.length).toBe(1);
635 + expect(spy.argsForCall.length).toBe(2); // temp double validation
636 });
637
638 it('should have received the validator\'s return value', function() {