UNPKG

15.8 kBJavaScriptView Raw
1/**
2 * Copyright 2013-present, Facebook, Inc.
3 * All rights reserved.
4 *
5 * This source code is licensed under the BSD-style license found in the
6 * LICENSE file in the root directory of this source tree. An additional grant
7 * of patent rights can be found in the PATENTS file in the same directory.
8 *
9 */
10
11'use strict';
12
13var ReactElement = require('./ReactElement');
14var ReactPropTypeLocationNames = require('./ReactPropTypeLocationNames');
15var ReactPropTypesSecret = require('./ReactPropTypesSecret');
16
17var emptyFunction = require('fbjs/lib/emptyFunction');
18var getIteratorFn = require('./getIteratorFn');
19var warning = require('fbjs/lib/warning');
20
21/**
22 * Collection of methods that allow declaration and validation of props that are
23 * supplied to React components. Example usage:
24 *
25 * var Props = require('ReactPropTypes');
26 * var MyArticle = React.createClass({
27 * propTypes: {
28 * // An optional string prop named "description".
29 * description: Props.string,
30 *
31 * // A required enum prop named "category".
32 * category: Props.oneOf(['News','Photos']).isRequired,
33 *
34 * // A prop named "dialog" that requires an instance of Dialog.
35 * dialog: Props.instanceOf(Dialog).isRequired
36 * },
37 * render: function() { ... }
38 * });
39 *
40 * A more formal specification of how these methods are used:
41 *
42 * type := array|bool|func|object|number|string|oneOf([...])|instanceOf(...)
43 * decl := ReactPropTypes.{type}(.isRequired)?
44 *
45 * Each and every declaration produces a function with the same signature. This
46 * allows the creation of custom validation functions. For example:
47 *
48 * var MyLink = React.createClass({
49 * propTypes: {
50 * // An optional string or URI prop named "href".
51 * href: function(props, propName, componentName) {
52 * var propValue = props[propName];
53 * if (propValue != null && typeof propValue !== 'string' &&
54 * !(propValue instanceof URI)) {
55 * return new Error(
56 * 'Expected a string or an URI for ' + propName + ' in ' +
57 * componentName
58 * );
59 * }
60 * }
61 * },
62 * render: function() {...}
63 * });
64 *
65 * @internal
66 */
67
68var ANONYMOUS = '<<anonymous>>';
69
70var ReactPropTypes = {
71 array: createPrimitiveTypeChecker('array'),
72 bool: createPrimitiveTypeChecker('boolean'),
73 func: createPrimitiveTypeChecker('function'),
74 number: createPrimitiveTypeChecker('number'),
75 object: createPrimitiveTypeChecker('object'),
76 string: createPrimitiveTypeChecker('string'),
77 symbol: createPrimitiveTypeChecker('symbol'),
78
79 any: createAnyTypeChecker(),
80 arrayOf: createArrayOfTypeChecker,
81 element: createElementTypeChecker(),
82 instanceOf: createInstanceTypeChecker,
83 node: createNodeChecker(),
84 objectOf: createObjectOfTypeChecker,
85 oneOf: createEnumTypeChecker,
86 oneOfType: createUnionTypeChecker,
87 shape: createShapeTypeChecker
88};
89
90/**
91 * inlined Object.is polyfill to avoid requiring consumers ship their own
92 * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/is
93 */
94/*eslint-disable no-self-compare*/
95function is(x, y) {
96 // SameValue algorithm
97 if (x === y) {
98 // Steps 1-5, 7-10
99 // Steps 6.b-6.e: +0 != -0
100 return x !== 0 || 1 / x === 1 / y;
101 } else {
102 // Step 6.a: NaN == NaN
103 return x !== x && y !== y;
104 }
105}
106/*eslint-enable no-self-compare*/
107
108/**
109 * We use an Error-like object for backward compatibility as people may call
110 * PropTypes directly and inspect their output. However we don't use real
111 * Errors anymore. We don't inspect their stack anyway, and creating them
112 * is prohibitively expensive if they are created too often, such as what
113 * happens in oneOfType() for any type before the one that matched.
114 */
115function PropTypeError(message) {
116 this.message = message;
117 this.stack = '';
118}
119// Make `instanceof Error` still work for returned errors.
120PropTypeError.prototype = Error.prototype;
121
122function createChainableTypeChecker(validate) {
123 if (process.env.NODE_ENV !== 'production') {
124 var manualPropTypeCallCache = {};
125 }
126 function checkType(isRequired, props, propName, componentName, location, propFullName, secret) {
127 componentName = componentName || ANONYMOUS;
128 propFullName = propFullName || propName;
129 if (process.env.NODE_ENV !== 'production') {
130 if (secret !== ReactPropTypesSecret && typeof console !== 'undefined') {
131 var cacheKey = componentName + ':' + propName;
132 if (!manualPropTypeCallCache[cacheKey]) {
133 process.env.NODE_ENV !== 'production' ? warning(false, 'You are manually calling a React.PropTypes validation ' + 'function for the `%s` prop on `%s`. This is deprecated ' + 'and will not work in production with the next major version. ' + 'You may be seeing this warning due to a third-party PropTypes ' + 'library. See https://fb.me/react-warning-dont-call-proptypes ' + 'for details.', propFullName, componentName) : void 0;
134 manualPropTypeCallCache[cacheKey] = true;
135 }
136 }
137 }
138 if (props[propName] == null) {
139 var locationName = ReactPropTypeLocationNames[location];
140 if (isRequired) {
141 if (props[propName] === null) {
142 return new PropTypeError('The ' + locationName + ' `' + propFullName + '` is marked as required ' + ('in `' + componentName + '`, but its value is `null`.'));
143 }
144 return new PropTypeError('The ' + locationName + ' `' + propFullName + '` is marked as required in ' + ('`' + componentName + '`, but its value is `undefined`.'));
145 }
146 return null;
147 } else {
148 return validate(props, propName, componentName, location, propFullName);
149 }
150 }
151
152 var chainedCheckType = checkType.bind(null, false);
153 chainedCheckType.isRequired = checkType.bind(null, true);
154
155 return chainedCheckType;
156}
157
158function createPrimitiveTypeChecker(expectedType) {
159 function validate(props, propName, componentName, location, propFullName, secret) {
160 var propValue = props[propName];
161 var propType = getPropType(propValue);
162 if (propType !== expectedType) {
163 var locationName = ReactPropTypeLocationNames[location];
164 // `propValue` being instance of, say, date/regexp, pass the 'object'
165 // check, but we can offer a more precise error message here rather than
166 // 'of type `object`'.
167 var preciseType = getPreciseType(propValue);
168
169 return new PropTypeError('Invalid ' + locationName + ' `' + propFullName + '` of type ' + ('`' + preciseType + '` supplied to `' + componentName + '`, expected ') + ('`' + expectedType + '`.'));
170 }
171 return null;
172 }
173 return createChainableTypeChecker(validate);
174}
175
176function createAnyTypeChecker() {
177 return createChainableTypeChecker(emptyFunction.thatReturns(null));
178}
179
180function createArrayOfTypeChecker(typeChecker) {
181 function validate(props, propName, componentName, location, propFullName) {
182 if (typeof typeChecker !== 'function') {
183 return new PropTypeError('Property `' + propFullName + '` of component `' + componentName + '` has invalid PropType notation inside arrayOf.');
184 }
185 var propValue = props[propName];
186 if (!Array.isArray(propValue)) {
187 var locationName = ReactPropTypeLocationNames[location];
188 var propType = getPropType(propValue);
189 return new PropTypeError('Invalid ' + locationName + ' `' + propFullName + '` of type ' + ('`' + propType + '` supplied to `' + componentName + '`, expected an array.'));
190 }
191 for (var i = 0; i < propValue.length; i++) {
192 var error = typeChecker(propValue, i, componentName, location, propFullName + '[' + i + ']', ReactPropTypesSecret);
193 if (error instanceof Error) {
194 return error;
195 }
196 }
197 return null;
198 }
199 return createChainableTypeChecker(validate);
200}
201
202function createElementTypeChecker() {
203 function validate(props, propName, componentName, location, propFullName) {
204 var propValue = props[propName];
205 if (!ReactElement.isValidElement(propValue)) {
206 var locationName = ReactPropTypeLocationNames[location];
207 var propType = getPropType(propValue);
208 return new PropTypeError('Invalid ' + locationName + ' `' + propFullName + '` of type ' + ('`' + propType + '` supplied to `' + componentName + '`, expected a single ReactElement.'));
209 }
210 return null;
211 }
212 return createChainableTypeChecker(validate);
213}
214
215function createInstanceTypeChecker(expectedClass) {
216 function validate(props, propName, componentName, location, propFullName) {
217 if (!(props[propName] instanceof expectedClass)) {
218 var locationName = ReactPropTypeLocationNames[location];
219 var expectedClassName = expectedClass.name || ANONYMOUS;
220 var actualClassName = getClassName(props[propName]);
221 return new PropTypeError('Invalid ' + locationName + ' `' + propFullName + '` of type ' + ('`' + actualClassName + '` supplied to `' + componentName + '`, expected ') + ('instance of `' + expectedClassName + '`.'));
222 }
223 return null;
224 }
225 return createChainableTypeChecker(validate);
226}
227
228function createEnumTypeChecker(expectedValues) {
229 if (!Array.isArray(expectedValues)) {
230 process.env.NODE_ENV !== 'production' ? warning(false, 'Invalid argument supplied to oneOf, expected an instance of array.') : void 0;
231 return emptyFunction.thatReturnsNull;
232 }
233
234 function validate(props, propName, componentName, location, propFullName) {
235 var propValue = props[propName];
236 for (var i = 0; i < expectedValues.length; i++) {
237 if (is(propValue, expectedValues[i])) {
238 return null;
239 }
240 }
241
242 var locationName = ReactPropTypeLocationNames[location];
243 var valuesString = JSON.stringify(expectedValues);
244 return new PropTypeError('Invalid ' + locationName + ' `' + propFullName + '` of value `' + propValue + '` ' + ('supplied to `' + componentName + '`, expected one of ' + valuesString + '.'));
245 }
246 return createChainableTypeChecker(validate);
247}
248
249function createObjectOfTypeChecker(typeChecker) {
250 function validate(props, propName, componentName, location, propFullName) {
251 if (typeof typeChecker !== 'function') {
252 return new PropTypeError('Property `' + propFullName + '` of component `' + componentName + '` has invalid PropType notation inside objectOf.');
253 }
254 var propValue = props[propName];
255 var propType = getPropType(propValue);
256 if (propType !== 'object') {
257 var locationName = ReactPropTypeLocationNames[location];
258 return new PropTypeError('Invalid ' + locationName + ' `' + propFullName + '` of type ' + ('`' + propType + '` supplied to `' + componentName + '`, expected an object.'));
259 }
260 for (var key in propValue) {
261 if (propValue.hasOwnProperty(key)) {
262 var error = typeChecker(propValue, key, componentName, location, propFullName + '.' + key, ReactPropTypesSecret);
263 if (error instanceof Error) {
264 return error;
265 }
266 }
267 }
268 return null;
269 }
270 return createChainableTypeChecker(validate);
271}
272
273function createUnionTypeChecker(arrayOfTypeCheckers) {
274 if (!Array.isArray(arrayOfTypeCheckers)) {
275 process.env.NODE_ENV !== 'production' ? warning(false, 'Invalid argument supplied to oneOfType, expected an instance of array.') : void 0;
276 return emptyFunction.thatReturnsNull;
277 }
278
279 function validate(props, propName, componentName, location, propFullName) {
280 for (var i = 0; i < arrayOfTypeCheckers.length; i++) {
281 var checker = arrayOfTypeCheckers[i];
282 if (checker(props, propName, componentName, location, propFullName, ReactPropTypesSecret) == null) {
283 return null;
284 }
285 }
286
287 var locationName = ReactPropTypeLocationNames[location];
288 return new PropTypeError('Invalid ' + locationName + ' `' + propFullName + '` supplied to ' + ('`' + componentName + '`.'));
289 }
290 return createChainableTypeChecker(validate);
291}
292
293function createNodeChecker() {
294 function validate(props, propName, componentName, location, propFullName) {
295 if (!isNode(props[propName])) {
296 var locationName = ReactPropTypeLocationNames[location];
297 return new PropTypeError('Invalid ' + locationName + ' `' + propFullName + '` supplied to ' + ('`' + componentName + '`, expected a ReactNode.'));
298 }
299 return null;
300 }
301 return createChainableTypeChecker(validate);
302}
303
304function createShapeTypeChecker(shapeTypes) {
305 function validate(props, propName, componentName, location, propFullName) {
306 var propValue = props[propName];
307 var propType = getPropType(propValue);
308 if (propType !== 'object') {
309 var locationName = ReactPropTypeLocationNames[location];
310 return new PropTypeError('Invalid ' + locationName + ' `' + propFullName + '` of type `' + propType + '` ' + ('supplied to `' + componentName + '`, expected `object`.'));
311 }
312 for (var key in shapeTypes) {
313 var checker = shapeTypes[key];
314 if (!checker) {
315 continue;
316 }
317 var error = checker(propValue, key, componentName, location, propFullName + '.' + key, ReactPropTypesSecret);
318 if (error) {
319 return error;
320 }
321 }
322 return null;
323 }
324 return createChainableTypeChecker(validate);
325}
326
327function isNode(propValue) {
328 switch (typeof propValue) {
329 case 'number':
330 case 'string':
331 case 'undefined':
332 return true;
333 case 'boolean':
334 return !propValue;
335 case 'object':
336 if (Array.isArray(propValue)) {
337 return propValue.every(isNode);
338 }
339 if (propValue === null || ReactElement.isValidElement(propValue)) {
340 return true;
341 }
342
343 var iteratorFn = getIteratorFn(propValue);
344 if (iteratorFn) {
345 var iterator = iteratorFn.call(propValue);
346 var step;
347 if (iteratorFn !== propValue.entries) {
348 while (!(step = iterator.next()).done) {
349 if (!isNode(step.value)) {
350 return false;
351 }
352 }
353 } else {
354 // Iterator will provide entry [k,v] tuples rather than values.
355 while (!(step = iterator.next()).done) {
356 var entry = step.value;
357 if (entry) {
358 if (!isNode(entry[1])) {
359 return false;
360 }
361 }
362 }
363 }
364 } else {
365 return false;
366 }
367
368 return true;
369 default:
370 return false;
371 }
372}
373
374function isSymbol(propType, propValue) {
375 // Native Symbol.
376 if (propType === 'symbol') {
377 return true;
378 }
379
380 // 19.4.3.5 Symbol.prototype[@@toStringTag] === 'Symbol'
381 if (propValue['@@toStringTag'] === 'Symbol') {
382 return true;
383 }
384
385 // Fallback for non-spec compliant Symbols which are polyfilled.
386 if (typeof Symbol === 'function' && propValue instanceof Symbol) {
387 return true;
388 }
389
390 return false;
391}
392
393// Equivalent of `typeof` but with special handling for array and regexp.
394function getPropType(propValue) {
395 var propType = typeof propValue;
396 if (Array.isArray(propValue)) {
397 return 'array';
398 }
399 if (propValue instanceof RegExp) {
400 // Old webkits (at least until Android 4.0) return 'function' rather than
401 // 'object' for typeof a RegExp. We'll normalize this here so that /bla/
402 // passes PropTypes.object.
403 return 'object';
404 }
405 if (isSymbol(propType, propValue)) {
406 return 'symbol';
407 }
408 return propType;
409}
410
411// This handles more types than `getPropType`. Only used for error messages.
412// See `createPrimitiveTypeChecker`.
413function getPreciseType(propValue) {
414 var propType = getPropType(propValue);
415 if (propType === 'object') {
416 if (propValue instanceof Date) {
417 return 'date';
418 } else if (propValue instanceof RegExp) {
419 return 'regexp';
420 }
421 }
422 return propType;
423}
424
425// Returns class name of the object, if any.
426function getClassName(propValue) {
427 if (!propValue.constructor || !propValue.constructor.name) {
428 return ANONYMOUS;
429 }
430 return propValue.constructor.name;
431}
432
433module.exports = ReactPropTypes;
\No newline at end of file