UNPKG

21.6 kBJavaScriptView Raw
1/**
2 * Copyright (c) 2013-present, Facebook, Inc.
3 *
4 * This source code is licensed under the MIT license found in the
5 * LICENSE file in the root directory of this source tree.
6 */
7
8'use strict';
9
10var ReactIs = require('react-is');
11var assign = require('object-assign');
12
13var ReactPropTypesSecret = require('./lib/ReactPropTypesSecret');
14var checkPropTypes = require('./checkPropTypes');
15
16var has = Function.call.bind(Object.prototype.hasOwnProperty);
17var printWarning = function() {};
18
19if (process.env.NODE_ENV !== 'production') {
20 printWarning = function(text) {
21 var message = 'Warning: ' + text;
22 if (typeof console !== 'undefined') {
23 console.error(message);
24 }
25 try {
26 // --- Welcome to debugging React ---
27 // This error was thrown as a convenience so that you can use this stack
28 // to find the callsite that caused this warning to fire.
29 throw new Error(message);
30 } catch (x) {}
31 };
32}
33
34function emptyFunctionThatReturnsNull() {
35 return null;
36}
37
38module.exports = function(isValidElement, throwOnDirectAccess) {
39 /* global Symbol */
40 var ITERATOR_SYMBOL = typeof Symbol === 'function' && Symbol.iterator;
41 var FAUX_ITERATOR_SYMBOL = '@@iterator'; // Before Symbol spec.
42
43 /**
44 * Returns the iterator method function contained on the iterable object.
45 *
46 * Be sure to invoke the function with the iterable as context:
47 *
48 * var iteratorFn = getIteratorFn(myIterable);
49 * if (iteratorFn) {
50 * var iterator = iteratorFn.call(myIterable);
51 * ...
52 * }
53 *
54 * @param {?object} maybeIterable
55 * @return {?function}
56 */
57 function getIteratorFn(maybeIterable) {
58 var iteratorFn = maybeIterable && (ITERATOR_SYMBOL && maybeIterable[ITERATOR_SYMBOL] || maybeIterable[FAUX_ITERATOR_SYMBOL]);
59 if (typeof iteratorFn === 'function') {
60 return iteratorFn;
61 }
62 }
63
64 /**
65 * Collection of methods that allow declaration and validation of props that are
66 * supplied to React components. Example usage:
67 *
68 * var Props = require('ReactPropTypes');
69 * var MyArticle = React.createClass({
70 * propTypes: {
71 * // An optional string prop named "description".
72 * description: Props.string,
73 *
74 * // A required enum prop named "category".
75 * category: Props.oneOf(['News','Photos']).isRequired,
76 *
77 * // A prop named "dialog" that requires an instance of Dialog.
78 * dialog: Props.instanceOf(Dialog).isRequired
79 * },
80 * render: function() { ... }
81 * });
82 *
83 * A more formal specification of how these methods are used:
84 *
85 * type := array|bool|func|object|number|string|oneOf([...])|instanceOf(...)
86 * decl := ReactPropTypes.{type}(.isRequired)?
87 *
88 * Each and every declaration produces a function with the same signature. This
89 * allows the creation of custom validation functions. For example:
90 *
91 * var MyLink = React.createClass({
92 * propTypes: {
93 * // An optional string or URI prop named "href".
94 * href: function(props, propName, componentName) {
95 * var propValue = props[propName];
96 * if (propValue != null && typeof propValue !== 'string' &&
97 * !(propValue instanceof URI)) {
98 * return new Error(
99 * 'Expected a string or an URI for ' + propName + ' in ' +
100 * componentName
101 * );
102 * }
103 * }
104 * },
105 * render: function() {...}
106 * });
107 *
108 * @internal
109 */
110
111 var ANONYMOUS = '<<anonymous>>';
112
113 // Important!
114 // Keep this list in sync with production version in `./factoryWithThrowingShims.js`.
115 var ReactPropTypes = {
116 array: createPrimitiveTypeChecker('array'),
117 bool: createPrimitiveTypeChecker('boolean'),
118 func: createPrimitiveTypeChecker('function'),
119 number: createPrimitiveTypeChecker('number'),
120 object: createPrimitiveTypeChecker('object'),
121 string: createPrimitiveTypeChecker('string'),
122 symbol: createPrimitiveTypeChecker('symbol'),
123
124 any: createAnyTypeChecker(),
125 arrayOf: createArrayOfTypeChecker,
126 element: createElementTypeChecker(),
127 elementType: createElementTypeTypeChecker(),
128 instanceOf: createInstanceTypeChecker,
129 node: createNodeChecker(),
130 objectOf: createObjectOfTypeChecker,
131 oneOf: createEnumTypeChecker,
132 oneOfType: createUnionTypeChecker,
133 shape: createShapeTypeChecker,
134 exact: createStrictShapeTypeChecker,
135 };
136
137 /**
138 * inlined Object.is polyfill to avoid requiring consumers ship their own
139 * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/is
140 */
141 /*eslint-disable no-self-compare*/
142 function is(x, y) {
143 // SameValue algorithm
144 if (x === y) {
145 // Steps 1-5, 7-10
146 // Steps 6.b-6.e: +0 != -0
147 return x !== 0 || 1 / x === 1 / y;
148 } else {
149 // Step 6.a: NaN == NaN
150 return x !== x && y !== y;
151 }
152 }
153 /*eslint-enable no-self-compare*/
154
155 /**
156 * We use an Error-like object for backward compatibility as people may call
157 * PropTypes directly and inspect their output. However, we don't use real
158 * Errors anymore. We don't inspect their stack anyway, and creating them
159 * is prohibitively expensive if they are created too often, such as what
160 * happens in oneOfType() for any type before the one that matched.
161 */
162 function PropTypeError(message) {
163 this.message = message;
164 this.stack = '';
165 }
166 // Make `instanceof Error` still work for returned errors.
167 PropTypeError.prototype = Error.prototype;
168
169 function createChainableTypeChecker(validate) {
170 if (process.env.NODE_ENV !== 'production') {
171 var manualPropTypeCallCache = {};
172 var manualPropTypeWarningCount = 0;
173 }
174 function checkType(isRequired, props, propName, componentName, location, propFullName, secret) {
175 componentName = componentName || ANONYMOUS;
176 propFullName = propFullName || propName;
177
178 if (secret !== ReactPropTypesSecret) {
179 if (throwOnDirectAccess) {
180 // New behavior only for users of `prop-types` package
181 var err = new Error(
182 'Calling PropTypes validators directly is not supported by the `prop-types` package. ' +
183 'Use `PropTypes.checkPropTypes()` to call them. ' +
184 'Read more at http://fb.me/use-check-prop-types'
185 );
186 err.name = 'Invariant Violation';
187 throw err;
188 } else if (process.env.NODE_ENV !== 'production' && typeof console !== 'undefined') {
189 // Old behavior for people using React.PropTypes
190 var cacheKey = componentName + ':' + propName;
191 if (
192 !manualPropTypeCallCache[cacheKey] &&
193 // Avoid spamming the console because they are often not actionable except for lib authors
194 manualPropTypeWarningCount < 3
195 ) {
196 printWarning(
197 'You are manually calling a React.PropTypes validation ' +
198 'function for the `' + propFullName + '` prop on `' + componentName + '`. This is deprecated ' +
199 'and will throw in the standalone `prop-types` package. ' +
200 'You may be seeing this warning due to a third-party PropTypes ' +
201 'library. See https://fb.me/react-warning-dont-call-proptypes ' + 'for details.'
202 );
203 manualPropTypeCallCache[cacheKey] = true;
204 manualPropTypeWarningCount++;
205 }
206 }
207 }
208 if (props[propName] == null) {
209 if (isRequired) {
210 if (props[propName] === null) {
211 return new PropTypeError('The ' + location + ' `' + propFullName + '` is marked as required ' + ('in `' + componentName + '`, but its value is `null`.'));
212 }
213 return new PropTypeError('The ' + location + ' `' + propFullName + '` is marked as required in ' + ('`' + componentName + '`, but its value is `undefined`.'));
214 }
215 return null;
216 } else {
217 return validate(props, propName, componentName, location, propFullName);
218 }
219 }
220
221 var chainedCheckType = checkType.bind(null, false);
222 chainedCheckType.isRequired = checkType.bind(null, true);
223
224 return chainedCheckType;
225 }
226
227 function createPrimitiveTypeChecker(expectedType) {
228 function validate(props, propName, componentName, location, propFullName, secret) {
229 var propValue = props[propName];
230 var propType = getPropType(propValue);
231 if (propType !== expectedType) {
232 // `propValue` being instance of, say, date/regexp, pass the 'object'
233 // check, but we can offer a more precise error message here rather than
234 // 'of type `object`'.
235 var preciseType = getPreciseType(propValue);
236
237 return new PropTypeError('Invalid ' + location + ' `' + propFullName + '` of type ' + ('`' + preciseType + '` supplied to `' + componentName + '`, expected ') + ('`' + expectedType + '`.'));
238 }
239 return null;
240 }
241 return createChainableTypeChecker(validate);
242 }
243
244 function createAnyTypeChecker() {
245 return createChainableTypeChecker(emptyFunctionThatReturnsNull);
246 }
247
248 function createArrayOfTypeChecker(typeChecker) {
249 function validate(props, propName, componentName, location, propFullName) {
250 if (typeof typeChecker !== 'function') {
251 return new PropTypeError('Property `' + propFullName + '` of component `' + componentName + '` has invalid PropType notation inside arrayOf.');
252 }
253 var propValue = props[propName];
254 if (!Array.isArray(propValue)) {
255 var propType = getPropType(propValue);
256 return new PropTypeError('Invalid ' + location + ' `' + propFullName + '` of type ' + ('`' + propType + '` supplied to `' + componentName + '`, expected an array.'));
257 }
258 for (var i = 0; i < propValue.length; i++) {
259 var error = typeChecker(propValue, i, componentName, location, propFullName + '[' + i + ']', ReactPropTypesSecret);
260 if (error instanceof Error) {
261 return error;
262 }
263 }
264 return null;
265 }
266 return createChainableTypeChecker(validate);
267 }
268
269 function createElementTypeChecker() {
270 function validate(props, propName, componentName, location, propFullName) {
271 var propValue = props[propName];
272 if (!isValidElement(propValue)) {
273 var propType = getPropType(propValue);
274 return new PropTypeError('Invalid ' + location + ' `' + propFullName + '` of type ' + ('`' + propType + '` supplied to `' + componentName + '`, expected a single ReactElement.'));
275 }
276 return null;
277 }
278 return createChainableTypeChecker(validate);
279 }
280
281 function createElementTypeTypeChecker() {
282 function validate(props, propName, componentName, location, propFullName) {
283 var propValue = props[propName];
284 if (!ReactIs.isValidElementType(propValue)) {
285 var propType = getPropType(propValue);
286 return new PropTypeError('Invalid ' + location + ' `' + propFullName + '` of type ' + ('`' + propType + '` supplied to `' + componentName + '`, expected a single ReactElement type.'));
287 }
288 return null;
289 }
290 return createChainableTypeChecker(validate);
291 }
292
293 function createInstanceTypeChecker(expectedClass) {
294 function validate(props, propName, componentName, location, propFullName) {
295 if (!(props[propName] instanceof expectedClass)) {
296 var expectedClassName = expectedClass.name || ANONYMOUS;
297 var actualClassName = getClassName(props[propName]);
298 return new PropTypeError('Invalid ' + location + ' `' + propFullName + '` of type ' + ('`' + actualClassName + '` supplied to `' + componentName + '`, expected ') + ('instance of `' + expectedClassName + '`.'));
299 }
300 return null;
301 }
302 return createChainableTypeChecker(validate);
303 }
304
305 function createEnumTypeChecker(expectedValues) {
306 if (!Array.isArray(expectedValues)) {
307 if (process.env.NODE_ENV !== 'production') {
308 if (arguments.length > 1) {
309 printWarning(
310 'Invalid arguments supplied to oneOf, expected an array, got ' + arguments.length + ' arguments. ' +
311 'A common mistake is to write oneOf(x, y, z) instead of oneOf([x, y, z]).'
312 );
313 } else {
314 printWarning('Invalid argument supplied to oneOf, expected an array.');
315 }
316 }
317 return emptyFunctionThatReturnsNull;
318 }
319
320 function validate(props, propName, componentName, location, propFullName) {
321 var propValue = props[propName];
322 for (var i = 0; i < expectedValues.length; i++) {
323 if (is(propValue, expectedValues[i])) {
324 return null;
325 }
326 }
327
328 var valuesString = JSON.stringify(expectedValues, function replacer(key, value) {
329 var type = getPreciseType(value);
330 if (type === 'symbol') {
331 return String(value);
332 }
333 return value;
334 });
335 return new PropTypeError('Invalid ' + location + ' `' + propFullName + '` of value `' + String(propValue) + '` ' + ('supplied to `' + componentName + '`, expected one of ' + valuesString + '.'));
336 }
337 return createChainableTypeChecker(validate);
338 }
339
340 function createObjectOfTypeChecker(typeChecker) {
341 function validate(props, propName, componentName, location, propFullName) {
342 if (typeof typeChecker !== 'function') {
343 return new PropTypeError('Property `' + propFullName + '` of component `' + componentName + '` has invalid PropType notation inside objectOf.');
344 }
345 var propValue = props[propName];
346 var propType = getPropType(propValue);
347 if (propType !== 'object') {
348 return new PropTypeError('Invalid ' + location + ' `' + propFullName + '` of type ' + ('`' + propType + '` supplied to `' + componentName + '`, expected an object.'));
349 }
350 for (var key in propValue) {
351 if (has(propValue, key)) {
352 var error = typeChecker(propValue, key, componentName, location, propFullName + '.' + key, ReactPropTypesSecret);
353 if (error instanceof Error) {
354 return error;
355 }
356 }
357 }
358 return null;
359 }
360 return createChainableTypeChecker(validate);
361 }
362
363 function createUnionTypeChecker(arrayOfTypeCheckers) {
364 if (!Array.isArray(arrayOfTypeCheckers)) {
365 process.env.NODE_ENV !== 'production' ? printWarning('Invalid argument supplied to oneOfType, expected an instance of array.') : void 0;
366 return emptyFunctionThatReturnsNull;
367 }
368
369 for (var i = 0; i < arrayOfTypeCheckers.length; i++) {
370 var checker = arrayOfTypeCheckers[i];
371 if (typeof checker !== 'function') {
372 printWarning(
373 'Invalid argument supplied to oneOfType. Expected an array of check functions, but ' +
374 'received ' + getPostfixForTypeWarning(checker) + ' at index ' + i + '.'
375 );
376 return emptyFunctionThatReturnsNull;
377 }
378 }
379
380 function validate(props, propName, componentName, location, propFullName) {
381 for (var i = 0; i < arrayOfTypeCheckers.length; i++) {
382 var checker = arrayOfTypeCheckers[i];
383 if (checker(props, propName, componentName, location, propFullName, ReactPropTypesSecret) == null) {
384 return null;
385 }
386 }
387
388 return new PropTypeError('Invalid ' + location + ' `' + propFullName + '` supplied to ' + ('`' + componentName + '`.'));
389 }
390 return createChainableTypeChecker(validate);
391 }
392
393 function createNodeChecker() {
394 function validate(props, propName, componentName, location, propFullName) {
395 if (!isNode(props[propName])) {
396 return new PropTypeError('Invalid ' + location + ' `' + propFullName + '` supplied to ' + ('`' + componentName + '`, expected a ReactNode.'));
397 }
398 return null;
399 }
400 return createChainableTypeChecker(validate);
401 }
402
403 function createShapeTypeChecker(shapeTypes) {
404 function validate(props, propName, componentName, location, propFullName) {
405 var propValue = props[propName];
406 var propType = getPropType(propValue);
407 if (propType !== 'object') {
408 return new PropTypeError('Invalid ' + location + ' `' + propFullName + '` of type `' + propType + '` ' + ('supplied to `' + componentName + '`, expected `object`.'));
409 }
410 for (var key in shapeTypes) {
411 var checker = shapeTypes[key];
412 if (!checker) {
413 continue;
414 }
415 var error = checker(propValue, key, componentName, location, propFullName + '.' + key, ReactPropTypesSecret);
416 if (error) {
417 return error;
418 }
419 }
420 return null;
421 }
422 return createChainableTypeChecker(validate);
423 }
424
425 function createStrictShapeTypeChecker(shapeTypes) {
426 function validate(props, propName, componentName, location, propFullName) {
427 var propValue = props[propName];
428 var propType = getPropType(propValue);
429 if (propType !== 'object') {
430 return new PropTypeError('Invalid ' + location + ' `' + propFullName + '` of type `' + propType + '` ' + ('supplied to `' + componentName + '`, expected `object`.'));
431 }
432 // We need to check all keys in case some are required but missing from
433 // props.
434 var allKeys = assign({}, props[propName], shapeTypes);
435 for (var key in allKeys) {
436 var checker = shapeTypes[key];
437 if (!checker) {
438 return new PropTypeError(
439 'Invalid ' + location + ' `' + propFullName + '` key `' + key + '` supplied to `' + componentName + '`.' +
440 '\nBad object: ' + JSON.stringify(props[propName], null, ' ') +
441 '\nValid keys: ' + JSON.stringify(Object.keys(shapeTypes), null, ' ')
442 );
443 }
444 var error = checker(propValue, key, componentName, location, propFullName + '.' + key, ReactPropTypesSecret);
445 if (error) {
446 return error;
447 }
448 }
449 return null;
450 }
451
452 return createChainableTypeChecker(validate);
453 }
454
455 function isNode(propValue) {
456 switch (typeof propValue) {
457 case 'number':
458 case 'string':
459 case 'undefined':
460 return true;
461 case 'boolean':
462 return !propValue;
463 case 'object':
464 if (Array.isArray(propValue)) {
465 return propValue.every(isNode);
466 }
467 if (propValue === null || isValidElement(propValue)) {
468 return true;
469 }
470
471 var iteratorFn = getIteratorFn(propValue);
472 if (iteratorFn) {
473 var iterator = iteratorFn.call(propValue);
474 var step;
475 if (iteratorFn !== propValue.entries) {
476 while (!(step = iterator.next()).done) {
477 if (!isNode(step.value)) {
478 return false;
479 }
480 }
481 } else {
482 // Iterator will provide entry [k,v] tuples rather than values.
483 while (!(step = iterator.next()).done) {
484 var entry = step.value;
485 if (entry) {
486 if (!isNode(entry[1])) {
487 return false;
488 }
489 }
490 }
491 }
492 } else {
493 return false;
494 }
495
496 return true;
497 default:
498 return false;
499 }
500 }
501
502 function isSymbol(propType, propValue) {
503 // Native Symbol.
504 if (propType === 'symbol') {
505 return true;
506 }
507
508 // falsy value can't be a Symbol
509 if (!propValue) {
510 return false;
511 }
512
513 // 19.4.3.5 Symbol.prototype[@@toStringTag] === 'Symbol'
514 if (propValue['@@toStringTag'] === 'Symbol') {
515 return true;
516 }
517
518 // Fallback for non-spec compliant Symbols which are polyfilled.
519 if (typeof Symbol === 'function' && propValue instanceof Symbol) {
520 return true;
521 }
522
523 return false;
524 }
525
526 // Equivalent of `typeof` but with special handling for array and regexp.
527 function getPropType(propValue) {
528 var propType = typeof propValue;
529 if (Array.isArray(propValue)) {
530 return 'array';
531 }
532 if (propValue instanceof RegExp) {
533 // Old webkits (at least until Android 4.0) return 'function' rather than
534 // 'object' for typeof a RegExp. We'll normalize this here so that /bla/
535 // passes PropTypes.object.
536 return 'object';
537 }
538 if (isSymbol(propType, propValue)) {
539 return 'symbol';
540 }
541 return propType;
542 }
543
544 // This handles more types than `getPropType`. Only used for error messages.
545 // See `createPrimitiveTypeChecker`.
546 function getPreciseType(propValue) {
547 if (typeof propValue === 'undefined' || propValue === null) {
548 return '' + propValue;
549 }
550 var propType = getPropType(propValue);
551 if (propType === 'object') {
552 if (propValue instanceof Date) {
553 return 'date';
554 } else if (propValue instanceof RegExp) {
555 return 'regexp';
556 }
557 }
558 return propType;
559 }
560
561 // Returns a string that is postfixed to a warning about an invalid type.
562 // For example, "undefined" or "of type array"
563 function getPostfixForTypeWarning(value) {
564 var type = getPreciseType(value);
565 switch (type) {
566 case 'array':
567 case 'object':
568 return 'an ' + type;
569 case 'boolean':
570 case 'date':
571 case 'regexp':
572 return 'a ' + type;
573 default:
574 return type;
575 }
576 }
577
578 // Returns class name of the object, if any.
579 function getClassName(propValue) {
580 if (!propValue.constructor || !propValue.constructor.name) {
581 return ANONYMOUS;
582 }
583 return propValue.constructor.name;
584 }
585
586 ReactPropTypes.checkPropTypes = checkPropTypes;
587 ReactPropTypes.resetWarningCache = checkPropTypes.resetWarningCache;
588 ReactPropTypes.PropTypes = ReactPropTypes;
589
590 return ReactPropTypes;
591};