1
+/**
2
+ * Copyright 2014 Facebook, Inc.
3
+ *
4
+ * Licensed under the Apache License, Version 2.0 (the "License");
5
+ * you may not use this file except in compliance with the License.
6
+ * You may obtain a copy of the License at
7
+ *
8
+ * http://www.apache.org/licenses/LICENSE-2.0
9
+ *
10
+ * Unless required by applicable law or agreed to in writing, software
11
+ * distributed under the License is distributed on an "AS IS" BASIS,
12
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ * See the License for the specific language governing permissions and
14
+ * limitations under the License.
15
+ *
16
+ * @providesModule ReactDescriptorValidator
17
+ */
18
+
19
+/**
20
+ * ReactDescriptorValidator provides a wrapper around a descriptor factory
21
+ * which validates the props passed to the descriptor. This is intended to be
22
+ * used only in DEV and could be replaced by a static type checker for languages
23
+ * that support it.
24
+ */
25
+
26
+"use strict";
27
+
28
+var ReactDescriptor = require('ReactDescriptor');
29
+var ReactPropTypeLocations = require('ReactPropTypeLocations');
30
+var ReactCurrentOwner = require('ReactCurrentOwner');
31
+
32
+var monitorCodeUse = require('monitorCodeUse');
33
+
34
+/**
35
+ * Warn if there's no key explicitly set on dynamic arrays of children or
36
+ * object keys are not valid. This allows us to keep track of children between
37
+ * updates.
38
+ */
39
+var ownerHasKeyUseWarning = {
40
+ 'react_key_warning': {},
41
+ 'react_numeric_key_warning': {}
42
+};
43
+var ownerHasMonitoredObjectMap = {};
44
+
45
+var loggedTypeFailures = {};
46
+
47
+var NUMERIC_PROPERTY_REGEX = /^\d+$/;
48
+
49
+/**
50
+ * Gets the current owner's displayName for use in warnings.
51
+ *
52
+ * @internal
53
+ * @return {?string} Display name or undefined
54
+ */
55
+function getCurrentOwnerDisplayName() {
56
+ var current = ReactCurrentOwner.current;
57
+ return current && current.constructor.displayName || undefined;
58
+}
59
+
60
+/**
61
+ * Warn if the component doesn't have an explicit key assigned to it.
62
+ * This component is in an array. The array could grow and shrink or be
63
+ * reordered. All children that haven't already been validated are required to
64
+ * have a "key" property assigned to it.
65
+ *
66
+ * @internal
67
+ * @param {ReactComponent} component Component that requires a key.
68
+ * @param {*} parentType component's parent's type.
69
+ */
70
+function validateExplicitKey(component, parentType) {
71
+ if (component._store.validated || component.props.key != null) {
72
+ return;
73
+ }
74
+ component._store.validated = true;
75
+
76
+ warnAndMonitorForKeyUse(
77
+ 'react_key_warning',
78
+ 'Each child in an array should have a unique "key" prop.',
79
+ component,
80
+ parentType
81
+ );
82
+}
83
+
84
+/**
85
+ * Warn if the key is being defined as an object property but has an incorrect
86
+ * value.
87
+ *
88
+ * @internal
89
+ * @param {string} name Property name of the key.
90
+ * @param {ReactComponent} component Component that requires a key.
91
+ * @param {*} parentType component's parent's type.
92
+ */
93
+function validatePropertyKey(name, component, parentType) {
94
+ if (!NUMERIC_PROPERTY_REGEX.test(name)) {
95
+ return;
96
+ }
97
+ warnAndMonitorForKeyUse(
98
+ 'react_numeric_key_warning',
99
+ 'Child objects should have non-numeric keys so ordering is preserved.',
100
+ component,
101
+ parentType
102
+ );
103
+}
104
+
105
+/**
106
+ * Shared warning and monitoring code for the key warnings.
107
+ *
108
+ * @internal
109
+ * @param {string} warningID The id used when logging.
110
+ * @param {string} message The base warning that gets output.
111
+ * @param {ReactComponent} component Component that requires a key.
112
+ * @param {*} parentType component's parent's type.
113
+ */
114
+function warnAndMonitorForKeyUse(warningID, message, component, parentType) {
115
+ var ownerName = getCurrentOwnerDisplayName();
116
+ var parentName = parentType.displayName;
117
+
118
+ var useName = ownerName || parentName;
119
+ var memoizer = ownerHasKeyUseWarning[warningID];
120
+ if (memoizer.hasOwnProperty(useName)) {
121
+ return;
122
+ }
123
+ memoizer[useName] = true;
124
+
125
+ message += ownerName ?
126
+ ` Check the render method of ${ownerName}.` :
127
+ ` Check the renderComponent call using <${parentName}>.`;
128
+
129
+ // Usually the current owner is the offender, but if it accepts children as a
130
+ // property, it may be the creator of the child that's responsible for
131
+ // assigning it a key.
132
+ var childOwnerName = null;
133
+ if (component._owner && component._owner !== ReactCurrentOwner.current) {
134
+ // Name of the component that originally created this child.
135
+ childOwnerName = component._owner.constructor.displayName;
136
+
137
+ message += ` It was passed a child from ${childOwnerName}.`;
138
+ }
139
+
140
+ message += ' See http://fb.me/react-warning-keys for more information.';
141
+ monitorCodeUse(warningID, {
142
+ component: useName,
143
+ componentOwner: childOwnerName
144
+ });
145
+ console.warn(message);
146
+}
147
+
148
+/**
149
+ * Log that we're using an object map. We're considering deprecating this
150
+ * feature and replace it with proper Map and ImmutableMap data structures.
151
+ *
152
+ * @internal
153
+ */
154
+function monitorUseOfObjectMap() {
155
+ var currentName = getCurrentOwnerDisplayName() || '';
156
+ if (ownerHasMonitoredObjectMap.hasOwnProperty(currentName)) {
157
+ return;
158
+ }
159
+ ownerHasMonitoredObjectMap[currentName] = true;
160
+ monitorCodeUse('react_object_map_children');
161
+}
162
+
163
+/**
164
+ * Ensure that every component either is passed in a static location, in an
165
+ * array with an explicit keys property defined, or in an object literal
166
+ * with valid key property.
167
+ *
168
+ * @internal
169
+ * @param {*} component Statically passed child of any type.
170
+ * @param {*} parentType component's parent's type.
171
+ * @return {boolean}
172
+ */
173
+function validateChildKeys(component, parentType) {
174
+ if (Array.isArray(component)) {
175
+ for (var i = 0; i < component.length; i++) {
176
+ var child = component[i];
177
+ if (ReactDescriptor.isValidDescriptor(child)) {
178
+ validateExplicitKey(child, parentType);
179
+ }
180
+ }
181
+ } else if (ReactDescriptor.isValidDescriptor(component)) {
182
+ // This component was passed in a valid location.
183
+ component._store.validated = true;
184
+ } else if (component && typeof component === 'object') {
185
+ monitorUseOfObjectMap();
186
+ for (var name in component) {
187
+ validatePropertyKey(name, component[name], parentType);
188
+ }
189
+ }
190
+}
191
+
192
+/**
193
+ * Assert that the props are valid
194
+ *
195
+ * @param {string} componentName Name of the component for error messages.
196
+ * @param {object} propTypes Map of prop name to a ReactPropType
197
+ * @param {object} props
198
+ * @param {string} location e.g. "prop", "context", "child context"
199
+ * @private
200
+ */
201
+function checkPropTypes(componentName, propTypes, props, location) {
202
+ for (var propName in propTypes) {
203
+ if (propTypes.hasOwnProperty(propName)) {
204
+ var error;
205
+ // Prop type validation may throw. In case they do, we don't want to
206
+ // fail the render phase where it didn't fail before. So we log it.
207
+ // After these have been cleaned up, we'll let them throw.
208
+ try {
209
+ error = propTypes[propName](props, propName, componentName, location);
210
+ } catch (ex) {
211
+ error = ex;
212
+ }
213
+ if (error instanceof Error && !(error.message in loggedTypeFailures)) {
214
+ // Only monitor this failure once because there tends to be a lot of the
215
+ // same error.
216
+ loggedTypeFailures[error.message] = true;
217
+ // This will soon use the warning module
218
+ monitorCodeUse(
219
+ 'react_failed_descriptor_type_check',
220
+ { message: error.message }
221
+ );
222
+ }
223
+ }
224
+ }
225
+}
226
+
227
+var ReactDescriptorValidator = {
228
+
229
+ /**
230
+ * Wraps a descriptor factory function in another function which validates
231
+ * the props and context of the descriptor and warns about any failed type
232
+ * checks.
233
+ *
234
+ * @param {function} factory The original descriptor factory
235
+ * @param {object?} propTypes A prop type definition set
236
+ * @param {object?} contextTypes A context type definition set
237
+ * @return {object} The component descriptor, which may be invalid.
238
+ * @private
239
+ */
240
+ createFactory: function(factory, propTypes, contextTypes) {
241
+ var validatedFactory = function(props, children) {
242
+ var descriptor = factory.apply(this, arguments);
243
+
244
+ for (var i = 1; i < arguments.length; i++) {
245
+ validateChildKeys(arguments[i], descriptor.type);
246
+ }
247
+
248
+ var name = descriptor.type.displayName;
249
+ if (propTypes) {
250
+ checkPropTypes(
251
+ name,
252
+ propTypes,
253
+ descriptor.props,
254
+ ReactPropTypeLocations.prop
255
+ );
256
+ }
257
+ if (contextTypes) {
258
+ checkPropTypes(
259
+ name,
260
+ contextTypes,
261
+ descriptor._context,
262
+ ReactPropTypeLocations.context
263
+ );
264
+ }
265
+ return descriptor;
266
+ };
267
+
268
+ validatedFactory.prototype = factory.prototype;
269
+ validatedFactory.type = factory.type;
270
+
271
+ // Copy static properties
272
+ for (var key in factory) {
273
+ if (factory.hasOwnProperty(key)) {
274
+ validatedFactory[key] = factory[key];
275
+ }
276
+ }
277
+
278
+ return validatedFactory;
279
+ }
280
+
281
+};
282
+
283
+module.exports = ReactDescriptorValidator;