Skip to content

Commit

Permalink
chore[react-devtools]: extract some utils into separate modules to un…
Browse files Browse the repository at this point in the history
…ify implementations
  • Loading branch information
hoxyq committed Sep 15, 2024
1 parent 374fd73 commit 4ba5e78
Show file tree
Hide file tree
Showing 4 changed files with 150 additions and 216 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow
*/

// Do not add / import anything to this file.
// This function could be used from multiple places, including hook.

// Skips CSS and object arguments, inlines other in the first argument as a template string
export default function formatConsoleArguments(
maybeMessage: any,
...inputArgs: $ReadOnlyArray<any>
): $ReadOnlyArray<any> {
if (inputArgs.length === 0 || typeof maybeMessage !== 'string') {
return [maybeMessage, ...inputArgs];
}

const args = inputArgs.slice();

let template = '';
let argumentsPointer = 0;
for (let i = 0; i < maybeMessage.length; ++i) {
const currentChar = maybeMessage[i];
if (currentChar !== '%') {
template += currentChar;
continue;
}

const nextChar = maybeMessage[i + 1];
++i;

// Only keep CSS and objects, inline other arguments
switch (nextChar) {
case 'c':
case 'O':
case 'o': {
++argumentsPointer;
template += `%${nextChar}`;

break;
}
case 'd':
case 'i': {
const [arg] = args.splice(argumentsPointer, 1);
template += parseInt(arg, 10).toString();

break;
}
case 'f': {
const [arg] = args.splice(argumentsPointer, 1);
template += parseFloat(arg).toString();

break;
}
case 's': {
const [arg] = args.splice(argumentsPointer, 1);
template += arg.toString();

break;
}

default:
template += `%${nextChar}`;
}
}

return [template, ...args];
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow
*/

// Do not add / import anything to this file.
// This function could be used from multiple places, including hook.

// Formats an array of args with a style for console methods, using
// the following algorithm:
// 1. The first param is a string that contains %c
// - Bail out and return the args without modifying the styles.
// We don't want to affect styles that the developer deliberately set.
// 2. The first param is a string that doesn't contain %c but contains
// string formatting
// - [`%c${args[0]}`, style, ...args.slice(1)]
// - Note: we assume that the string formatting that the developer uses
// is correct.
// 3. The first param is a string that doesn't contain string formatting
// OR is not a string
// - Create a formatting string where:
// boolean, string, symbol -> %s
// number -> %f OR %i depending on if it's an int or float
// default -> %o
export default function formatWithStyles(
inputArgs: $ReadOnlyArray<any>,
style?: string,
): $ReadOnlyArray<any> {
if (
inputArgs === undefined ||
inputArgs === null ||
inputArgs.length === 0 ||
// Matches any of %c but not %%c
(typeof inputArgs[0] === 'string' && inputArgs[0].match(/([^%]|^)(%c)/g)) ||
style === undefined
) {
return inputArgs;
}

// Matches any of %(o|O|d|i|s|f), but not %%(o|O|d|i|s|f)
const REGEXP = /([^%]|^)((%%)*)(%([oOdisf]))/g;
if (typeof inputArgs[0] === 'string' && inputArgs[0].match(REGEXP)) {
return [`%c${inputArgs[0]}`, style, ...inputArgs.slice(1)];
} else {
const firstArg = inputArgs.reduce((formatStr, elem, i) => {
if (i > 0) {
formatStr += ' ';
}
switch (typeof elem) {
case 'string':
case 'boolean':
case 'symbol':
return (formatStr += '%s');
case 'number':
const formatting = Number.isInteger(elem) ? '%i' : '%f';
return (formatStr += formatting);
default:
return (formatStr += '%o');
}
}, '%c');
return [firstArg, style, ...inputArgs];
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,15 @@
*/

import {compareVersions} from 'compare-versions';
import {dehydrate} from '../hydration';
import {dehydrate} from 'react-devtools-shared/src/hydration';
import isArray from 'shared/isArray';

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

export {default as formatWithStyles} from './formatWithStyles';
export {default as formatConsoleArguments} from './formatConsoleArguments';

// TODO: update this to the first React version that has a corresponding DevTools backend
const FIRST_DEVTOOLS_BACKEND_LOCKSTEP_VER = '999.9.9';
export function hasAssignedBackend(version?: string): boolean {
Expand Down Expand Up @@ -164,125 +167,6 @@ export function serializeToString(data: any): string {
);
}

// NOTE: KEEP IN SYNC with src/hook.js
// Formats an array of args with a style for console methods, using
// the following algorithm:
// 1. The first param is a string that contains %c
// - Bail out and return the args without modifying the styles.
// We don't want to affect styles that the developer deliberately set.
// 2. The first param is a string that doesn't contain %c but contains
// string formatting
// - [`%c${args[0]}`, style, ...args.slice(1)]
// - Note: we assume that the string formatting that the developer uses
// is correct.
// 3. The first param is a string that doesn't contain string formatting
// OR is not a string
// - Create a formatting string where:
// boolean, string, symbol -> %s
// number -> %f OR %i depending on if it's an int or float
// default -> %o
export function formatWithStyles(
inputArgs: $ReadOnlyArray<any>,
style?: string,
): $ReadOnlyArray<any> {
if (
inputArgs === undefined ||
inputArgs === null ||
inputArgs.length === 0 ||
// Matches any of %c but not %%c
(typeof inputArgs[0] === 'string' && inputArgs[0].match(/([^%]|^)(%c)/g)) ||
style === undefined
) {
return inputArgs;
}

// Matches any of %(o|O|d|i|s|f), but not %%(o|O|d|i|s|f)
const REGEXP = /([^%]|^)((%%)*)(%([oOdisf]))/g;
if (typeof inputArgs[0] === 'string' && inputArgs[0].match(REGEXP)) {
return [`%c${inputArgs[0]}`, style, ...inputArgs.slice(1)];
} else {
const firstArg = inputArgs.reduce((formatStr, elem, i) => {
if (i > 0) {
formatStr += ' ';
}
switch (typeof elem) {
case 'string':
case 'boolean':
case 'symbol':
return (formatStr += '%s');
case 'number':
const formatting = Number.isInteger(elem) ? '%i' : '%f';
return (formatStr += formatting);
default:
return (formatStr += '%o');
}
}, '%c');
return [firstArg, style, ...inputArgs];
}
}

// NOTE: KEEP IN SYNC with src/hook.js
// Skips CSS and object arguments, inlines other in the first argument as a template string
export function formatConsoleArguments(
maybeMessage: any,
...inputArgs: $ReadOnlyArray<any>
): $ReadOnlyArray<any> {
if (inputArgs.length === 0 || typeof maybeMessage !== 'string') {
return [maybeMessage, ...inputArgs];
}

const args = inputArgs.slice();

let template = '';
let argumentsPointer = 0;
for (let i = 0; i < maybeMessage.length; ++i) {
const currentChar = maybeMessage[i];
if (currentChar !== '%') {
template += currentChar;
continue;
}

const nextChar = maybeMessage[i + 1];
++i;

// Only keep CSS and objects, inline other arguments
switch (nextChar) {
case 'c':
case 'O':
case 'o': {
++argumentsPointer;
template += `%${nextChar}`;

break;
}
case 'd':
case 'i': {
const [arg] = args.splice(argumentsPointer, 1);
template += parseInt(arg, 10).toString();

break;
}
case 'f': {
const [arg] = args.splice(argumentsPointer, 1);
template += parseFloat(arg).toString();

break;
}
case 's': {
const [arg] = args.splice(argumentsPointer, 1);
template += arg.toString();

break;
}

default:
template += `%${nextChar}`;
}
}

return [template, ...args];
}

// 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
Expand Down
Loading

0 comments on commit 4ba5e78

Please sign in to comment.