Skip to content

Commit 6e0eea3

Browse files
committed
fix(formatting): Fix JSX delimiters escaping in string
BREAKING CHANGE: Improve string escaping of string that contains JSX delimiters (`{`,`}`,`<`,`>`) Before: ``` console.log(reactElementToJsxString(<div>{`Mustache :{`}</div>); // <div>Mustache :&lbrace;</div> ``` Now: ``` console.log(reactElementToJsxString(<div>{`Mustache :{`}</div>); // <div>{`Mustache :{`}</div> ```
1 parent d18809e commit 6e0eea3

File tree

3 files changed

+84
-35
lines changed

3 files changed

+84
-35
lines changed

src/formatter/formatTreeNode.js

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,17 +4,30 @@ import formatReactElementNode from './formatReactElementNode';
44
import type { Options } from './../options';
55
import type { TreeNode } from './../tree';
66

7-
const escape = (s: string): string =>
8-
s.replace(/{/g, '&lbrace;').replace(/}/g, '&rbrace;');
7+
const jsxStopChars = ['<', '>', '{', '}'];
8+
const shouldBeEscaped = (s: string) =>
9+
jsxStopChars.some(jsxStopChar => s.includes(jsxStopChar));
10+
11+
const escape = (s: string) => {
12+
if (!shouldBeEscaped(s)) {
13+
return s;
14+
}
15+
16+
return `{\`${s}\`}`;
17+
};
918

1019
export default (
1120
node: TreeNode,
1221
inline: boolean,
1322
lvl: number,
1423
options: Options
1524
): string => {
16-
if (node.type === 'string' || node.type === 'number') {
17-
return node.value ? escape(node.value.toString()) : '';
25+
if (node.type === 'number') {
26+
return String(node.value);
27+
}
28+
29+
if (node.type === 'string') {
30+
return node.value ? escape(String(node.value)) : '';
1831
}
1932

2033
if (node.type === 'ReactElement') {

src/formatter/formatTreeNode.spec.js

Lines changed: 63 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,70 @@
22

33
import formatTreeNode from './formatTreeNode';
44

5+
jest.mock('./formatReactElementNode', () => () =>
6+
'<MockedFormatReactElementNodeResult />'
7+
);
8+
59
describe('formatTreeNode', () => {
6-
it('should escape JSX entity on string node', () => {
10+
it('should format number tree node', () => {
11+
expect(formatTreeNode({ type: 'number', value: 42 }, true, 0, {})).toBe(
12+
'42'
13+
);
14+
});
15+
16+
it('should format string tree node', () => {
17+
expect(formatTreeNode({ type: 'string', value: 'foo' }, true, 0, {})).toBe(
18+
'foo'
19+
);
20+
});
21+
22+
it('should format react element tree node', () => {
23+
expect(
24+
formatTreeNode(
25+
{
26+
type: 'ReactElement',
27+
displayName: 'Foo',
28+
},
29+
true,
30+
0,
31+
{}
32+
)
33+
).toBe('<MockedFormatReactElementNodeResult />');
34+
});
35+
36+
const jsxDelimiters = ['<', '>', '{', '}'];
37+
jsxDelimiters.forEach(char => {
38+
it(`should escape string that contains the JSX delimiter "${char}"`, () => {
39+
expect(
40+
formatTreeNode(
41+
{ type: 'string', value: `I contain ${char}, is will be escaped` },
42+
true,
43+
0,
44+
{}
45+
)
46+
).toBe(`{\`I contain ${char}, is will be escaped\`}`);
47+
});
48+
});
49+
50+
it('should preserve the format of string', () => {
51+
expect(formatTreeNode({ type: 'string', value: 'foo\nbar' }, true, 0, {}))
52+
.toBe(`foo
53+
bar`);
54+
755
expect(
8-
formatTreeNode({ type: 'string', value: '{ foo: "bar" }' }, true, 0, {})
9-
).toBe('&lbrace; foo: "bar" &rbrace;');
56+
formatTreeNode(
57+
{
58+
type: 'string',
59+
value: JSON.stringify({ foo: 'bar' }, null, 2),
60+
},
61+
false,
62+
0,
63+
{
64+
tabStop: 2,
65+
}
66+
)
67+
).toBe(`{\`{
68+
"foo": "bar"
69+
}\`}`);
1070
});
1171
});

src/index.spec.js

Lines changed: 4 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -206,16 +206,16 @@ describe('reactElementToJSXString(ReactElement)', () => {
206206
);
207207
});
208208

209-
it('reactElementToJSXString(<script type="application/json+ld">&lbrace; hello: \'world\' &rbrace;</script>)', () => {
209+
it('reactElementToJSXString(<script type="application/json+ld">{`{ hello: \'world\' }`}</script>)', () => {
210210
expect(
211211
reactElementToJSXString(
212212
<script type="application/json+ld">
213-
&lbrace; hello: 'world' &rbrace;
213+
{`{ hello: 'world' }`}
214214
</script>
215215
)
216216
).toEqual(
217217
`<script type="application/json+ld">
218-
&lbrace; hello: 'world' &rbrace;
218+
{\`{ hello: 'world' }\`}
219219
</script>`
220220
);
221221
});
@@ -227,31 +227,7 @@ describe('reactElementToJSXString(ReactElement)', () => {
227227
)
228228
).toEqual(
229229
`<script type="application/json+ld">
230-
&lbrace; hello: 'world' &rbrace;
231-
</script>`
232-
);
233-
});
234-
235-
it('reactElementToJSXString(<script type="application/json+ld">\\u007B hello: \'world\' \\u007D</script>)', () => {
236-
expect(
237-
reactElementToJSXString(
238-
<script type="application/json+ld">\u007B hello: 'world' \u007D</script>
239-
)
240-
).toEqual(
241-
`<script type="application/json+ld">
242-
\\u007B hello: 'world' \\u007D
243-
</script>`
244-
);
245-
});
246-
247-
it('reactElementToJSXString(<script type="application/json+ld">{ hello: \'world\' }</script>)', () => {
248-
expect(
249-
reactElementToJSXString(
250-
<script type="application/json+ld">{`{ hello: 'world' }`}</script>
251-
)
252-
).toEqual(
253-
`<script type="application/json+ld">
254-
&lbrace; hello: 'world' &rbrace;
230+
{\`{ hello: 'world' }\`}
255231
</script>`
256232
);
257233
});

0 commit comments

Comments
 (0)