main
js 321 lines 9.47 KB
Raw
1 /**
2 * Copyright (c) Meta Platforms, Inc. and affiliates.
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 * @flow
8 */
9
10 import {
11 REACT_ELEMENT_TYPE,
12 REACT_FORWARD_REF_TYPE,
13 REACT_LAZY_TYPE,
14 REACT_MEMO_TYPE,
15 REACT_SUSPENSE_TYPE,
16 REACT_SUSPENSE_LIST_TYPE,
17 REACT_VIEW_TRANSITION_TYPE,
18 } from 'shared/ReactSymbols';
19
20 import type {LazyComponent} from 'react/src/ReactLazy';
21
22 import isArray from 'shared/isArray';
23 import getPrototypeOf from 'shared/getPrototypeOf';
24
25 import {enableViewTransition} from 'shared/ReactFeatureFlags';
26
27 // Used for DEV messages to keep track of which parent rendered some props,
28 // in case they error.
29 export const jsxPropsParents: WeakMap<any, any> = new WeakMap();
30 export const jsxChildrenParents: WeakMap<any, any> = new WeakMap();
31
32 function isObjectPrototype(object: any): boolean {
33 if (!object) {
34 return false;
35 }
36 const ObjectPrototype = Object.prototype;
37 if (object === ObjectPrototype) {
38 return true;
39 }
40 // It might be an object from a different Realm which is
41 // still just a plain simple object.
42 if (getPrototypeOf(object)) {
43 return false;
44 }
45 const names = Object.getOwnPropertyNames(object);
46 for (let i = 0; i < names.length; i++) {
47 if (!(names[i] in ObjectPrototype)) {
48 return false;
49 }
50 }
51 return true;
52 }
53
54 export function isGetter(object: any, name: string): boolean {
55 const ObjectPrototype = Object.prototype;
56 if (object === ObjectPrototype || object === null) {
57 return false;
58 }
59 const descriptor = Object.getOwnPropertyDescriptor(object, name);
60 if (descriptor === undefined) {
61 return isGetter(getPrototypeOf(object), name);
62 }
63 return typeof descriptor.get === 'function';
64 }
65
66 export function isSimpleObject(object: any): boolean {
67 if (!isObjectPrototype(getPrototypeOf(object))) {
68 return false;
69 }
70 const names = Object.getOwnPropertyNames(object);
71 for (let i = 0; i < names.length; i++) {
72 const descriptor = Object.getOwnPropertyDescriptor(object, names[i]);
73 if (!descriptor) {
74 return false;
75 }
76 if (!descriptor.enumerable) {
77 if (
78 (names[i] === 'key' || names[i] === 'ref') &&
79 typeof descriptor.get === 'function'
80 ) {
81 // React adds key and ref getters to props objects to issue warnings.
82 // Those getters will not be transferred to the client, but that's ok,
83 // so we'll special case them.
84 continue;
85 }
86 return false;
87 }
88 }
89 return true;
90 }
91
92 export function objectName(object: mixed): string {
93 // $FlowFixMe[method-unbinding]
94 const name = Object.prototype.toString.call(object);
95 // Extract 'Object' from '[object Object]':
96 return name.slice(8, name.length - 1);
97 }
98
99 function describeKeyForErrorMessage(key: string): string {
100 const encodedKey = JSON.stringify(key);
101 return '"' + key + '"' === encodedKey ? key : encodedKey;
102 }
103
104 export function describeValueForErrorMessage(value: mixed): string {
105 switch (typeof value) {
106 case 'string': {
107 return JSON.stringify(
108 value.length <= 10 ? value : value.slice(0, 10) + '...',
109 );
110 }
111 case 'object': {
112 if (isArray(value)) {
113 return '[...]';
114 }
115 if (value !== null && value.$$typeof === CLIENT_REFERENCE_TAG) {
116 return describeClientReference(value);
117 }
118 const name = objectName(value);
119 if (name === 'Object') {
120 return '{...}';
121 }
122 return name;
123 }
124 case 'function': {
125 if ((value as any).$$typeof === CLIENT_REFERENCE_TAG) {
126 return describeClientReference(value);
127 }
128 const name = (value as any).displayName || value.name;
129 return name ? 'function ' + name : 'function';
130 }
131 default:
132 // eslint-disable-next-line react-internal/safe-string-coercion
133 return String(value);
134 }
135 }
136
137 function describeElementType(type: any): string {
138 if (typeof type === 'string') {
139 return type;
140 }
141 switch (type) {
142 case REACT_SUSPENSE_TYPE:
143 return 'Suspense';
144 case REACT_SUSPENSE_LIST_TYPE:
145 return 'SuspenseList';
146 case REACT_VIEW_TRANSITION_TYPE:
147 if (enableViewTransition) {
148 return 'ViewTransition';
149 }
150 }
151 if (typeof type === 'object') {
152 switch (type.$$typeof) {
153 case REACT_FORWARD_REF_TYPE:
154 return describeElementType(type.render);
155 case REACT_MEMO_TYPE:
156 return describeElementType(type.type);
157 case REACT_LAZY_TYPE: {
158 const lazyComponent: LazyComponent<any, any> = type as any;
159 const payload = lazyComponent._payload;
160 const init = lazyComponent._init;
161 try {
162 // Lazy may contain any component type so we recursively resolve it.
163 return describeElementType(init(payload));
164 } catch (x) {}
165 }
166 }
167 }
168 return '';
169 }
170
171 const CLIENT_REFERENCE_TAG = Symbol.for('react.client.reference');
172
173 function describeClientReference(ref: any) {
174 return 'client';
175 }
176
177 export function describeObjectForErrorMessage(
178 objectOrArray: {+[key: string | number]: mixed, ...} | $ReadOnlyArray<mixed>,
179 expandedName?: string,
180 ): string {
181 const objKind = objectName(objectOrArray);
182 if (objKind !== 'Object' && objKind !== 'Array') {
183 return objKind;
184 }
185 let str = '';
186 let start = -1;
187 let length = 0;
188 if (isArray(objectOrArray)) {
189 if (__DEV__ && jsxChildrenParents.has(objectOrArray)) {
190 // Print JSX Children
191 const type = jsxChildrenParents.get(objectOrArray);
192 str = '<' + describeElementType(type) + '>';
193 const array: $ReadOnlyArray<mixed> = objectOrArray;
194 for (let i = 0; i < array.length; i++) {
195 const value = array[i];
196 let substr;
197 if (typeof value === 'string') {
198 substr = value;
199 } else if (typeof value === 'object' && value !== null) {
200 substr = '{' + describeObjectForErrorMessage(value) + '}';
201 } else {
202 substr = '{' + describeValueForErrorMessage(value) + '}';
203 }
204 if ('' + i === expandedName) {
205 start = str.length;
206 length = substr.length;
207 str += substr;
208 } else if (substr.length < 15 && str.length + substr.length < 40) {
209 str += substr;
210 } else {
211 str += '{...}';
212 }
213 }
214 str += '</' + describeElementType(type) + '>';
215 } else {
216 // Print Array
217 str = '[';
218 const array: $ReadOnlyArray<mixed> = objectOrArray;
219 for (let i = 0; i < array.length; i++) {
220 if (i > 0) {
221 str += ', ';
222 }
223 const value = array[i];
224 let substr;
225 if (typeof value === 'object' && value !== null) {
226 substr = describeObjectForErrorMessage(value);
227 } else {
228 substr = describeValueForErrorMessage(value);
229 }
230 if ('' + i === expandedName) {
231 start = str.length;
232 length = substr.length;
233 str += substr;
234 } else if (substr.length < 10 && str.length + substr.length < 40) {
235 str += substr;
236 } else {
237 str += '...';
238 }
239 }
240 str += ']';
241 }
242 } else {
243 if (objectOrArray.$$typeof === REACT_ELEMENT_TYPE) {
244 str = '<' + describeElementType(objectOrArray.type) + '/>';
245 } else if (objectOrArray.$$typeof === CLIENT_REFERENCE_TAG) {
246 return describeClientReference(objectOrArray);
247 } else if (__DEV__ && jsxPropsParents.has(objectOrArray)) {
248 // Print JSX
249 const type = jsxPropsParents.get(objectOrArray);
250 str = '<' + (describeElementType(type) || '...');
251 const object: {+[key: string | number]: mixed, ...} = objectOrArray;
252 const names = Object.keys(object);
253 for (let i = 0; i < names.length; i++) {
254 str += ' ';
255 const name = names[i];
256 str += describeKeyForErrorMessage(name) + '=';
257 const value = object[name];
258 let substr;
259 if (
260 name === expandedName &&
261 typeof value === 'object' &&
262 value !== null
263 ) {
264 substr = describeObjectForErrorMessage(value);
265 } else {
266 substr = describeValueForErrorMessage(value);
267 }
268 if (typeof value !== 'string') {
269 substr = '{' + substr + '}';
270 }
271 if (name === expandedName) {
272 start = str.length;
273 length = substr.length;
274 str += substr;
275 } else if (substr.length < 10 && str.length + substr.length < 40) {
276 str += substr;
277 } else {
278 str += '...';
279 }
280 }
281 str += '>';
282 } else {
283 // Print Object
284 str = '{';
285 const object: {+[key: string | number]: mixed, ...} = objectOrArray;
286 const names = Object.keys(object);
287 for (let i = 0; i < names.length; i++) {
288 if (i > 0) {
289 str += ', ';
290 }
291 const name = names[i];
292 str += describeKeyForErrorMessage(name) + ': ';
293 const value = object[name];
294 let substr;
295 if (typeof value === 'object' && value !== null) {
296 substr = describeObjectForErrorMessage(value);
297 } else {
298 substr = describeValueForErrorMessage(value);
299 }
300 if (name === expandedName) {
301 start = str.length;
302 length = substr.length;
303 str += substr;
304 } else if (substr.length < 10 && str.length + substr.length < 40) {
305 str += substr;
306 } else {
307 str += '...';
308 }
309 }
310 str += '}';
311 }
312 }
313 if (expandedName === undefined) {
314 return str;
315 }
316 if (start > -1 && length > 0) {
317 const highlight = ' '.repeat(start) + '^'.repeat(length);
318 return '\n ' + str + '\n ' + highlight;
319 }
320 return '\n ' + str;
321 }