You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

252 lines
8.9 KiB

/**
* Copyright (c) 2014-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
*/
/**
* ReactElementValidator provides a wrapper around a element factory
* which validates the props passed to the element. 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 ReactCurrentOwner = require('./ReactCurrentOwner');
var ReactComponentTreeHook = require('./ReactComponentTreeHook');
var ReactElement = require('./ReactElement');
var checkReactTypeSpec = require('./checkReactTypeSpec');
var canDefineProperty = require('./canDefineProperty');
var getIteratorFn = require('./getIteratorFn');
var warning = require('fbjs/lib/warning');
var lowPriorityWarning = require('./lowPriorityWarning');
function getDeclarationErrorAddendum() {
if (ReactCurrentOwner.current) {
var name = ReactCurrentOwner.current.getName();
if (name) {
return ' Check the render method of `' + name + '`.';
}
}
return '';
}
function getSourceInfoErrorAddendum(elementProps) {
if (elementProps !== null && elementProps !== undefined && elementProps.__source !== undefined) {
var source = elementProps.__source;
var fileName = source.fileName.replace(/^.*[\\\/]/, '');
var lineNumber = source.lineNumber;
return ' Check your code at ' + fileName + ':' + lineNumber + '.';
}
return '';
}
/**
* 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 = {};
function getCurrentComponentErrorInfo(parentType) {
var info = getDeclarationErrorAddendum();
if (!info) {
var parentName = typeof parentType === 'string' ? parentType : parentType.displayName || parentType.name;
if (parentName) {
info = ' Check the top-level render call using <' + parentName + '>.';
}
}
return info;
}
/**
* Warn if the element doesn't have an explicit key assigned to it.
* This element 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. Error statuses are cached so a warning
* will only be shown once.
*
* @internal
* @param {ReactElement} element Element that requires a key.
* @param {*} parentType element's parent's type.
*/
function validateExplicitKey(element, parentType) {
if (!element._store || element._store.validated || element.key != null) {
return;
}
element._store.validated = true;
var memoizer = ownerHasKeyUseWarning.uniqueKey || (ownerHasKeyUseWarning.uniqueKey = {});
var currentComponentErrorInfo = getCurrentComponentErrorInfo(parentType);
if (memoizer[currentComponentErrorInfo]) {
return;
}
memoizer[currentComponentErrorInfo] = true;
// 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 childOwner = '';
if (element && element._owner && element._owner !== ReactCurrentOwner.current) {
// Give the component that originally created this child.
childOwner = ' It was passed a child from ' + element._owner.getName() + '.';
}
process.env.NODE_ENV !== 'production' ? warning(false, 'Each child in an array or iterator should have a unique "key" prop.' + '%s%s See https://fb.me/react-warning-keys for more information.%s', currentComponentErrorInfo, childOwner, ReactComponentTreeHook.getCurrentStackAddendum(element)) : void 0;
}
/**
* Ensure that every element 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 {ReactNode} node Statically passed child of any type.
* @param {*} parentType node's parent's type.
*/
function validateChildKeys(node, parentType) {
if (typeof node !== 'object') {
return;
}
if (Array.isArray(node)) {
for (var i = 0; i < node.length; i++) {
var child = node[i];
if (ReactElement.isValidElement(child)) {
validateExplicitKey(child, parentType);
}
}
} else if (ReactElement.isValidElement(node)) {
// This element was passed in a valid location.
if (node._store) {
node._store.validated = true;
}
} else if (node) {
var iteratorFn = getIteratorFn(node);
// Entry iterators provide implicit keys.
if (iteratorFn) {
if (iteratorFn !== node.entries) {
var iterator = iteratorFn.call(node);
var step;
while (!(step = iterator.next()).done) {
if (ReactElement.isValidElement(step.value)) {
validateExplicitKey(step.value, parentType);
}
}
}
}
}
}
/**
* Given an element, validate that its props follow the propTypes definition,
* provided by the type.
*
* @param {ReactElement} element
*/
function validatePropTypes(element) {
var componentClass = element.type;
if (typeof componentClass !== 'function') {
return;
}
var name = componentClass.displayName || componentClass.name;
if (componentClass.propTypes) {
checkReactTypeSpec(componentClass.propTypes, element.props, 'prop', name, element, null);
}
if (typeof componentClass.getDefaultProps === 'function') {
process.env.NODE_ENV !== 'production' ? warning(componentClass.getDefaultProps.isReactClassApproved, 'getDefaultProps is only used on classic React.createClass ' + 'definitions. Use a static property named `defaultProps` instead.') : void 0;
}
}
var ReactElementValidator = {
createElement: function (type, props, children) {
var validType = typeof type === 'string' || typeof type === 'function';
// We warn in this case but don't throw. We expect the element creation to
// succeed and there will likely be errors in render.
if (!validType) {
if (typeof type !== 'function' && typeof type !== 'string') {
var info = '';
if (type === undefined || typeof type === 'object' && type !== null && Object.keys(type).length === 0) {
info += ' You likely forgot to export your component from the file ' + "it's defined in.";
}
var sourceInfo = getSourceInfoErrorAddendum(props);
if (sourceInfo) {
info += sourceInfo;
} else {
info += getDeclarationErrorAddendum();
}
info += ReactComponentTreeHook.getCurrentStackAddendum();
var currentSource = props !== null && props !== undefined && props.__source !== undefined ? props.__source : null;
ReactComponentTreeHook.pushNonStandardWarningStack(true, currentSource);
process.env.NODE_ENV !== 'production' ? warning(false, 'React.createElement: type is invalid -- expected a string (for ' + 'built-in components) or a class/function (for composite ' + 'components) but got: %s.%s', type == null ? type : typeof type, info) : void 0;
ReactComponentTreeHook.popNonStandardWarningStack();
}
}
var element = ReactElement.createElement.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 (element == null) {
return element;
}
// Skip key warning if the type isn't valid since our key validation logic
// doesn't expect a non-string/function type and can throw confusing errors.
// We don't want exception behavior to differ between dev and prod.
// (Rendering will throw with a helpful message and as soon as the type is
// fixed, the key warnings will appear.)
if (validType) {
for (var i = 2; i < arguments.length; i++) {
validateChildKeys(arguments[i], type);
}
}
validatePropTypes(element);
return element;
},
createFactory: function (type) {
var validatedFactory = ReactElementValidator.createElement.bind(null, type);
// Legacy hook TODO: Warn if this is accessed
validatedFactory.type = type;
if (process.env.NODE_ENV !== 'production') {
if (canDefineProperty) {
Object.defineProperty(validatedFactory, 'type', {
enumerable: false,
get: function () {
lowPriorityWarning(false, 'Factory.type is deprecated. Access the class directly ' + 'before passing it to createFactory.');
Object.defineProperty(this, 'type', {
value: type
});
return type;
}
});
}
}
return validatedFactory;
},
cloneElement: function (element, props, children) {
var newElement = ReactElement.cloneElement.apply(this, arguments);
for (var i = 2; i < arguments.length; i++) {
validateChildKeys(arguments[i], newElement.type);
}
validatePropTypes(newElement);
return newElement;
}
};
module.exports = ReactElementValidator;