Skip to content

Commit 207040a

Browse files
committed
fix lone surrogate handling, add tests
1 parent bd8029f commit 207040a

File tree

2 files changed

+32
-5
lines changed

2 files changed

+32
-5
lines changed

src/index.ts

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,20 @@
11
const chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_$';
22
const reserved = /^(?:do|if|in|for|int|let|new|try|var|byte|case|char|else|enum|goto|long|this|void|with|await|break|catch|class|const|final|float|short|super|throw|while|yield|delete|double|export|import|native|return|switch|throws|typeof|boolean|default|extends|finally|package|private|abstract|continue|debugger|function|volatile|interface|protected|transient|implements|instanceof|synchronized)$/;
33
const unsafe = /[<>\/\u2028\u2029]/g;
4-
const escaped: Record<string, string> = { '<': '\\u003C', '>' : '\\u003E', '/': '\\u002F', '\u2028': '\\u2028', '\u2029': '\\u2029' };
4+
const escaped: Record<string, string> = {
5+
'<': '\\u003C',
6+
'>' : '\\u003E',
7+
'/': '\\u002F',
8+
'\\': '\\\\',
9+
'\b': '\\b',
10+
'\f': '\\f',
11+
'\n': '\\n',
12+
'\r': '\\r',
13+
'\t': '\\t',
14+
'\0': '\\u0000',
15+
'\u2028': '\\u2028',
16+
'\u2029': '\\u2029'
17+
};
518
const objectProtoOwnPropertyNames = Object.getOwnPropertyNames(Object.prototype).sort().join('\0');
619

720
export default function devalue(value: any) {
@@ -226,16 +239,21 @@ function stringifyString(str: string) {
226239
let result = '"';
227240

228241
for (let i = 0; i < str.length; i += 1) {
229-
const char = str[i];
242+
const char = str.charAt(i);
230243
const code = char.charCodeAt(0);
231244

232245
if (char === '"') {
233246
result += '\\"';
234247
} else if (char in escaped) {
235248
result += escaped[char];
236-
} else if ((code >= 0xD800 && code <= 0xDBFF) && i < str.length - 1) {
237-
// escape lone surrogates
238-
result += `\\\\u${code.toString(16).toUpperCase()}`;
249+
} else if ((code >= 0xD800 && code <= 0xDBFF)) {
250+
const next = str.charCodeAt(i + 1);
251+
if (next >= 0xDC00 && next <= 0xDFFF) {
252+
result += char;
253+
} else {
254+
// lone surrogates
255+
result += `\\u${code.toString(16).toUpperCase()}`;
256+
}
239257
} else {
240258
result += char;
241259
}

test/test.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,15 @@ describe('devalue', () => {
3535
test('Map', new Map([['a', 'b']]), 'new Map([["a","b"]])');
3636
});
3737

38+
describe('strings', () => {
39+
test('newline', 'a\nb', JSON.stringify('a\nb'));
40+
test('double quotes', '"yar"', JSON.stringify('"yar"'));
41+
test('lone surrogate', "\uD800", '"\\uD800"');
42+
test('surrogate pair', '𝌆', JSON.stringify('𝌆'));
43+
test('nul', '\0', JSON.stringify('\0'));
44+
test('backslash', '\\', JSON.stringify('\\'));
45+
});
46+
3847
describe('cycles', () => {
3948
let map = new Map();
4049
map.set('self', map);

0 commit comments

Comments
 (0)