From 5530e842b26f733dceff443b4e3bdca035e94aef Mon Sep 17 00:00:00 2001 From: gtmnayan Date: Sun, 14 May 2023 11:20:09 +0545 Subject: [PATCH 1/9] speed up stringify string --- package.json | 2 +- src/uneval.js | 2 +- src/utils.js | 72 ++++++++++++++++++++++++++++++--------------------- test/test.js | 36 +++++++++++++------------- 4 files changed, 63 insertions(+), 49 deletions(-) diff --git a/package.json b/package.json index 644fea6..bf50b3c 100644 --- a/package.json +++ b/package.json @@ -1,5 +1,5 @@ { - "name": "devalue", + "name": "devalue2", "description": "Gets the job done when JSON.stringify can't", "version": "4.3.0", "repository": "Rich-Harris/devalue", diff --git a/src/uneval.js b/src/uneval.js index 79f35a3..21a98a4 100644 --- a/src/uneval.js +++ b/src/uneval.js @@ -8,7 +8,7 @@ import { } from './utils.js'; const chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_$'; -const unsafe_chars = /[<>\b\f\n\r\t\0\u2028\u2029]/g; +const unsafe_chars = /[<\b\f\n\r\t\0\u2028\u2029]/g; 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)$/; diff --git a/src/utils.js b/src/utils.js index 6bf9674..2c09f57 100644 --- a/src/utils.js +++ b/src/utils.js @@ -1,15 +1,12 @@ /** @type {Record} */ export const escaped = { '<': '\\u003C', - '>': '\\u003E', - '/': '\\u002F', '\\': '\\\\', '\b': '\\b', '\f': '\\f', '\n': '\\n', '\r': '\\r', '\t': '\\t', - '\0': '\\u0000', '\u2028': '\\u2028', '\u2029': '\\u2029' }; @@ -31,7 +28,7 @@ export function is_primitive(thing) { return Object(thing) !== thing; } -const object_proto_names = Object.getOwnPropertyNames(Object.prototype) +const object_proto_names = /* @__PURE__ */ Object.getOwnPropertyNames(Object.prototype) .sort() .join('\0'); @@ -51,35 +48,52 @@ export function get_type(thing) { return Object.prototype.toString.call(thing).slice(8, -1); } +/** @param {string} char */ +function get_escaped_char(char) { + switch (char) { + case '"': + return '\\"'; + case '<': + return '\\u003C'; + case '\\': + return '\\\\'; + case '\n': + return '\\n'; + case '\r': + return '\\r'; + case '\t': + return '\\t'; + case '\b': + return '\\b'; + case '\f': + return '\\f'; + case '\u2028': + return '\\u2028'; + case '\u2029': + return '\\u2029'; + default: + return char < ' ' + ? `\\u${char.charCodeAt(0).toString(16).padStart(4, '0')}` + : ''; + } +} + /** @param {string} str */ export function stringify_string(str) { - let result = '"'; + let result = ''; + let last_pos = 0; + const len = str.length; - for (let i = 0; i < str.length; i += 1) { - const char = str.charAt(i); - const code = char.charCodeAt(0); - - if (char === '"') { - result += '\\"'; - } else if (char in escaped) { - result += escaped[char]; - } else if (code <= 0x001F) { - result += `\\u${code.toString(16).toUpperCase().padStart(4, "0")}` - } else if (code >= 0xd800 && code <= 0xdfff) { - const next = str.charCodeAt(i + 1); - - // If this is the beginning of a [high, low] surrogate pair, - // add the next two characters, otherwise escape - if (code <= 0xdbff && next >= 0xdc00 && next <= 0xdfff) { - result += char + str[++i]; - } else { - result += `\\u${code.toString(16).toUpperCase()}`; - } - } else { - result += char; + for (let i = 0; i < len; i += 1) { + const char = str[i]; + const replacement = get_escaped_char(char); + if (replacement) { + result += str.slice(last_pos, i) + replacement; + last_pos = i + 1; } } - result += '"'; - return result; + result += str.slice(last_pos); + + return `"${result}"`; } diff --git a/test/test.js b/test/test.js index 1857c6b..afe3557 100644 --- a/test/test.js +++ b/test/test.js @@ -167,26 +167,26 @@ const fixtures = { { name: 'lone low surrogate', value: 'a\uDC00b', - js: '"a\\uDC00b"', - json: '["a\\uDC00b"]' + js: '"a\uDC00b"', + json: '["a\uDC00b"]' }, { name: 'lone high surrogate', value: 'a\uD800b', - js: '"a\\uD800b"', - json: '["a\\uD800b"]' + js: '"a\uD800b"', + json: '["a\uD800b"]' }, { name: 'two low surrogates', value: 'a\uDC00\uDC00b', - js: '"a\\uDC00\\uDC00b"', - json: '["a\\uDC00\\uDC00b"]' + js: '"a\uDC00\uDC00b"', + json: '["a\uDC00\uDC00b"]' }, { name: 'two high surrogates', value: 'a\uD800\uD800b', - js: '"a\\uD800\\uD800b"', - json: '["a\\uD800\\uD800b"]' + js: '"a\uD800\uD800b"', + json: '["a\uD800\uD800b"]' }, { name: 'surrogate pair', @@ -197,8 +197,8 @@ const fixtures = { { name: 'surrogate pair in wrong order', value: 'a\uDC00\uD800b', - js: '"a\\uDC00\\uD800b"', - json: '["a\\uDC00\\uD800b"]' + js: '"a\uDC00\uD800b"', + json: '["a\uDC00\uD800b"]' }, { name: 'nul', @@ -215,8 +215,8 @@ const fixtures = { { name: 'control character extremum', value: '\u001F', - js: '"\\u001F"', - json: '["\\u001F"]' + js: '"\\u001f"', + json: '["\\u001f"]' }, { name: 'backslash', @@ -342,20 +342,20 @@ const fixtures = { { name: 'Dangerous string', value: `