From acc42187c4688cd12c437b540419eca25722f084 Mon Sep 17 00:00:00 2001 From: Matthias Behr Date: Mon, 10 Apr 2023 19:15:22 +0200 Subject: [PATCH] feat: add json5 support for rest query manual Add JSON5 support for manual rest query entries with indenting/formatting keeping comments and orig json5 codes (e.g. hex numbers). --- src/webview/package.json | 11 +- .../src/components/dltRestQueryManual.js | 20 +-- src/webview/src/components/utils/json5.js | 69 ++++++++++ src/webview/yarn.lock | 119 +++++++++++++++++- 4 files changed, 206 insertions(+), 13 deletions(-) create mode 100644 src/webview/src/components/utils/json5.js diff --git a/src/webview/package.json b/src/webview/package.json index 2b2a494..cff0f28 100644 --- a/src/webview/package.json +++ b/src/webview/package.json @@ -13,6 +13,7 @@ "@testing-library/react": "^13.4.0", "@testing-library/user-event": "^14.4.3", "dompurify": "^2.4.0", + "jju": "^1.4.0", "json5": "2.2.3", "json-stable-stringify": "^1.0.1", "marked": "^3.0.8", @@ -20,7 +21,9 @@ "react-dom": "^18.2.0", "react-scripts": "5.0.1", "react-table": "^7.8.0", - "web-vitals": "^3.0.2" + "web-vitals": "^3.0.2", + "fs": "npm:fs@0.0.2", + "assert": "npm:assert@2.0.0" }, "scripts": { "start": "react-scripts start", @@ -35,6 +38,12 @@ "react-app/jest" ] }, + "browser": { + "fs": false, + "os": false, + "path": false, + "assert": false + }, "browserslist": { "production": [ ">0.2%", diff --git a/src/webview/src/components/dltRestQueryManual.js b/src/webview/src/components/dltRestQueryManual.js index 043dce6..d85831c 100644 --- a/src/webview/src/components/dltRestQueryManual.js +++ b/src/webview/src/components/dltRestQueryManual.js @@ -6,6 +6,8 @@ import DialogActions from '@mui/material/DialogActions'; import { Button, DialogContent, DialogTitle, IconButton, TextField, Typography, Link } from '@mui/material'; import CloseIcon from '@mui/icons-material/Close'; import ErrorIcon from '@mui/icons-material/Error'; +import JSON5 from 'json5'; +import { formatJson5 } from './utils/json5'; // todo move those two functions into a util class. It's currently duplicated in docs. @@ -26,10 +28,11 @@ const rqUriDecode = (rq) => { if (andCnt) { toRet += ' &\n'; } andCnt++; try { - toRet += command + '=' + JSON.stringify(JSON.parse(commandParams), ' ', 2) + '\n'; + JSON5.parse(commandParams); + toRet += command + '=' + formatJson5(commandParams) + '\n'; } catch { if (commandParams.includes('{') || commandParams.includes('[') || commandParams.includes('"')) { - toRet += `\n`; + toRet += `\n`; } else { toRet += `${command}=${commandParams}`; } @@ -59,12 +62,15 @@ const rqUriEncode = (rq) => { const command = commandStr.slice(0, eqIdx); const commandParam = commandStr.slice(eqIdx + 1); try { - const commandParamEncoded = encodeURIComponent(JSON.stringify(JSON.parse(commandParam))); + // we do only check that its a valid json5 but then keep the orig data + // todo add formatting e.g. with the jju tokenizer + JSON5.parse(commandParam); + const commandParamEncoded = encodeURIComponent(formatJson5(commandParam)); toRet += `${command}=${commandParamEncoded}`; } catch { // if its a simple string then it's ok if (commandParam.includes('{') || commandParam.includes('[') || commandParam.includes('"')) { - toRet += `&\n`; + toRet += `&\n`; ok = false; } else { toRet += `${command}=${commandParam}`; @@ -84,7 +90,7 @@ const rqUriEncode = (rq) => { */ export default function DLTRestQueryManualDialog(props) { - console.log(`DLTRestQueryManualDialog(open=${props.open}, applyMode=${props.applyMode})`); + //console.log(`DLTRestQueryManualDialog(open=${props.open}, applyMode=${props.applyMode})`); // const [dataSource, setDataSource] = React.useState(props.dataSource); const [text, setText] = React.useState(props.dataSource); @@ -110,8 +116,8 @@ export default function DLTRestQueryManualDialog(props) { props.onClose(); } const handleSave = () => { - console.log(`DLTRestQueryManualDialog handleSave()`); - console.log(` dataSource=${text}`); + //console.log(`DLTRestQueryManualDialog handleSave()`); + //console.log(` dataSource=${text}`); props.onChange(text); // dont check for changes props.onClose(); diff --git a/src/webview/src/components/utils/json5.js b/src/webview/src/components/utils/json5.js new file mode 100644 index 0000000..ca23e72 --- /dev/null +++ b/src/webview/src/components/utils/json5.js @@ -0,0 +1,69 @@ +import jju from 'jju'; + +/** + * Indent/format a string representing a valid json5 expression. + * + * It's like JSON.stringify(JSON.parse(str)) but for JSON5 strings keeping + * comments, hex numbers, etc. + * The string is expected to be a valid JSON5 string already! + * + * The following rules are applied: + * - whitespace at begin of the line is ignored + * - whitespace at end of a line is removed + * - keys at begin of line are indented by 2 spaces per nested stack + * - after separator ':' a space is added + * - {} at begin of line are indented by 2 spaces per nested stack + * - literals at begin of line are indented... todo + * @param {string} json5Str + * @returns formatted string as valid json5 + */ +export function formatJson5(json5Str) { + try { + // use jju tokenizer: + const tokens = jju.tokenize(json5Str); + let formattedStr = ''; + let ignoreDirectNextWhitespace = false; + let nextLine = ''; + for (const token of tokens) { + const atBeginOfLine = nextLine.length === 0; + if (ignoreDirectNextWhitespace && token.type !== 'whitespace') { ignoreDirectNextWhitespace = false; } + switch (token.type) { + case 'separator': + switch (token.raw) { + case '{': + case '}': + if (atBeginOfLine) { nextLine += ' '.repeat(token.stack.length); } + nextLine += token.raw; + break; + case ':': + nextLine += token.raw; + nextLine += ' '; + ignoreDirectNextWhitespace = true; + break; + default: + nextLine += token.raw; + break; + } + break; + case 'newline': formattedStr += nextLine.trimEnd(); nextLine = ''; formattedStr += '\n'; break; + case 'literal': + case 'key': + if (atBeginOfLine) { + nextLine += ' '.repeat(token.stack.length); + } + nextLine += token.raw; + break; + case 'whitespace': if (!atBeginOfLine && !ignoreDirectNextWhitespace) { nextLine += token.raw; } break; + case 'comment': nextLine += token.raw; break; + default: + console.error(`formatJson5: unknown token '${token.type}'`, token); + break; + } + } + if (nextLine.length > 0) formattedStr += nextLine; + return formattedStr; + } catch (e) { + console.error(`formatJson5: got error='${e}'`); + return json5Str; + } +} diff --git a/src/webview/yarn.lock b/src/webview/yarn.lock index 0ac440f..7ea3e00 100644 --- a/src/webview/yarn.lock +++ b/src/webview/yarn.lock @@ -3165,6 +3165,16 @@ asap@~2.0.6: resolved "https://registry.yarnpkg.com/asap/-/asap-2.0.6.tgz#e50347611d7e690943208bbdafebcbc2fb866d46" integrity sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY= +"assert@npm:assert@2.0.0": + version "2.0.0" + resolved "https://registry.yarnpkg.com/assert/-/assert-2.0.0.tgz#95fc1c616d48713510680f2eaf2d10dd22e02d32" + integrity sha512-se5Cd+js9dXJnu6Ag2JFc00t+HmHOen+8Q+L7O9zI0PqQXr20uk2J0XQqMxZEeo5U50o8Nvmmx7dZrl+Ufr35A== + dependencies: + es6-object-assign "^1.1.0" + is-nan "^1.2.1" + object-is "^1.0.1" + util "^0.12.0" + ast-types-flow@^0.0.7: version "0.0.7" resolved "https://registry.yarnpkg.com/ast-types-flow/-/ast-types-flow-0.0.7.tgz#f70b735c6bca1a5c9c22d982c3e39e7feba3bdad" @@ -3197,6 +3207,11 @@ autoprefixer@^10.4.11: picocolors "^1.0.0" postcss-value-parser "^4.2.0" +available-typed-arrays@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz#92f95616501069d07d10edb2fc37d3e1c65123b7" + integrity sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw== + axe-core@^4.4.3: version "4.4.3" resolved "https://registry.yarnpkg.com/axe-core/-/axe-core-4.4.3.tgz#11c74d23d5013c0fa5d183796729bc3482bd2f6f" @@ -4566,6 +4581,11 @@ es-to-primitive@^1.2.1: is-date-object "^1.0.1" is-symbol "^1.0.2" +es6-object-assign@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/es6-object-assign/-/es6-object-assign-1.1.0.tgz#c2c3582656247c39ea107cb1e6652b6f9f24523c" + integrity sha512-MEl9uirslVwqQU369iHNWZXsI8yaZYGg/D65aOgZkeyFJwHYSxilf7rQzXKI7DdDuBPrBXbfk3sl9hJhmd5AUw== + escalade@^3.1.0, escalade@^3.1.1: version "3.1.1" resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.1.tgz#d8cfdc7000965c5a0174b4a82eaa5c0552742e40" @@ -5101,6 +5121,13 @@ follow-redirects@^1.0.0: resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.13.0.tgz#b42e8d93a2a7eea5ed88633676d6597bc8e384db" integrity sha512-aq6gF1BEKje4a9i9+5jimNFIpq4Q1WiwBToeRK5NvZBd/TRsmW8BsJfOEGkr76TbOyPVD3OVDN910EcUNtRYEA== +for-each@^0.3.3: + version "0.3.3" + resolved "https://registry.yarnpkg.com/for-each/-/for-each-0.3.3.tgz#69b447e88a0a5d32c3e7084f3f1710034b21376e" + integrity sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw== + dependencies: + is-callable "^1.1.3" + fork-ts-checker-webpack-plugin@^6.5.0: version "6.5.2" resolved "https://registry.yarnpkg.com/fork-ts-checker-webpack-plugin/-/fork-ts-checker-webpack-plugin-6.5.2.tgz#4f67183f2f9eb8ba7df7177ce3cf3e75cdafb340" @@ -5183,6 +5210,11 @@ fs.realpath@^1.0.0: resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8= +"fs@npm:fs@0.0.2": + version "0.0.2" + resolved "https://registry.yarnpkg.com/fs/-/fs-0.0.2.tgz#e1f244ef3933c1b2a64bd4799136060d0f5914f8" + integrity sha512-YAiVokMCrSIFZiroB1oz51hPiPRVcUtSa4x2U5RYXyhS9VAPdiFigKbPTnOSq7XY8wd3FIVPYmXpo5lMzFmxgg== + fsevents@^2.3.2, fsevents@~2.3.2: version "2.3.2" resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.2.tgz#8a526f78b8fdf4623b709e0b975c52c24c02fd1a" @@ -5326,6 +5358,13 @@ globby@^11.0.4, globby@^11.1.0: merge2 "^1.4.1" slash "^3.0.0" +gopd@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/gopd/-/gopd-1.0.1.tgz#29ff76de69dac7489b7c0918a5788e56477c332c" + integrity sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA== + dependencies: + get-intrinsic "^1.1.3" + graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.2.0, graceful-fs@^4.2.4: version "4.2.4" resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.4.tgz#2256bde14d3632958c465ebc96dc467ca07a29fb" @@ -5674,6 +5713,14 @@ ipaddr.js@^2.0.1: resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-2.0.1.tgz#eca256a7a877e917aeb368b0a7497ddf42ef81c0" integrity sha512-1qTgH9NG+IIJ4yfKs2e6Pp1bZg8wbDbKHT21HrLIeYBTRLgMYKnMTPAuI3Lcs61nfx5h1xlXnbJtH1kX5/d/ng== +is-arguments@^1.0.4: + version "1.1.1" + resolved "https://registry.yarnpkg.com/is-arguments/-/is-arguments-1.1.1.tgz#15b3f88fda01f2a97fec84ca761a560f123efa9b" + integrity sha512-8Q7EARjzEnKpt/PCD7e1cgUS0a6X8u5tdSiMqXhojOdoV9TsMsiO+9VLC5vAmO8N7/GmXn7yjR8qnA6bVAEzfA== + dependencies: + call-bind "^1.0.2" + has-tostringtag "^1.0.0" + is-arrayish@^0.2.1: version "0.2.1" resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d" @@ -5701,16 +5748,16 @@ is-boolean-object@^1.1.0: call-bind "^1.0.2" has-tostringtag "^1.0.0" +is-callable@^1.1.3, is-callable@^1.2.6: + version "1.2.7" + resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.7.tgz#3bc2a85ea742d9e36205dcacdd72ca1fdc51b055" + integrity sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA== + is-callable@^1.1.4, is-callable@^1.2.2: version "1.2.2" resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.2.tgz#c7c6715cd22d4ddb48d3e19970223aceabb080d9" integrity sha512-dnMqspv5nU3LoewK2N/y7KLtxtakvTuaCsU9FU50/QDmdbHNy/4/JuRtMHqRU22o3q+W89YQndQEeCVwK+3qrA== -is-callable@^1.2.6: - version "1.2.7" - resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.7.tgz#3bc2a85ea742d9e36205dcacdd72ca1fdc51b055" - integrity sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA== - is-core-module@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.0.0.tgz#58531b70aed1db7c0e8d4eb1a0a2d1ddd64bd12d" @@ -5755,6 +5802,13 @@ is-generator-fn@^2.0.0: resolved "https://registry.yarnpkg.com/is-generator-fn/-/is-generator-fn-2.1.0.tgz#7d140adc389aaf3011a8f2a2a4cfa6faadffb118" integrity sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ== +is-generator-function@^1.0.7: + version "1.0.10" + resolved "https://registry.yarnpkg.com/is-generator-function/-/is-generator-function-1.0.10.tgz#f1558baf1ac17e0deea7c0415c438351ff2b3c72" + integrity sha512-jsEjy9l3yiXEQ+PsXdmBwEPcOxaXWLspKdplFUVI9vq1iZgIekeC0L167qeu86czQaxed3q/Uzuw0swL0irL8A== + dependencies: + has-tostringtag "^1.0.0" + is-glob@^4.0.0, is-glob@^4.0.1, is-glob@~4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.1.tgz#7567dbe9f2f5e2467bc77ab83c4a29482407a5dc" @@ -5779,6 +5833,14 @@ is-module@^1.0.0: resolved "https://registry.yarnpkg.com/is-module/-/is-module-1.0.0.tgz#3258fb69f78c14d5b815d664336b4cffb6441591" integrity sha1-Mlj7afeMFNW4FdZkM2tM/7ZEFZE= +is-nan@^1.2.1: + version "1.3.2" + resolved "https://registry.yarnpkg.com/is-nan/-/is-nan-1.3.2.tgz#043a54adea31748b55b6cd4e09aadafa69bd9e1d" + integrity sha512-E+zBKpQ2t6MEo1VsonYmluk9NxGrbzpeeLC2xIViuO2EjU2xsXsBPwTr3Ykv9l08UYEVEdWeRZNouaZqF6RN0w== + dependencies: + call-bind "^1.0.0" + define-properties "^1.1.3" + is-negative-zero@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/is-negative-zero/-/is-negative-zero-2.0.0.tgz#9553b121b0fac28869da9ed459e20c7543788461" @@ -5879,6 +5941,17 @@ is-symbol@^1.0.3: dependencies: has-symbols "^1.0.2" +is-typed-array@^1.1.10, is-typed-array@^1.1.3: + version "1.1.10" + resolved "https://registry.yarnpkg.com/is-typed-array/-/is-typed-array-1.1.10.tgz#36a5b5cb4189b575d1a3e4b08536bfb485801e3f" + integrity sha512-PJqgEHiWZvMpaFZ3uTc8kHPM4+4ADTlDniuQL7cU/UDA0Ql7F70yGfHph3cLNe+c9toaigv+DFzTJKhc2CtO6A== + dependencies: + available-typed-arrays "^1.0.5" + call-bind "^1.0.2" + for-each "^0.3.3" + gopd "^1.0.1" + has-tostringtag "^1.0.0" + is-typedarray@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a" @@ -6462,6 +6535,11 @@ jest@^27.4.3: import-local "^3.0.2" jest-cli "^27.5.1" +jju@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/jju/-/jju-1.4.0.tgz#a3abe2718af241a2b2904f84a625970f389ae32a" + integrity sha512-8wb9Yw966OSxApiCt0K3yNJL8pnNeIv+OEq2YMidz4FKP6nonSRoOXc80iXY4JaN2FC11B9qsNmDsm+ZOfMROA== + js-sdsl@^4.1.4: version "4.1.4" resolved "https://registry.yarnpkg.com/js-sdsl/-/js-sdsl-4.1.4.tgz#78793c90f80e8430b7d8dc94515b6c77d98a26a6" @@ -7183,6 +7261,14 @@ object-inspect@^1.8.0: resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.8.0.tgz#df807e5ecf53a609cc6bfe93eac3cc7be5b3a9d0" integrity sha512-jLdtEOB112fORuypAyl/50VRVIBIdVQOSUUGQHzJ4xBSbit81zRarz7GThkEFZy1RceYrWYcPcBFPQwHyAc1gA== +object-is@^1.0.1: + version "1.1.5" + resolved "https://registry.yarnpkg.com/object-is/-/object-is-1.1.5.tgz#b9deeaa5fc7f1846a0faecdceec138e5778f53ac" + integrity sha512-3cyDsyHgtmi7I7DfSSI2LDp6SK2lwvtbg0p0R1e0RvTqF5ceGx+K2dfSjm1bKDMVCFEDAQvy+o8c6a7VujOddw== + dependencies: + call-bind "^1.0.2" + define-properties "^1.1.3" + object-keys@^1.0.12, object-keys@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.1.1.tgz#1c47f272df277f3b1daf061677d9c82e2322c60e" @@ -9731,6 +9817,17 @@ util.promisify@~1.0.0: has-symbols "^1.0.1" object.getownpropertydescriptors "^2.1.0" +util@^0.12.0: + version "0.12.5" + resolved "https://registry.yarnpkg.com/util/-/util-0.12.5.tgz#5f17a6059b73db61a875668781a1c2b136bd6fbc" + integrity sha512-kZf/K6hEIrWHI6XqOFUiiMa+79wE/D8Q+NCNAWclkyg3b4d2k7s0QGepNjiABc+aR3N1PAyHL7p6UcLY6LmrnA== + dependencies: + inherits "^2.0.3" + is-arguments "^1.0.4" + is-generator-function "^1.0.7" + is-typed-array "^1.1.3" + which-typed-array "^1.1.2" + utila@~0.4: version "0.4.0" resolved "https://registry.yarnpkg.com/utila/-/utila-0.4.0.tgz#8a16a05d445657a3aea5eecc5b12a4fa5379772c" @@ -10005,6 +10102,18 @@ which-boxed-primitive@^1.0.2: is-string "^1.0.5" is-symbol "^1.0.3" +which-typed-array@^1.1.2: + version "1.1.9" + resolved "https://registry.yarnpkg.com/which-typed-array/-/which-typed-array-1.1.9.tgz#307cf898025848cf995e795e8423c7f337efbde6" + integrity sha512-w9c4xkx6mPidwp7180ckYWfMmvxpjlZuIudNtDf4N/tTAUB8VJbX25qZoAsrtGuYNnGw3pa0AXgbGKRB8/EceA== + dependencies: + available-typed-arrays "^1.0.5" + call-bind "^1.0.2" + for-each "^0.3.3" + gopd "^1.0.1" + has-tostringtag "^1.0.0" + is-typed-array "^1.1.10" + which@^1.3.1: version "1.3.1" resolved "https://registry.yarnpkg.com/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a"