factoryWithTypeCheckers.js 21 KB

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