Skip to content

Commit 4ba5e78

Browse files
committed
chore[react-devtools]: extract some utils into separate modules to unify implementations
1 parent 374fd73 commit 4ba5e78

File tree

4 files changed

+150
-216
lines changed

4 files changed

+150
-216
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
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+
// Do not add / import anything to this file.
11+
// This function could be used from multiple places, including hook.
12+
13+
// Skips CSS and object arguments, inlines other in the first argument as a template string
14+
export default function formatConsoleArguments(
15+
maybeMessage: any,
16+
...inputArgs: $ReadOnlyArray<any>
17+
): $ReadOnlyArray<any> {
18+
if (inputArgs.length === 0 || typeof maybeMessage !== 'string') {
19+
return [maybeMessage, ...inputArgs];
20+
}
21+
22+
const args = inputArgs.slice();
23+
24+
let template = '';
25+
let argumentsPointer = 0;
26+
for (let i = 0; i < maybeMessage.length; ++i) {
27+
const currentChar = maybeMessage[i];
28+
if (currentChar !== '%') {
29+
template += currentChar;
30+
continue;
31+
}
32+
33+
const nextChar = maybeMessage[i + 1];
34+
++i;
35+
36+
// Only keep CSS and objects, inline other arguments
37+
switch (nextChar) {
38+
case 'c':
39+
case 'O':
40+
case 'o': {
41+
++argumentsPointer;
42+
template += `%${nextChar}`;
43+
44+
break;
45+
}
46+
case 'd':
47+
case 'i': {
48+
const [arg] = args.splice(argumentsPointer, 1);
49+
template += parseInt(arg, 10).toString();
50+
51+
break;
52+
}
53+
case 'f': {
54+
const [arg] = args.splice(argumentsPointer, 1);
55+
template += parseFloat(arg).toString();
56+
57+
break;
58+
}
59+
case 's': {
60+
const [arg] = args.splice(argumentsPointer, 1);
61+
template += arg.toString();
62+
63+
break;
64+
}
65+
66+
default:
67+
template += `%${nextChar}`;
68+
}
69+
}
70+
71+
return [template, ...args];
72+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
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+
// Do not add / import anything to this file.
11+
// This function could be used from multiple places, including hook.
12+
13+
// Formats an array of args with a style for console methods, using
14+
// the following algorithm:
15+
// 1. The first param is a string that contains %c
16+
// - Bail out and return the args without modifying the styles.
17+
// We don't want to affect styles that the developer deliberately set.
18+
// 2. The first param is a string that doesn't contain %c but contains
19+
// string formatting
20+
// - [`%c${args[0]}`, style, ...args.slice(1)]
21+
// - Note: we assume that the string formatting that the developer uses
22+
// is correct.
23+
// 3. The first param is a string that doesn't contain string formatting
24+
// OR is not a string
25+
// - Create a formatting string where:
26+
// boolean, string, symbol -> %s
27+
// number -> %f OR %i depending on if it's an int or float
28+
// default -> %o
29+
export default function formatWithStyles(
30+
inputArgs: $ReadOnlyArray<any>,
31+
style?: string,
32+
): $ReadOnlyArray<any> {
33+
if (
34+
inputArgs === undefined ||
35+
inputArgs === null ||
36+
inputArgs.length === 0 ||
37+
// Matches any of %c but not %%c
38+
(typeof inputArgs[0] === 'string' && inputArgs[0].match(/([^%]|^)(%c)/g)) ||
39+
style === undefined
40+
) {
41+
return inputArgs;
42+
}
43+
44+
// Matches any of %(o|O|d|i|s|f), but not %%(o|O|d|i|s|f)
45+
const REGEXP = /([^%]|^)((%%)*)(%([oOdisf]))/g;
46+
if (typeof inputArgs[0] === 'string' && inputArgs[0].match(REGEXP)) {
47+
return [`%c${inputArgs[0]}`, style, ...inputArgs.slice(1)];
48+
} else {
49+
const firstArg = inputArgs.reduce((formatStr, elem, i) => {
50+
if (i > 0) {
51+
formatStr += ' ';
52+
}
53+
switch (typeof elem) {
54+
case 'string':
55+
case 'boolean':
56+
case 'symbol':
57+
return (formatStr += '%s');
58+
case 'number':
59+
const formatting = Number.isInteger(elem) ? '%i' : '%f';
60+
return (formatStr += formatting);
61+
default:
62+
return (formatStr += '%o');
63+
}
64+
}, '%c');
65+
return [firstArg, style, ...inputArgs];
66+
}
67+
}

packages/react-devtools-shared/src/backend/utils.js packages/react-devtools-shared/src/backend/utils/index.js

+4-120
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,15 @@
99
*/
1010

1111
import {compareVersions} from 'compare-versions';
12-
import {dehydrate} from '../hydration';
12+
import {dehydrate} from 'react-devtools-shared/src/hydration';
1313
import isArray from 'shared/isArray';
1414

1515
import type {Source} from 'react-devtools-shared/src/shared/types';
1616
import type {DehydratedData} from 'react-devtools-shared/src/frontend/types';
1717

18+
export {default as formatWithStyles} from './formatWithStyles';
19+
export {default as formatConsoleArguments} from './formatConsoleArguments';
20+
1821
// TODO: update this to the first React version that has a corresponding DevTools backend
1922
const FIRST_DEVTOOLS_BACKEND_LOCKSTEP_VER = '999.9.9';
2023
export function hasAssignedBackend(version?: string): boolean {
@@ -164,125 +167,6 @@ export function serializeToString(data: any): string {
164167
);
165168
}
166169

167-
// NOTE: KEEP IN SYNC with src/hook.js
168-
// Formats an array of args with a style for console methods, using
169-
// the following algorithm:
170-
// 1. The first param is a string that contains %c
171-
// - Bail out and return the args without modifying the styles.
172-
// We don't want to affect styles that the developer deliberately set.
173-
// 2. The first param is a string that doesn't contain %c but contains
174-
// string formatting
175-
// - [`%c${args[0]}`, style, ...args.slice(1)]
176-
// - Note: we assume that the string formatting that the developer uses
177-
// is correct.
178-
// 3. The first param is a string that doesn't contain string formatting
179-
// OR is not a string
180-
// - Create a formatting string where:
181-
// boolean, string, symbol -> %s
182-
// number -> %f OR %i depending on if it's an int or float
183-
// default -> %o
184-
export function formatWithStyles(
185-
inputArgs: $ReadOnlyArray<any>,
186-
style?: string,
187-
): $ReadOnlyArray<any> {
188-
if (
189-
inputArgs === undefined ||
190-
inputArgs === null ||
191-
inputArgs.length === 0 ||
192-
// Matches any of %c but not %%c
193-
(typeof inputArgs[0] === 'string' && inputArgs[0].match(/([^%]|^)(%c)/g)) ||
194-
style === undefined
195-
) {
196-
return inputArgs;
197-
}
198-
199-
// Matches any of %(o|O|d|i|s|f), but not %%(o|O|d|i|s|f)
200-
const REGEXP = /([^%]|^)((%%)*)(%([oOdisf]))/g;
201-
if (typeof inputArgs[0] === 'string' && inputArgs[0].match(REGEXP)) {
202-
return [`%c${inputArgs[0]}`, style, ...inputArgs.slice(1)];
203-
} else {
204-
const firstArg = inputArgs.reduce((formatStr, elem, i) => {
205-
if (i > 0) {
206-
formatStr += ' ';
207-
}
208-
switch (typeof elem) {
209-
case 'string':
210-
case 'boolean':
211-
case 'symbol':
212-
return (formatStr += '%s');
213-
case 'number':
214-
const formatting = Number.isInteger(elem) ? '%i' : '%f';
215-
return (formatStr += formatting);
216-
default:
217-
return (formatStr += '%o');
218-
}
219-
}, '%c');
220-
return [firstArg, style, ...inputArgs];
221-
}
222-
}
223-
224-
// NOTE: KEEP IN SYNC with src/hook.js
225-
// Skips CSS and object arguments, inlines other in the first argument as a template string
226-
export function formatConsoleArguments(
227-
maybeMessage: any,
228-
...inputArgs: $ReadOnlyArray<any>
229-
): $ReadOnlyArray<any> {
230-
if (inputArgs.length === 0 || typeof maybeMessage !== 'string') {
231-
return [maybeMessage, ...inputArgs];
232-
}
233-
234-
const args = inputArgs.slice();
235-
236-
let template = '';
237-
let argumentsPointer = 0;
238-
for (let i = 0; i < maybeMessage.length; ++i) {
239-
const currentChar = maybeMessage[i];
240-
if (currentChar !== '%') {
241-
template += currentChar;
242-
continue;
243-
}
244-
245-
const nextChar = maybeMessage[i + 1];
246-
++i;
247-
248-
// Only keep CSS and objects, inline other arguments
249-
switch (nextChar) {
250-
case 'c':
251-
case 'O':
252-
case 'o': {
253-
++argumentsPointer;
254-
template += `%${nextChar}`;
255-
256-
break;
257-
}
258-
case 'd':
259-
case 'i': {
260-
const [arg] = args.splice(argumentsPointer, 1);
261-
template += parseInt(arg, 10).toString();
262-
263-
break;
264-
}
265-
case 'f': {
266-
const [arg] = args.splice(argumentsPointer, 1);
267-
template += parseFloat(arg).toString();
268-
269-
break;
270-
}
271-
case 's': {
272-
const [arg] = args.splice(argumentsPointer, 1);
273-
template += arg.toString();
274-
275-
break;
276-
}
277-
278-
default:
279-
template += `%${nextChar}`;
280-
}
281-
}
282-
283-
return [template, ...args];
284-
}
285-
286170
// based on https://github.com/tmpfs/format-util/blob/0e62d430efb0a1c51448709abd3e2406c14d8401/format.js#L1
287171
// based on https://developer.mozilla.org/en-US/docs/Web/API/console#Using_string_substitutions
288172
// Implements s, d, i and f placeholders

0 commit comments

Comments
 (0)