diff --git a/packages/react-devtools-shared/src/backend/utils.js b/packages/react-devtools-shared/src/backend/utils.js index 09f821f4e1877..6945efc3f6e9c 100644 --- a/packages/react-devtools-shared/src/backend/utils.js +++ b/packages/react-devtools-shared/src/backend/utils.js @@ -159,7 +159,6 @@ export function serializeToString(data: any): string { // based on https://github.com/tmpfs/format-util/blob/0e62d430efb0a1c51448709abd3e2406c14d8401/format.js#L1 // based on https://developer.mozilla.org/en-US/docs/Web/API/console#Using_string_substitutions // Implements s, d, i and f placeholders -// NOTE: KEEP IN SYNC with src/hook.js export function format( maybeMessage: any, ...inputArgs: $ReadOnlyArray diff --git a/packages/react-devtools-shared/src/hook.js b/packages/react-devtools-shared/src/hook.js index 5844074863bf5..2eac28c1ca940 100644 --- a/packages/react-devtools-shared/src/hook.js +++ b/packages/react-devtools-shared/src/hook.js @@ -171,55 +171,67 @@ export function installHook(target: any): DevToolsHook | null { } catch (err) {} } - // NOTE: KEEP IN SYNC with src/backend/utils.js function format( - maybeMessage: any, - ...inputArgs: $ReadOnlyArray - ): string { - const args = inputArgs.slice(); - - // Symbols cannot be concatenated with Strings. - let formatted = String(maybeMessage); - - // If the first argument is a string, check for substitutions. - if (typeof maybeMessage === 'string') { - if (args.length) { - const REGEXP = /(%?)(%([jds]))/g; - - formatted = formatted.replace(REGEXP, (match, escaped, ptn, flag) => { - let arg = args.shift(); - switch (flag) { - case 's': - arg += ''; - break; - case 'd': - case 'i': - arg = parseInt(arg, 10).toString(); - break; - case 'f': - arg = parseFloat(arg).toString(); - break; - } - if (!escaped) { - return arg; - } - args.unshift(arg); - return match; - }); + inputArgs: $ReadOnlyArray, + color: string + ): $ReadOnlyArray { + // The console APIs have two forms: + // 1) Placeholder form, where the first N+1 arguments are: + // - a string containing N placeholders + // - N placeholder values + // followed by any number of arbitrary values. + // 2) Non-placeholder form, where all arguments are arbitrary values. + + // The goal is to colorize the placeholder string (if it exists) along with + // any additional arguments that are strings. Examples: + // ['A', 1, 'B'] => ['%c%s %o %s', 'color: X', 'A', 1, 'B'] + // ['A %d', 1, 'B'] => ['%cA %d %c%s', 'color: X', 1, 'color: X', 'B'] + // ['A%cB', 'color: blue', 'C'] => ['%cA%cB %c%s', 'color: X', 'color: blue', 'color: X', 'C'] + + // Determine whether the first arg is a string containing N placeholders. + const REGEXP = /(^|[^%])%[cfijdsoO]/g; + const numPlaceholders = typeof inputArgs[0] === 'string' + ? (inputArgs[0].match(REGEXP) || []).length + : 0; + + if (numPlaceholders === 0) { + let placeholderString = '%c'; + + for (let i = 0; i < inputArgs.length; i++) { + if (typeof inputArgs[i] === 'string') { + placeholderString += '%s '; + } else { + placeholderString += '%o '; + } } - } - // Arguments that remain after formatting. - if (args.length) { - for (let i = 0; i < args.length; i++) { - formatted += ' ' + String(args[i]); + return [placeholderString.trimEnd(), `color: ${color}`, ...inputArgs]; + } else { + const placeholderArgs = inputArgs.slice(1, numPlaceholders + 1); + const nonPlaceholderArgs = inputArgs.slice(numPlaceholders + 1); + let placeholderString = '%c' + inputArgs[0]; + + if (nonPlaceholderArgs.length > 0) { + // Apply the base color again in case it was overriden by the user string. + placeholderString += ' %c'; + for (let i = 0; i < nonPlaceholderArgs.length; i++) { + if (typeof nonPlaceholderArgs[i] === 'string') { + placeholderString += '%s '; + } else { + placeholderString += '%o '; + } + } + return [ + placeholderString.trimEnd(), + `color: ${color}`, + ...placeholderArgs, + `color: ${color}`, + ...nonPlaceholderArgs, + ]; + } else { + return [placeholderString, `color: ${color}`, ...placeholderArgs]; } } - - // Update escaped %% values. - formatted = formatted.replace(/%{2,2}/g, '%'); - - return String(formatted); } let unpatchFn = null; @@ -291,7 +303,7 @@ export function installHook(target: any): DevToolsHook | null { } if (color) { - originalMethod(`%c${format(...args)}`, `color: ${color}`); + originalMethod(...format(args, color)); } else { throw Error('Console color is not defined'); }