Skip to content

Commit a44c786

Browse files
author
Brian Vaughn
committed
Fixed another Symbol concatenation issue with DevTools format() util
1 parent 7bef382 commit a44c786

File tree

2 files changed

+71
-30
lines changed

2 files changed

+71
-30
lines changed

packages/react-devtools-shared/src/__tests__/utils-test.js

+31
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import {
1111
getDisplayName,
1212
getDisplayNameForReactElement,
1313
} from 'react-devtools-shared/src/utils';
14+
import {format} from 'react-devtools-shared/src/backend/utils';
1415
import {
1516
REACT_SUSPENSE_LIST_TYPE as SuspenseList,
1617
REACT_STRICT_MODE_TYPE as StrictMode,
@@ -45,6 +46,7 @@ describe('utils', () => {
4546
expect(getDisplayName(FauxComponent, 'Fallback')).toEqual('Fallback');
4647
});
4748
});
49+
4850
describe('getDisplayNameForReactElement', () => {
4951
it('should return correct display name for an element with function type', () => {
5052
function FauxComponent() {}
@@ -54,29 +56,58 @@ describe('utils', () => {
5456
'OverrideDisplayName',
5557
);
5658
});
59+
5760
it('should return correct display name for an element with a type of StrictMode', () => {
5861
const element = createElement(StrictMode);
5962
expect(getDisplayNameForReactElement(element)).toEqual('StrictMode');
6063
});
64+
6165
it('should return correct display name for an element with a type of SuspenseList', () => {
6266
const element = createElement(SuspenseList);
6367
expect(getDisplayNameForReactElement(element)).toEqual('SuspenseList');
6468
});
69+
6570
it('should return NotImplementedInDevtools for an element with invalid symbol type', () => {
6671
const element = createElement(Symbol('foo'));
6772
expect(getDisplayNameForReactElement(element)).toEqual(
6873
'NotImplementedInDevtools',
6974
);
7075
});
76+
7177
it('should return NotImplementedInDevtools for an element with invalid type', () => {
7278
const element = createElement(true);
7379
expect(getDisplayNameForReactElement(element)).toEqual(
7480
'NotImplementedInDevtools',
7581
);
7682
});
83+
7784
it('should return Element for null type', () => {
7885
const element = createElement();
7986
expect(getDisplayNameForReactElement(element)).toEqual('Element');
8087
});
8188
});
89+
90+
describe('format', () => {
91+
it('should format simple strings', () => {
92+
expect(format('a', 'b', 'c')).toEqual('a b c');
93+
});
94+
95+
it('should format multiple argument types', () => {
96+
expect(format('abc', 123, true)).toEqual('abc 123 true');
97+
});
98+
99+
it('should support string substitutions', () => {
100+
expect(format('a %s b %s c', 123, true)).toEqual('a 123 b true c');
101+
});
102+
103+
it('should gracefully handle Symbol types', () => {
104+
expect(format(Symbol('a'), 'b', Symbol('c'))).toEqual(
105+
'Symbol(a) b Symbol(c)',
106+
);
107+
});
108+
109+
it('should gracefully handle Symbol type for the first argument', () => {
110+
expect(format(Symbol('abc'), 123)).toEqual('Symbol(abc) 123');
111+
});
112+
});
82113
});

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

+40-30
Original file line numberDiff line numberDiff line change
@@ -163,43 +163,53 @@ export function format(
163163
maybeMessage: any,
164164
...inputArgs: $ReadOnlyArray<any>
165165
): string {
166-
if (typeof maybeMessage !== 'string') {
167-
return [maybeMessage, ...inputArgs].join(' ');
168-
}
169-
170-
const re = /(%?)(%([jds]))/g;
171166
const args = inputArgs.slice();
172-
let formatted: string = maybeMessage;
173167

174-
if (args.length) {
175-
formatted = formatted.replace(re, (match, escaped, ptn, flag) => {
176-
let arg = args.shift();
177-
switch (flag) {
178-
case 's':
179-
arg += '';
180-
break;
181-
case 'd':
182-
case 'i':
183-
arg = parseInt(arg, 10).toString();
184-
break;
185-
case 'f':
186-
arg = parseFloat(arg).toString();
187-
break;
188-
}
189-
if (!escaped) {
190-
return arg;
191-
}
192-
args.unshift(arg);
193-
return match;
194-
});
168+
// Symbols cannot be concatenated with Strings.
169+
let formatted: string =
170+
typeof maybeMessage === 'symbol'
171+
? maybeMessage.toString()
172+
: '' + maybeMessage;
173+
174+
// If the first argument is a string, check for substitutions.
175+
if (typeof maybeMessage === 'string') {
176+
if (args.length) {
177+
const REGEXP = /(%?)(%([jds]))/g;
178+
179+
formatted = formatted.replace(REGEXP, (match, escaped, ptn, flag) => {
180+
let arg = args.shift();
181+
switch (flag) {
182+
case 's':
183+
arg += '';
184+
break;
185+
case 'd':
186+
case 'i':
187+
arg = parseInt(arg, 10).toString();
188+
break;
189+
case 'f':
190+
arg = parseFloat(arg).toString();
191+
break;
192+
}
193+
if (!escaped) {
194+
return arg;
195+
}
196+
args.unshift(arg);
197+
return match;
198+
});
199+
}
195200
}
196201

197-
// arguments remain after formatting
202+
// Arguments that remain after formatting.
198203
if (args.length) {
199-
formatted += ' ' + args.join(' ');
204+
for (let i = 0; i < args.length; i++) {
205+
const arg = args[i];
206+
207+
// Symbols cannot be concatenated with Strings.
208+
formatted += ' ' + (typeof arg === 'symbol' ? arg.toString() : arg);
209+
}
200210
}
201211

202-
// update escaped %% values
212+
// Update escaped %% values.
203213
formatted = formatted.replace(/%{2,2}/g, '%');
204214

205215
return '' + formatted;

0 commit comments

Comments
 (0)