mirror of
https://github.com/facebook/react.git
synced 2025-11-01 09:12:30 +00:00
We still have some issues to work out when the type argument is a mock.
280 lines
8.4 KiB
JavaScript
280 lines
8.4 KiB
JavaScript
/**
|
|
* Copyright 2014 Facebook, Inc.
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*
|
|
* @providesModule ReactDescriptorValidator
|
|
*/
|
|
|
|
/**
|
|
* ReactDescriptorValidator provides a wrapper around a descriptor factory
|
|
* which validates the props passed to the descriptor. This is intended to be
|
|
* used only in DEV and could be replaced by a static type checker for languages
|
|
* that support it.
|
|
*/
|
|
|
|
"use strict";
|
|
|
|
var ReactDescriptor = require('ReactDescriptor');
|
|
var ReactPropTypeLocations = require('ReactPropTypeLocations');
|
|
var ReactCurrentOwner = require('ReactCurrentOwner');
|
|
|
|
var monitorCodeUse = require('monitorCodeUse');
|
|
|
|
/**
|
|
* Warn if there's no key explicitly set on dynamic arrays of children or
|
|
* object keys are not valid. This allows us to keep track of children between
|
|
* updates.
|
|
*/
|
|
var ownerHasKeyUseWarning = {
|
|
'react_key_warning': {},
|
|
'react_numeric_key_warning': {}
|
|
};
|
|
var ownerHasMonitoredObjectMap = {};
|
|
|
|
var loggedTypeFailures = {};
|
|
|
|
var NUMERIC_PROPERTY_REGEX = /^\d+$/;
|
|
|
|
/**
|
|
* Gets the current owner's displayName for use in warnings.
|
|
*
|
|
* @internal
|
|
* @return {?string} Display name or undefined
|
|
*/
|
|
function getCurrentOwnerDisplayName() {
|
|
var current = ReactCurrentOwner.current;
|
|
return current && current.constructor.displayName || undefined;
|
|
}
|
|
|
|
/**
|
|
* Warn if the component doesn't have an explicit key assigned to it.
|
|
* This component is in an array. The array could grow and shrink or be
|
|
* reordered. All children that haven't already been validated are required to
|
|
* have a "key" property assigned to it.
|
|
*
|
|
* @internal
|
|
* @param {ReactComponent} component Component that requires a key.
|
|
* @param {*} parentType component's parent's type.
|
|
*/
|
|
function validateExplicitKey(component, parentType) {
|
|
if (component._store.validated || component.key != null) {
|
|
return;
|
|
}
|
|
component._store.validated = true;
|
|
|
|
warnAndMonitorForKeyUse(
|
|
'react_key_warning',
|
|
'Each child in an array should have a unique "key" prop.',
|
|
component,
|
|
parentType
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Warn if the key is being defined as an object property but has an incorrect
|
|
* value.
|
|
*
|
|
* @internal
|
|
* @param {string} name Property name of the key.
|
|
* @param {ReactComponent} component Component that requires a key.
|
|
* @param {*} parentType component's parent's type.
|
|
*/
|
|
function validatePropertyKey(name, component, parentType) {
|
|
if (!NUMERIC_PROPERTY_REGEX.test(name)) {
|
|
return;
|
|
}
|
|
warnAndMonitorForKeyUse(
|
|
'react_numeric_key_warning',
|
|
'Child objects should have non-numeric keys so ordering is preserved.',
|
|
component,
|
|
parentType
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Shared warning and monitoring code for the key warnings.
|
|
*
|
|
* @internal
|
|
* @param {string} warningID The id used when logging.
|
|
* @param {string} message The base warning that gets output.
|
|
* @param {ReactComponent} component Component that requires a key.
|
|
* @param {*} parentType component's parent's type.
|
|
*/
|
|
function warnAndMonitorForKeyUse(warningID, message, component, parentType) {
|
|
var ownerName = getCurrentOwnerDisplayName();
|
|
var parentName = parentType.displayName;
|
|
|
|
var useName = ownerName || parentName;
|
|
var memoizer = ownerHasKeyUseWarning[warningID];
|
|
if (memoizer.hasOwnProperty(useName)) {
|
|
return;
|
|
}
|
|
memoizer[useName] = true;
|
|
|
|
message += ownerName ?
|
|
` Check the render method of ${ownerName}.` :
|
|
` Check the renderComponent call using <${parentName}>.`;
|
|
|
|
// Usually the current owner is the offender, but if it accepts children as a
|
|
// property, it may be the creator of the child that's responsible for
|
|
// assigning it a key.
|
|
var childOwnerName = null;
|
|
if (component._owner && component._owner !== ReactCurrentOwner.current) {
|
|
// Name of the component that originally created this child.
|
|
childOwnerName = component._owner.constructor.displayName;
|
|
|
|
message += ` It was passed a child from ${childOwnerName}.`;
|
|
}
|
|
|
|
message += ' See http://fb.me/react-warning-keys for more information.';
|
|
monitorCodeUse(warningID, {
|
|
component: useName,
|
|
componentOwner: childOwnerName
|
|
});
|
|
console.warn(message);
|
|
}
|
|
|
|
/**
|
|
* Log that we're using an object map. We're considering deprecating this
|
|
* feature and replace it with proper Map and ImmutableMap data structures.
|
|
*
|
|
* @internal
|
|
*/
|
|
function monitorUseOfObjectMap() {
|
|
var currentName = getCurrentOwnerDisplayName() || '';
|
|
if (ownerHasMonitoredObjectMap.hasOwnProperty(currentName)) {
|
|
return;
|
|
}
|
|
ownerHasMonitoredObjectMap[currentName] = true;
|
|
monitorCodeUse('react_object_map_children');
|
|
}
|
|
|
|
/**
|
|
* Ensure that every component either is passed in a static location, in an
|
|
* array with an explicit keys property defined, or in an object literal
|
|
* with valid key property.
|
|
*
|
|
* @internal
|
|
* @param {*} component Statically passed child of any type.
|
|
* @param {*} parentType component's parent's type.
|
|
* @return {boolean}
|
|
*/
|
|
function validateChildKeys(component, parentType) {
|
|
if (Array.isArray(component)) {
|
|
for (var i = 0; i < component.length; i++) {
|
|
var child = component[i];
|
|
if (ReactDescriptor.isValidDescriptor(child)) {
|
|
validateExplicitKey(child, parentType);
|
|
}
|
|
}
|
|
} else if (ReactDescriptor.isValidDescriptor(component)) {
|
|
// This component was passed in a valid location.
|
|
component._store.validated = true;
|
|
} else if (component && typeof component === 'object') {
|
|
monitorUseOfObjectMap();
|
|
for (var name in component) {
|
|
validatePropertyKey(name, component[name], parentType);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Assert that the props are valid
|
|
*
|
|
* @param {string} componentName Name of the component for error messages.
|
|
* @param {object} propTypes Map of prop name to a ReactPropType
|
|
* @param {object} props
|
|
* @param {string} location e.g. "prop", "context", "child context"
|
|
* @private
|
|
*/
|
|
function checkPropTypes(componentName, propTypes, props, location) {
|
|
for (var propName in propTypes) {
|
|
if (propTypes.hasOwnProperty(propName)) {
|
|
var error;
|
|
// Prop type validation may throw. In case they do, we don't want to
|
|
// fail the render phase where it didn't fail before. So we log it.
|
|
// After these have been cleaned up, we'll let them throw.
|
|
try {
|
|
error = propTypes[propName](props, propName, componentName, location);
|
|
} catch (ex) {
|
|
error = ex;
|
|
}
|
|
if (error instanceof Error && !(error.message in loggedTypeFailures)) {
|
|
// Only monitor this failure once because there tends to be a lot of the
|
|
// same error.
|
|
loggedTypeFailures[error.message] = true;
|
|
// This will soon use the warning module
|
|
monitorCodeUse(
|
|
'react_failed_descriptor_type_check',
|
|
{ message: error.message }
|
|
);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
var ReactDescriptorValidator = {
|
|
|
|
createDescriptor: function(type, props, children) {
|
|
var descriptor = ReactDescriptor.createDescriptor.apply(this, arguments);
|
|
|
|
// The result can be nullish if a mock or a custom function is used.
|
|
// TODO: Drop this when these are no longer allowed as the type argument.
|
|
if (descriptor == null) {
|
|
return descriptor;
|
|
}
|
|
|
|
for (var i = 2; i < arguments.length; i++) {
|
|
validateChildKeys(arguments[i], type);
|
|
}
|
|
|
|
var name = type.displayName;
|
|
if (type.propTypes) {
|
|
checkPropTypes(
|
|
name,
|
|
type.propTypes,
|
|
descriptor.props,
|
|
ReactPropTypeLocations.prop
|
|
);
|
|
}
|
|
if (type.contextTypes) {
|
|
checkPropTypes(
|
|
name,
|
|
type.contextTypes,
|
|
descriptor._context,
|
|
ReactPropTypeLocations.context
|
|
);
|
|
}
|
|
return descriptor;
|
|
},
|
|
|
|
createFactory: function(type) {
|
|
if (type.isReactLegacyFactory) {
|
|
// This is probably a legacy factory created by ReactCompositeComponent.
|
|
// We unwrap it to get to the underlying class.
|
|
// TODO: Drop this check when we drop ReactLegacyDescriptor.
|
|
type = type.type;
|
|
}
|
|
var validatedFactory = ReactDescriptorValidator.createDescriptor.bind(
|
|
null,
|
|
type
|
|
);
|
|
validatedFactory.type = type;
|
|
return validatedFactory;
|
|
}
|
|
|
|
};
|
|
|
|
module.exports = ReactDescriptorValidator;
|