diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 90a2d59..5bda2ea 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -45,7 +45,11 @@ "colors-named-hex": "^1.0.2", "franc-min": "^6.2.0", "hsl-matcher": "^1.2.4", + "jsox": "^1.2.123", "lezer": "^0.13.5", + "linguist-languages": "^9.0.0", + "node-sql-parser": "^5.3.12", + "php-parser": "^3.2.5", "pinia": "^3.0.3", "pinia-plugin-persistedstate": "^4.5.0", "prettier": "^3.6.2", @@ -71,6 +75,7 @@ "typescript-eslint": "^8.43.0", "unplugin-vue-components": "^29.0.0", "vite": "^7.1.5", + "vite-plugin-node-polyfills": "^0.24.0", "vue-eslint-parser": "^10.2.0", "vue-tsc": "^3.0.6" } @@ -1816,6 +1821,65 @@ "dev": true, "license": "MIT" }, + "node_modules/@rollup/plugin-inject": { + "version": "5.0.5", + "resolved": "https://registry.npmmirror.com/@rollup/plugin-inject/-/plugin-inject-5.0.5.tgz", + "integrity": "sha512-2+DEJbNBoPROPkgTDNe8/1YXWcqxbN5DTjASVIOx8HS+pITXushyNiBV56RB08zuptzz8gT3YfkqriTBVycepg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@rollup/pluginutils": "^5.0.1", + "estree-walker": "^2.0.2", + "magic-string": "^0.30.3" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, + "node_modules/@rollup/pluginutils": { + "version": "5.3.0", + "resolved": "https://registry.npmmirror.com/@rollup/pluginutils/-/pluginutils-5.3.0.tgz", + "integrity": "sha512-5EdhGZtnu3V88ces7s53hhfK5KSASnJZv8Lulpc04cWO3REESroJXg73DFsOmgbU2BhwV0E20bu2IDZb3VKW4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "estree-walker": "^2.0.2", + "picomatch": "^4.0.2" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, + "node_modules/@rollup/pluginutils/node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmmirror.com/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, "node_modules/@rollup/rollup-android-arm-eabi": { "version": "4.46.2", "resolved": "https://registry.npmmirror.com/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.46.2.tgz", @@ -2120,6 +2184,12 @@ "undici-types": "~7.10.0" } }, + "node_modules/@types/pegjs": { + "version": "0.10.6", + "resolved": "https://registry.npmmirror.com/@types/pegjs/-/pegjs-0.10.6.tgz", + "integrity": "sha512-eLYXDbZWXh2uxf+w8sXS8d6KSoXTswfps6fvCUuVAGN8eRpfe7h9eSRydxiSJvo9Bf+GzifsDOr9TMQlmJdmkw==", + "license": "MIT" + }, "node_modules/@types/remarkable": { "version": "2.0.8", "resolved": "https://registry.npmmirror.com/@types/remarkable/-/remarkable-2.0.8.tgz", @@ -2703,6 +2773,39 @@ "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", "license": "Python-2.0" }, + "node_modules/asn1.js": { + "version": "4.10.1", + "resolved": "https://registry.npmmirror.com/asn1.js/-/asn1.js-4.10.1.tgz", + "integrity": "sha512-p32cOF5q0Zqs9uBiONKYLm6BClCoBCM5O9JfeUSlnQLBTxYdTK+pW+nXflm8UkKd2UYlEbYz5qEi0JuZR9ckSw==", + "dev": true, + "license": "MIT", + "dependencies": { + "bn.js": "^4.0.0", + "inherits": "^2.0.1", + "minimalistic-assert": "^1.0.0" + } + }, + "node_modules/asn1.js/node_modules/bn.js": { + "version": "4.12.2", + "resolved": "https://registry.npmmirror.com/bn.js/-/bn.js-4.12.2.tgz", + "integrity": "sha512-n4DSx829VRTRByMRGdjQ9iqsN0Bh4OolPsFnaZBLcbi8iXcB+kJ9s7EnRt4wILZNV3kPLHkRVfOc/HvhC3ovDw==", + "dev": true, + "license": "MIT" + }, + "node_modules/assert": { + "version": "2.1.0", + "resolved": "https://registry.npmmirror.com/assert/-/assert-2.1.0.tgz", + "integrity": "sha512-eLHpSK/Y4nhMJ07gDaAzoX/XAKS8PSaojml3M0DM4JpV1LAi5JOJ/p6H/XWrl8L+DzVEvVCW1z3vWAaB9oTsQw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.2", + "is-nan": "^1.3.2", + "object-is": "^1.1.5", + "object.assign": "^4.1.4", + "util": "^0.12.5" + } + }, "node_modules/autolinker": { "version": "3.16.2", "resolved": "https://registry.npmmirror.com/autolinker/-/autolinker-3.16.2.tgz", @@ -2712,6 +2815,22 @@ "tslib": "^2.3.0" } }, + "node_modules/available-typed-arrays": { + "version": "1.0.7", + "resolved": "https://registry.npmmirror.com/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", + "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "possible-typed-array-names": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmmirror.com/balanced-match/-/balanced-match-1.0.2.tgz", @@ -2719,6 +2838,36 @@ "dev": true, "license": "MIT" }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmmirror.com/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/big-integer": { + "version": "1.6.52", + "resolved": "https://registry.npmmirror.com/big-integer/-/big-integer-1.6.52.tgz", + "integrity": "sha512-QxD8cf2eVqJOOz63z6JIN9BzvVs/dlySa5HGSBH5xtR8dPteIRQnBxxKqkNTiT6jbDTF6jAfrd4oMcND9RGbQg==", + "license": "Unlicense", + "engines": { + "node": ">=0.6" + } + }, "node_modules/binary-extensions": { "version": "2.3.0", "resolved": "https://registry.npmmirror.com/binary-extensions/-/binary-extensions-2.3.0.tgz", @@ -2741,6 +2890,13 @@ "url": "https://github.com/sponsors/antfu" } }, + "node_modules/bn.js": { + "version": "5.2.2", + "resolved": "https://registry.npmmirror.com/bn.js/-/bn.js-5.2.2.tgz", + "integrity": "sha512-v2YAxEmKaBLahNwE1mjp4WON6huMNeuDvagFZW+ASCuA/ku0bXR9hSMw0XpiqMoA3+rmnyck/tPRSFQkoC9Cuw==", + "dev": true, + "license": "MIT" + }, "node_modules/boolbase": { "version": "1.0.0", "resolved": "https://registry.npmmirror.com/boolbase/-/boolbase-1.0.0.tgz", @@ -2772,6 +2928,196 @@ "node": ">=8" } }, + "node_modules/brorand": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/brorand/-/brorand-1.1.0.tgz", + "integrity": "sha512-cKV8tMCEpQs4hK/ik71d6LrPOnpkpGBR0wzxqr68g2m/LB2GxVYQroAjMJZRVM1Y4BCjCKc3vAamxSzOY2RP+w==", + "dev": true, + "license": "MIT" + }, + "node_modules/browser-resolve": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/browser-resolve/-/browser-resolve-2.0.0.tgz", + "integrity": "sha512-7sWsQlYL2rGLy2IWm8WL8DCTJvYLc/qlOnsakDac87SOoCd16WLsaAMdCiAqsTNHIe+SXfaqyxyo6THoWqs8WQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "resolve": "^1.17.0" + } + }, + "node_modules/browserify-aes": { + "version": "1.2.0", + "resolved": "https://registry.npmmirror.com/browserify-aes/-/browserify-aes-1.2.0.tgz", + "integrity": "sha512-+7CHXqGuspUn/Sl5aO7Ea0xWGAtETPXNSAjHo48JfLdPWcMng33Xe4znFvQweqc/uzk5zSOI3H52CYnjCfb5hA==", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer-xor": "^1.0.3", + "cipher-base": "^1.0.0", + "create-hash": "^1.1.0", + "evp_bytestokey": "^1.0.3", + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/browserify-cipher": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/browserify-cipher/-/browserify-cipher-1.0.1.tgz", + "integrity": "sha512-sPhkz0ARKbf4rRQt2hTpAHqn47X3llLkUGn+xEJzLjwY8LRs2p0v7ljvI5EyoRO/mexrNunNECisZs+gw2zz1w==", + "dev": true, + "license": "MIT", + "dependencies": { + "browserify-aes": "^1.0.4", + "browserify-des": "^1.0.0", + "evp_bytestokey": "^1.0.0" + } + }, + "node_modules/browserify-des": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/browserify-des/-/browserify-des-1.0.2.tgz", + "integrity": "sha512-BioO1xf3hFwz4kc6iBhI3ieDFompMhrMlnDFC4/0/vd5MokpuAc3R+LYbwTA9A5Yc9pq9UYPqffKpW2ObuwX5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "cipher-base": "^1.0.1", + "des.js": "^1.0.0", + "inherits": "^2.0.1", + "safe-buffer": "^5.1.2" + } + }, + "node_modules/browserify-rsa": { + "version": "4.1.1", + "resolved": "https://registry.npmmirror.com/browserify-rsa/-/browserify-rsa-4.1.1.tgz", + "integrity": "sha512-YBjSAiTqM04ZVei6sXighu679a3SqWORA3qZTEqZImnlkDIFtKc6pNutpjyZ8RJTjQtuYfeetkxM11GwoYXMIQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "bn.js": "^5.2.1", + "randombytes": "^2.1.0", + "safe-buffer": "^5.2.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/browserify-sign": { + "version": "4.2.3", + "resolved": "https://registry.npmmirror.com/browserify-sign/-/browserify-sign-4.2.3.tgz", + "integrity": "sha512-JWCZW6SKhfhjJxO8Tyiiy+XYB7cqd2S5/+WeYHsKdNKFlCBhKbblba1A/HN/90YwtxKc8tCErjffZl++UNmGiw==", + "dev": true, + "license": "ISC", + "dependencies": { + "bn.js": "^5.2.1", + "browserify-rsa": "^4.1.0", + "create-hash": "^1.2.0", + "create-hmac": "^1.1.7", + "elliptic": "^6.5.5", + "hash-base": "~3.0", + "inherits": "^2.0.4", + "parse-asn1": "^5.1.7", + "readable-stream": "^2.3.8", + "safe-buffer": "^5.2.1" + }, + "engines": { + "node": ">= 0.12" + } + }, + "node_modules/browserify-sign/node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/browserify-sign/node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmmirror.com/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "dev": true, + "license": "MIT", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/browserify-sign/node_modules/readable-stream/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmmirror.com/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true, + "license": "MIT" + }, + "node_modules/browserify-sign/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmmirror.com/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/browserify-sign/node_modules/string_decoder/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmmirror.com/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true, + "license": "MIT" + }, + "node_modules/browserify-zlib": { + "version": "0.2.0", + "resolved": "https://registry.npmmirror.com/browserify-zlib/-/browserify-zlib-0.2.0.tgz", + "integrity": "sha512-Z942RysHXmJrhqk88FmKBVq/v5tqmSkDz7p54G/MGyjMnCFFnC79XWNbg+Vta8W6Wb2qtSZTSxIGkJrRpCFEiA==", + "dev": true, + "license": "MIT", + "dependencies": { + "pako": "~1.0.5" + } + }, + "node_modules/buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmmirror.com/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, + "node_modules/buffer-xor": { + "version": "1.0.3", + "resolved": "https://registry.npmmirror.com/buffer-xor/-/buffer-xor-1.0.3.tgz", + "integrity": "sha512-571s0T7nZWK6vB67HI5dyUF7wXiNcfaPPPTl6zYCNApANjIvYJTg7hlud/+cJpdAhS7dVzqMLmfhfHR3rAcOjQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/builtin-status-codes": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz", + "integrity": "sha512-HpGFw18DgFWlncDfjTa2rcQ4W88O1mC8e8yZ2AvQY5KDaktSTwo+KRf6nHK6FRI5FyRyb/5T6+TSxfP7QyGsmQ==", + "dev": true, + "license": "MIT" + }, "node_modules/c12": { "version": "3.0.4", "resolved": "https://registry.npmmirror.com/c12/-/c12-3.0.4.tgz", @@ -2802,6 +3148,56 @@ } } }, + "node_modules/call-bind": { + "version": "1.0.8", + "resolved": "https://registry.npmmirror.com/call-bind/-/call-bind-1.0.8.tgz", + "integrity": "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.0", + "es-define-property": "^1.0.0", + "get-intrinsic": "^1.2.4", + "set-function-length": "^1.2.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmmirror.com/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/callsites": { "version": "3.1.0", "resolved": "https://registry.npmmirror.com/callsites/-/callsites-3.1.0.tgz", @@ -2844,6 +3240,20 @@ "url": "https://paulmillr.com/funding/" } }, + "node_modules/cipher-base": { + "version": "1.0.6", + "resolved": "https://registry.npmmirror.com/cipher-base/-/cipher-base-1.0.6.tgz", + "integrity": "sha512-3Ek9H3X6pj5TgenXYtNWdaBon1tgYCaebd+XPg0keyjEbEfkD4KkmAxkQ/i1vYvxdcT5nscLBfq9VJRmCBcFSw==", + "dev": true, + "license": "MIT", + "dependencies": { + "inherits": "^2.0.4", + "safe-buffer": "^5.2.1" + }, + "engines": { + "node": ">= 0.10" + } + }, "node_modules/citty": { "version": "0.1.6", "resolved": "https://registry.npmmirror.com/citty/-/citty-0.1.6.tgz", @@ -2964,6 +3374,19 @@ "node": "^14.18.0 || >=16.10.0" } }, + "node_modules/console-browserify": { + "version": "1.2.0", + "resolved": "https://registry.npmmirror.com/console-browserify/-/console-browserify-1.2.0.tgz", + "integrity": "sha512-ZMkYO/LkF17QvCPqM0gxw8yUzigAOZOSWSHg91FH6orS7vcEj5dVZTidN2fQ14yBSdg97RqhSNwLUXInd52OTA==", + "dev": true + }, + "node_modules/constants-browserify": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/constants-browserify/-/constants-browserify-1.0.0.tgz", + "integrity": "sha512-xFxOwqIzR/e1k1gLiWEophSCMqXcwVHIH7akf7b/vxcUeGunlj3hvZaaqxwHsTgn+IndtkQJgSztIDWeumWJDQ==", + "dev": true, + "license": "MIT" + }, "node_modules/copy-anything": { "version": "3.0.5", "resolved": "https://registry.npmmirror.com/copy-anything/-/copy-anything-3.0.5.tgz", @@ -2979,6 +3402,67 @@ "url": "https://github.com/sponsors/mesqueeb" } }, + "node_modules/core-util-is": { + "version": "1.0.3", + "resolved": "https://registry.npmmirror.com/core-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/create-ecdh": { + "version": "4.0.4", + "resolved": "https://registry.npmmirror.com/create-ecdh/-/create-ecdh-4.0.4.tgz", + "integrity": "sha512-mf+TCx8wWc9VpuxfP2ht0iSISLZnt0JgWlrOKZiNqyUZWnjIaCIVNQArMHnCZKfEYRg6IM7A+NeJoN8gf/Ws0A==", + "dev": true, + "license": "MIT", + "dependencies": { + "bn.js": "^4.1.0", + "elliptic": "^6.5.3" + } + }, + "node_modules/create-ecdh/node_modules/bn.js": { + "version": "4.12.2", + "resolved": "https://registry.npmmirror.com/bn.js/-/bn.js-4.12.2.tgz", + "integrity": "sha512-n4DSx829VRTRByMRGdjQ9iqsN0Bh4OolPsFnaZBLcbi8iXcB+kJ9s7EnRt4wILZNV3kPLHkRVfOc/HvhC3ovDw==", + "dev": true, + "license": "MIT" + }, + "node_modules/create-hash": { + "version": "1.2.0", + "resolved": "https://registry.npmmirror.com/create-hash/-/create-hash-1.2.0.tgz", + "integrity": "sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg==", + "dev": true, + "license": "MIT", + "dependencies": { + "cipher-base": "^1.0.1", + "inherits": "^2.0.1", + "md5.js": "^1.3.4", + "ripemd160": "^2.0.1", + "sha.js": "^2.4.0" + } + }, + "node_modules/create-hmac": { + "version": "1.1.7", + "resolved": "https://registry.npmmirror.com/create-hmac/-/create-hmac-1.1.7.tgz", + "integrity": "sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg==", + "dev": true, + "license": "MIT", + "dependencies": { + "cipher-base": "^1.0.3", + "create-hash": "^1.1.0", + "inherits": "^2.0.1", + "ripemd160": "^2.0.0", + "safe-buffer": "^5.0.1", + "sha.js": "^2.4.8" + } + }, + "node_modules/create-require": { + "version": "1.1.1", + "resolved": "https://registry.npmmirror.com/create-require/-/create-require-1.1.1.tgz", + "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", + "dev": true, + "license": "MIT" + }, "node_modules/crelt": { "version": "1.0.6", "resolved": "https://registry.npmmirror.com/crelt/-/crelt-1.0.6.tgz", @@ -3000,6 +3484,33 @@ "node": ">= 8" } }, + "node_modules/crypto-browserify": { + "version": "3.12.1", + "resolved": "https://registry.npmmirror.com/crypto-browserify/-/crypto-browserify-3.12.1.tgz", + "integrity": "sha512-r4ESw/IlusD17lgQi1O20Fa3qNnsckR126TdUuBgAu7GBYSIPvdNyONd3Zrxh0xCwA4+6w/TDArBPsMvhur+KQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "browserify-cipher": "^1.0.1", + "browserify-sign": "^4.2.3", + "create-ecdh": "^4.0.4", + "create-hash": "^1.2.0", + "create-hmac": "^1.1.7", + "diffie-hellman": "^5.0.3", + "hash-base": "~3.0.4", + "inherits": "^2.0.4", + "pbkdf2": "^3.1.2", + "public-encrypt": "^4.0.3", + "randombytes": "^2.1.0", + "randomfill": "^1.0.4" + }, + "engines": { + "node": ">= 0.10" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/cssesc": { "version": "3.0.0", "resolved": "https://registry.npmmirror.com/cssesc/-/cssesc-3.0.0.tgz", @@ -3057,12 +3568,59 @@ "integrity": "sha512-2J6Kc/m3irCeqVG42T+SaUMesaK7oGWaedGnQQK/+O0gYc+2SP5bKh/KKTE7d7SJ+GCA9UUE1GRzh6oDe0EnGw==", "license": "MIT" }, + "node_modules/define-data-property": { + "version": "1.1.4", + "resolved": "https://registry.npmmirror.com/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/define-properties": { + "version": "1.2.1", + "resolved": "https://registry.npmmirror.com/define-properties/-/define-properties-1.2.1.tgz", + "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-data-property": "^1.0.1", + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/defu": { "version": "6.1.4", "resolved": "https://registry.npmmirror.com/defu/-/defu-6.1.4.tgz", "integrity": "sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==", "license": "MIT" }, + "node_modules/des.js": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/des.js/-/des.js-1.1.0.tgz", + "integrity": "sha512-r17GxjhUCjSRy8aiJpr8/UadFIzMzJGexI3Nmz4ADi9LYSFx4gTBp80+NaX/YsXWWLhpZ7v/v/ubEc/bCNfKwg==", + "dev": true, + "license": "MIT", + "dependencies": { + "inherits": "^2.0.1", + "minimalistic-assert": "^1.0.0" + } + }, "node_modules/destr": { "version": "2.0.5", "resolved": "https://registry.npmmirror.com/destr/-/destr-2.0.5.tgz", @@ -3082,12 +3640,44 @@ "node": ">=0.10" } }, + "node_modules/diffie-hellman": { + "version": "5.0.3", + "resolved": "https://registry.npmmirror.com/diffie-hellman/-/diffie-hellman-5.0.3.tgz", + "integrity": "sha512-kqag/Nl+f3GwyK25fhUMYj81BUOrZ9IuJsjIcDE5icNM9FJHAVm3VcUDxdLPoQtTuUylWm6ZIknYJwwaPxsUzg==", + "dev": true, + "license": "MIT", + "dependencies": { + "bn.js": "^4.1.0", + "miller-rabin": "^4.0.0", + "randombytes": "^2.0.0" + } + }, + "node_modules/diffie-hellman/node_modules/bn.js": { + "version": "4.12.2", + "resolved": "https://registry.npmmirror.com/bn.js/-/bn.js-4.12.2.tgz", + "integrity": "sha512-n4DSx829VRTRByMRGdjQ9iqsN0Bh4OolPsFnaZBLcbi8iXcB+kJ9s7EnRt4wILZNV3kPLHkRVfOc/HvhC3ovDw==", + "dev": true, + "license": "MIT" + }, "node_modules/discontinuous-range": { "version": "1.0.0", "resolved": "https://registry.npmmirror.com/discontinuous-range/-/discontinuous-range-1.0.0.tgz", "integrity": "sha512-c68LpLbO+7kP/b1Hr1qs8/BJ09F5khZGTxqxZuhzxpmwJKOgRFHJWIb9/KmqnqHhLdO55aOxFH/EGBvUQbL/RQ==", "license": "MIT" }, + "node_modules/domain-browser": { + "version": "4.22.0", + "resolved": "https://registry.npmmirror.com/domain-browser/-/domain-browser-4.22.0.tgz", + "integrity": "sha512-IGBwjF7tNk3cwypFNH/7bfzBcgSCbaMOD3GsaY1AU/JRrnHnYgEM0+9kQt52iZxjNsjBtJYtao146V+f8jFZNw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://bevry.me/fund" + } + }, "node_modules/dotenv": { "version": "16.5.0", "resolved": "https://registry.npmmirror.com/dotenv/-/dotenv-16.5.0.tgz", @@ -3102,6 +3692,44 @@ "url": "https://dotenvx.com" } }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/elliptic": { + "version": "6.6.1", + "resolved": "https://registry.npmmirror.com/elliptic/-/elliptic-6.6.1.tgz", + "integrity": "sha512-RaddvvMatK2LJHqFJ+YA4WysVN5Ita9E35botqIYspQ4TkRAlCicdzKOjlyv/1Za5RyTNn7di//eEV0uTAfe3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "bn.js": "^4.11.9", + "brorand": "^1.1.0", + "hash.js": "^1.0.0", + "hmac-drbg": "^1.0.1", + "inherits": "^2.0.4", + "minimalistic-assert": "^1.0.1", + "minimalistic-crypto-utils": "^1.0.1" + } + }, + "node_modules/elliptic/node_modules/bn.js": { + "version": "4.12.2", + "resolved": "https://registry.npmmirror.com/bn.js/-/bn.js-4.12.2.tgz", + "integrity": "sha512-n4DSx829VRTRByMRGdjQ9iqsN0Bh4OolPsFnaZBLcbi8iXcB+kJ9s7EnRt4wILZNV3kPLHkRVfOc/HvhC3ovDw==", + "dev": true, + "license": "MIT" + }, "node_modules/entities": { "version": "4.5.0", "resolved": "https://registry.npmmirror.com/entities/-/entities-4.5.0.tgz", @@ -3122,6 +3750,39 @@ "optional": true, "peer": true }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmmirror.com/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmmirror.com/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/esbuild": { "version": "0.25.2", "resolved": "https://registry.npmmirror.com/esbuild/-/esbuild-0.25.2.tgz", @@ -3365,6 +4026,27 @@ "node": ">=0.10.0" } }, + "node_modules/events": { + "version": "3.3.0", + "resolved": "https://registry.npmmirror.com/events/-/events-3.3.0.tgz", + "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.x" + } + }, + "node_modules/evp_bytestokey": { + "version": "1.0.3", + "resolved": "https://registry.npmmirror.com/evp_bytestokey/-/evp_bytestokey-1.0.3.tgz", + "integrity": "sha512-/f2Go4TognH/KvCISP7OUsHn85hT9nUkxxA9BEWxFn+Oj9o8ZNLm/40hdlgSLyuOimsrTKLUMEorQexp/aPQeA==", + "dev": true, + "license": "MIT", + "dependencies": { + "md5.js": "^1.3.4", + "safe-buffer": "^5.1.1" + } + }, "node_modules/exsolve": { "version": "1.0.5", "resolved": "https://registry.npmmirror.com/exsolve/-/exsolve-1.0.5.tgz", @@ -3497,6 +4179,22 @@ "dev": true, "license": "ISC" }, + "node_modules/for-each": { + "version": "0.3.5", + "resolved": "https://registry.npmmirror.com/for-each/-/for-each-0.3.5.tgz", + "integrity": "sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-callable": "^1.2.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/franc-min": { "version": "6.2.0", "resolved": "https://registry.npmmirror.com/franc-min/-/franc-min-6.2.0.tgz", @@ -3525,6 +4223,55 @@ "node": "^8.16.0 || ^10.6.0 || >=11.0.0" } }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmmirror.com/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmmirror.com/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/giget": { "version": "2.0.0", "resolved": "https://registry.npmmirror.com/giget/-/giget-2.0.0.tgz", @@ -3570,6 +4317,19 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmmirror.com/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/graphemer": { "version": "1.4.0", "resolved": "https://registry.npmmirror.com/graphemer/-/graphemer-1.4.0.tgz", @@ -3587,6 +4347,86 @@ "node": ">=8" } }, + "node_modules/has-property-descriptors": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hash-base": { + "version": "3.0.5", + "resolved": "https://registry.npmmirror.com/hash-base/-/hash-base-3.0.5.tgz", + "integrity": "sha512-vXm0l45VbcHEVlTCzs8M+s0VeYsB2lnlAaThoLKGXr3bE/VWDOelNUnycUPEhKEaXARL2TEFjBOyUiM6+55KBg==", + "dev": true, + "license": "MIT", + "dependencies": { + "inherits": "^2.0.4", + "safe-buffer": "^5.2.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/hash.js": { + "version": "1.1.7", + "resolved": "https://registry.npmmirror.com/hash.js/-/hash.js-1.1.7.tgz", + "integrity": "sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==", + "dev": true, + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "minimalistic-assert": "^1.0.1" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmmirror.com/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/he": { "version": "1.2.0", "resolved": "https://registry.npmmirror.com/he/-/he-1.2.0.tgz", @@ -3597,6 +4437,18 @@ "he": "bin/he" } }, + "node_modules/hmac-drbg": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/hmac-drbg/-/hmac-drbg-1.0.1.tgz", + "integrity": "sha512-Tti3gMqLdZfhOQY1Mzf/AanLiqh1WTiJgEj26ZuYQ9fbkLomzGchCws4FyrSd4VkpBfiNhaE1On+lOz894jvXg==", + "dev": true, + "license": "MIT", + "dependencies": { + "hash.js": "^1.0.3", + "minimalistic-assert": "^1.0.0", + "minimalistic-crypto-utils": "^1.0.1" + } + }, "node_modules/hookable": { "version": "5.5.3", "resolved": "https://registry.npmmirror.com/hookable/-/hookable-5.5.3.tgz", @@ -3615,6 +4467,34 @@ "url": "https://jaywcjlove.github.io/#/sponsor" } }, + "node_modules/https-browserify": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/https-browserify/-/https-browserify-1.0.0.tgz", + "integrity": "sha512-J+FkSdyD+0mA0N+81tMotaRMfSL9SGi+xpD3T6YApKsc3bGSXJlfXri3VyFOeYkfLRQisDk1W+jIFFKBeUBbBg==", + "dev": true, + "license": "MIT" + }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmmirror.com/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "BSD-3-Clause" + }, "node_modules/ignore": { "version": "5.3.2", "resolved": "https://registry.npmmirror.com/ignore/-/ignore-5.3.2.tgz", @@ -3658,6 +4538,30 @@ "node": ">=0.8.19" } }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmmirror.com/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/is-arguments": { + "version": "1.2.0", + "resolved": "https://registry.npmmirror.com/is-arguments/-/is-arguments-1.2.0.tgz", + "integrity": "sha512-7bVbi0huj/wrIAOzb8U1aszg9kdi3KN/CyU19CTI7tAoZYEZoL9yCDXpbXN+uPsuWnP02cyug1gleqq+TU+YCA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-binary-path": { "version": "2.1.0", "resolved": "https://registry.npmmirror.com/is-binary-path/-/is-binary-path-2.1.0.tgz", @@ -3671,6 +4575,35 @@ "node": ">=8" } }, + "node_modules/is-callable": { + "version": "1.2.7", + "resolved": "https://registry.npmmirror.com/is-callable/-/is-callable-1.2.7.tgz", + "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-core-module": { + "version": "2.16.1", + "resolved": "https://registry.npmmirror.com/is-core-module/-/is-core-module-2.16.1.tgz", + "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", + "dev": true, + "license": "MIT", + "dependencies": { + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmmirror.com/is-extglob/-/is-extglob-2.1.1.tgz", @@ -3681,6 +4614,25 @@ "node": ">=0.10.0" } }, + "node_modules/is-generator-function": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/is-generator-function/-/is-generator-function-1.1.0.tgz", + "integrity": "sha512-nPUB5km40q9e8UfN/Zc24eLlzdSf9OfKByBw9CIdw4H1giPMeA0OIJvbchsCu4npfI2QcMVBsGEBHKZ7wLTWmQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "get-proto": "^1.0.0", + "has-tostringtag": "^1.0.2", + "safe-regex-test": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-glob": { "version": "4.0.3", "resolved": "https://registry.npmmirror.com/is-glob/-/is-glob-4.0.3.tgz", @@ -3694,6 +4646,23 @@ "node": ">=0.10.0" } }, + "node_modules/is-nan": { + "version": "1.3.2", + "resolved": "https://registry.npmmirror.com/is-nan/-/is-nan-1.3.2.tgz", + "integrity": "sha512-E+zBKpQ2t6MEo1VsonYmluk9NxGrbzpeeLC2xIViuO2EjU2xsXsBPwTr3Ykv9l08UYEVEdWeRZNouaZqF6RN0w==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.0", + "define-properties": "^1.1.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-number": { "version": "7.0.0", "resolved": "https://registry.npmmirror.com/is-number/-/is-number-7.0.0.tgz", @@ -3704,6 +4673,41 @@ "node": ">=0.12.0" } }, + "node_modules/is-regex": { + "version": "1.2.1", + "resolved": "https://registry.npmmirror.com/is-regex/-/is-regex-1.2.1.tgz", + "integrity": "sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "gopd": "^1.2.0", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-typed-array": { + "version": "1.1.15", + "resolved": "https://registry.npmmirror.com/is-typed-array/-/is-typed-array-1.1.15.tgz", + "integrity": "sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "which-typed-array": "^1.1.16" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-what": { "version": "4.1.16", "resolved": "https://registry.npmmirror.com/is-what/-/is-what-4.1.16.tgz", @@ -3716,6 +4720,13 @@ "url": "https://github.com/sponsors/mesqueeb" } }, + "node_modules/isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmmirror.com/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", + "dev": true, + "license": "MIT" + }, "node_modules/isexe": { "version": "2.0.0", "resolved": "https://registry.npmmirror.com/isexe/-/isexe-2.0.0.tgz", @@ -3723,6 +4734,16 @@ "dev": true, "license": "ISC" }, + "node_modules/isomorphic-timers-promises": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/isomorphic-timers-promises/-/isomorphic-timers-promises-1.0.1.tgz", + "integrity": "sha512-u4sej9B1LPSxTGKB/HiuzvEQnXH0ECYkSVQU39koSwmFAxhlEAFl9RdTvLv4TOTQUgBS5O3O5fwUxk6byBZ+IQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + } + }, "node_modules/jiti": { "version": "2.4.2", "resolved": "https://registry.npmmirror.com/jiti/-/jiti-2.4.2.tgz", @@ -3776,6 +4797,15 @@ "dev": true, "license": "MIT" }, + "node_modules/jsox": { + "version": "1.2.123", + "resolved": "https://registry.npmmirror.com/jsox/-/jsox-1.2.123.tgz", + "integrity": "sha512-LYordXJ/0Q4G8pUE1Pvh4fkfGvZY7lRe4WIJKl0wr0rtFDVw9lcdNW95GH0DceJ6E9xh41zJNW0vreEz7xOxCw==", + "license": "MIT", + "bin": { + "jsox": "lib/cli.js" + } + }, "node_modules/keyv": { "version": "4.5.4", "resolved": "https://registry.npmmirror.com/keyv/-/keyv-4.5.4.tgz", @@ -3845,6 +4875,12 @@ "deprecated": "This package has been replaced by @lezer/common", "license": "MIT" }, + "node_modules/linguist-languages": { + "version": "9.0.0", + "resolved": "https://registry.npmmirror.com/linguist-languages/-/linguist-languages-9.0.0.tgz", + "integrity": "sha512-8sexSk1Qotkqy9l4J8X/EDRLvrB8QrYjx41YI3Qr7PrvMqdfYb0zKUI6AOhfnjD/uaDHE87s9Nbu9zWikcGRVw==", + "license": "MIT" + }, "node_modules/local-pkg": { "version": "1.1.1", "resolved": "https://registry.npmmirror.com/local-pkg/-/local-pkg-1.1.1.tgz", @@ -3895,6 +4931,28 @@ "@jridgewell/sourcemap-codec": "^1.5.5" } }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/md5.js": { + "version": "1.3.5", + "resolved": "https://registry.npmmirror.com/md5.js/-/md5.js-1.3.5.tgz", + "integrity": "sha512-xitP+WxNPcTTOgnTJcrhM0xvdPepipPSf3I8EIpGKeFLjt3PlJLIDG3u8EX53ZIubkb+5U2+3rELYpEhHhzdkg==", + "dev": true, + "license": "MIT", + "dependencies": { + "hash-base": "^3.0.0", + "inherits": "^2.0.1", + "safe-buffer": "^5.1.2" + } + }, "node_modules/merge2": { "version": "1.4.1", "resolved": "https://registry.npmmirror.com/merge2/-/merge2-1.4.1.tgz", @@ -3919,6 +4977,41 @@ "node": ">=8.6" } }, + "node_modules/miller-rabin": { + "version": "4.0.1", + "resolved": "https://registry.npmmirror.com/miller-rabin/-/miller-rabin-4.0.1.tgz", + "integrity": "sha512-115fLhvZVqWwHPbClyntxEVfVDfl9DLLTuJvq3g2O/Oxi8AiNouAHvDSzHS0viUJc+V5vm3eq91Xwqn9dp4jRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "bn.js": "^4.0.0", + "brorand": "^1.0.1" + }, + "bin": { + "miller-rabin": "bin/miller-rabin" + } + }, + "node_modules/miller-rabin/node_modules/bn.js": { + "version": "4.12.2", + "resolved": "https://registry.npmmirror.com/bn.js/-/bn.js-4.12.2.tgz", + "integrity": "sha512-n4DSx829VRTRByMRGdjQ9iqsN0Bh4OolPsFnaZBLcbi8iXcB+kJ9s7EnRt4wILZNV3kPLHkRVfOc/HvhC3ovDw==", + "dev": true, + "license": "MIT" + }, + "node_modules/minimalistic-assert": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", + "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==", + "dev": true, + "license": "ISC" + }, + "node_modules/minimalistic-crypto-utils": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz", + "integrity": "sha512-JIYlbt6g8i5jKfJ3xz7rF0LXmv2TkDxBLUkiBeZ7bAx4GnnNMr8xFpGnOxn6GhTEHx3SjRrZEoU+j04prX1ktg==", + "dev": true, + "license": "MIT" + }, "node_modules/minimatch": { "version": "3.1.2", "resolved": "https://registry.npmmirror.com/minimatch/-/minimatch-3.1.2.tgz", @@ -4062,6 +5155,65 @@ "optional": true, "peer": true }, + "node_modules/node-sql-parser": { + "version": "5.3.12", + "resolved": "https://registry.npmmirror.com/node-sql-parser/-/node-sql-parser-5.3.12.tgz", + "integrity": "sha512-GQBwA2e44qjbK0MzFwh5bNYefniV6cKT4KfjNDpuh/2EWipUEK1BCMc//moSidp8EF6fHB/EqJwUH9GCh9MAPg==", + "license": "Apache-2.0", + "dependencies": { + "@types/pegjs": "^0.10.0", + "big-integer": "^1.6.48" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/node-stdlib-browser": { + "version": "1.3.1", + "resolved": "https://registry.npmmirror.com/node-stdlib-browser/-/node-stdlib-browser-1.3.1.tgz", + "integrity": "sha512-X75ZN8DCLftGM5iKwoYLA3rjnrAEs97MkzvSd4q2746Tgpg8b8XWiBGiBG4ZpgcAqBgtgPHTiAc8ZMCvZuikDw==", + "dev": true, + "license": "MIT", + "dependencies": { + "assert": "^2.0.0", + "browser-resolve": "^2.0.0", + "browserify-zlib": "^0.2.0", + "buffer": "^5.7.1", + "console-browserify": "^1.1.0", + "constants-browserify": "^1.0.0", + "create-require": "^1.1.1", + "crypto-browserify": "^3.12.1", + "domain-browser": "4.22.0", + "events": "^3.0.0", + "https-browserify": "^1.0.0", + "isomorphic-timers-promises": "^1.0.1", + "os-browserify": "^0.3.0", + "path-browserify": "^1.0.1", + "pkg-dir": "^5.0.0", + "process": "^0.11.10", + "punycode": "^1.4.1", + "querystring-es3": "^0.2.1", + "readable-stream": "^3.6.0", + "stream-browserify": "^3.0.0", + "stream-http": "^3.2.0", + "string_decoder": "^1.0.0", + "timers-browserify": "^2.0.4", + "tty-browserify": "0.0.1", + "url": "^0.11.4", + "util": "^0.12.4", + "vm-browserify": "^1.0.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/node-stdlib-browser/node_modules/punycode": { + "version": "1.4.1", + "resolved": "https://registry.npmmirror.com/punycode/-/punycode-1.4.1.tgz", + "integrity": "sha512-jmYNElW7yvO7TV33CjSmvSiE2yco3bV2czu/OzDKdMNVZQWfxCblURLhf+47syQRBntjfLdd/H0egrzIG+oaFQ==", + "dev": true, + "license": "MIT" + }, "node_modules/normalize-path": { "version": "3.0.0", "resolved": "https://registry.npmmirror.com/normalize-path/-/normalize-path-3.0.0.tgz", @@ -4106,6 +5258,67 @@ "node": "^14.16.0 || >=16.10.0" } }, + "node_modules/object-inspect": { + "version": "1.13.4", + "resolved": "https://registry.npmmirror.com/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object-is": { + "version": "1.1.6", + "resolved": "https://registry.npmmirror.com/object-is/-/object-is-1.1.6.tgz", + "integrity": "sha512-F8cZ+KfGlSGi09lJT7/Nd6KJZ9ygtvYC0/UYYLI9nmQKLMnydpB9yvbv9K1uSkEu7FU9vYPmVwLg328tX+ot3Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmmirror.com/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.assign": { + "version": "4.1.7", + "resolved": "https://registry.npmmirror.com/object.assign/-/object.assign-4.1.7.tgz", + "integrity": "sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0", + "has-symbols": "^1.1.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/ohash": { "version": "2.0.11", "resolved": "https://registry.npmmirror.com/ohash/-/ohash-2.0.11.tgz", @@ -4132,6 +5345,13 @@ "node": ">= 0.8.0" } }, + "node_modules/os-browserify": { + "version": "0.3.0", + "resolved": "https://registry.npmmirror.com/os-browserify/-/os-browserify-0.3.0.tgz", + "integrity": "sha512-gjcpUc3clBf9+210TRaDWbf+rZZZEshZ+DlXMRCeAjp0xhTrnQsKHypIy1J3d5hKdUzj69t708EHtU8P6bUn0A==", + "dev": true, + "license": "MIT" + }, "node_modules/p-limit": { "version": "3.1.0", "resolved": "https://registry.npmmirror.com/p-limit/-/p-limit-3.1.0.tgz", @@ -4164,6 +5384,13 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/pako": { + "version": "1.0.11", + "resolved": "https://registry.npmmirror.com/pako/-/pako-1.0.11.tgz", + "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==", + "dev": true, + "license": "(MIT AND Zlib)" + }, "node_modules/parent-module": { "version": "1.0.1", "resolved": "https://registry.npmmirror.com/parent-module/-/parent-module-1.0.1.tgz", @@ -4177,6 +5404,24 @@ "node": ">=6" } }, + "node_modules/parse-asn1": { + "version": "5.1.7", + "resolved": "https://registry.npmmirror.com/parse-asn1/-/parse-asn1-5.1.7.tgz", + "integrity": "sha512-CTM5kuWR3sx9IFamcl5ErfPl6ea/N8IYwiJ+vpeB2g+1iknv7zBl5uPwbMbRVznRVbrNY6lGuDoE5b30grmbqg==", + "dev": true, + "license": "ISC", + "dependencies": { + "asn1.js": "^4.10.1", + "browserify-aes": "^1.2.0", + "evp_bytestokey": "^1.0.3", + "hash-base": "~3.0", + "pbkdf2": "^3.1.2", + "safe-buffer": "^5.2.1" + }, + "engines": { + "node": ">= 0.10" + } + }, "node_modules/path-browserify": { "version": "1.0.1", "resolved": "https://registry.npmmirror.com/path-browserify/-/path-browserify-1.0.1.tgz", @@ -4204,6 +5449,13 @@ "node": ">=8" } }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmmirror.com/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true, + "license": "MIT" + }, "node_modules/pathe": { "version": "2.0.3", "resolved": "https://registry.npmmirror.com/pathe/-/pathe-2.0.3.tgz", @@ -4211,12 +5463,70 @@ "devOptional": true, "license": "MIT" }, + "node_modules/pbkdf2": { + "version": "3.1.3", + "resolved": "https://registry.npmmirror.com/pbkdf2/-/pbkdf2-3.1.3.tgz", + "integrity": "sha512-wfRLBZ0feWRhCIkoMB6ete7czJcnNnqRpcoWQBLqatqXXmelSRqfdDK4F3u9T2s2cXas/hQJcryI/4lAL+XTlA==", + "dev": true, + "license": "MIT", + "dependencies": { + "create-hash": "~1.1.3", + "create-hmac": "^1.1.7", + "ripemd160": "=2.0.1", + "safe-buffer": "^5.2.1", + "sha.js": "^2.4.11", + "to-buffer": "^1.2.0" + }, + "engines": { + "node": ">=0.12" + } + }, + "node_modules/pbkdf2/node_modules/create-hash": { + "version": "1.1.3", + "resolved": "https://registry.npmmirror.com/create-hash/-/create-hash-1.1.3.tgz", + "integrity": "sha512-snRpch/kwQhcdlnZKYanNF1m0RDlrCdSKQaH87w1FCFPVPNCQ/Il9QJKAX2jVBZddRdaHBMC+zXa9Gw9tmkNUA==", + "dev": true, + "license": "MIT", + "dependencies": { + "cipher-base": "^1.0.1", + "inherits": "^2.0.1", + "ripemd160": "^2.0.0", + "sha.js": "^2.4.0" + } + }, + "node_modules/pbkdf2/node_modules/hash-base": { + "version": "2.0.2", + "resolved": "https://registry.npmmirror.com/hash-base/-/hash-base-2.0.2.tgz", + "integrity": "sha512-0TROgQ1/SxE6KmxWSvXHvRj90/Xo1JvZShofnYF+f6ZsGtR4eES7WfrQzPalmyagfKZCXpVnitiRebZulWsbiw==", + "dev": true, + "license": "MIT", + "dependencies": { + "inherits": "^2.0.1" + } + }, + "node_modules/pbkdf2/node_modules/ripemd160": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/ripemd160/-/ripemd160-2.0.1.tgz", + "integrity": "sha512-J7f4wutN8mdbV08MJnXibYpCOPHR+yzy+iQ/AsjMv2j8cLavQ8VGagDFUwwTAdF8FmRKVeNpbTTEwNHCW1g94w==", + "dev": true, + "license": "MIT", + "dependencies": { + "hash-base": "^2.0.0", + "inherits": "^2.0.1" + } + }, "node_modules/perfect-debounce": { "version": "1.0.0", "resolved": "https://registry.npmmirror.com/perfect-debounce/-/perfect-debounce-1.0.0.tgz", "integrity": "sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA==", "license": "MIT" }, + "node_modules/php-parser": { + "version": "3.2.5", + "resolved": "https://registry.npmmirror.com/php-parser/-/php-parser-3.2.5.tgz", + "integrity": "sha512-M1ZYlALFFnESbSdmRtTQrBFUHSriHgPhgqtTF/LCbZM4h7swR5PHtUceB2Kzby5CfqcsYwBn7OXTJ0+8Sajwkw==", + "license": "BSD-3-Clause" + }, "node_modules/picocolors": { "version": "1.1.1", "resolved": "https://registry.npmmirror.com/picocolors/-/picocolors-1.1.1.tgz", @@ -4284,6 +5594,19 @@ } } }, + "node_modules/pkg-dir": { + "version": "5.0.0", + "resolved": "https://registry.npmmirror.com/pkg-dir/-/pkg-dir-5.0.0.tgz", + "integrity": "sha512-NPE8TDbzl/3YQYY7CSS228s3g2ollTFnc+Qi3tqmqJp9Vg2ovUpixcJEo2HJScN2Ez+kEaal6y70c0ehqJBJeA==", + "dev": true, + "license": "MIT", + "dependencies": { + "find-up": "^5.0.0" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/pkg-types": { "version": "2.1.0", "resolved": "https://registry.npmmirror.com/pkg-types/-/pkg-types-2.1.0.tgz", @@ -4296,6 +5619,16 @@ "pathe": "^2.0.3" } }, + "node_modules/possible-typed-array-names": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz", + "integrity": "sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, "node_modules/postcss": { "version": "8.5.6", "resolved": "https://registry.npmmirror.com/postcss/-/postcss-8.5.6.tgz", @@ -4363,6 +5696,45 @@ "url": "https://github.com/prettier/prettier?sponsor=1" } }, + "node_modules/process": { + "version": "0.11.10", + "resolved": "https://registry.npmmirror.com/process/-/process-0.11.10.tgz", + "integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6.0" + } + }, + "node_modules/process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", + "dev": true, + "license": "MIT" + }, + "node_modules/public-encrypt": { + "version": "4.0.3", + "resolved": "https://registry.npmmirror.com/public-encrypt/-/public-encrypt-4.0.3.tgz", + "integrity": "sha512-zVpa8oKZSz5bTMTFClc1fQOnyyEzpl5ozpi1B5YcvBrdohMjH2rfsBtyXcuNuwjsDIXmBYlF2N5FlJYhR29t8Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "bn.js": "^4.1.0", + "browserify-rsa": "^4.0.0", + "create-hash": "^1.1.0", + "parse-asn1": "^5.0.0", + "randombytes": "^2.0.1", + "safe-buffer": "^5.1.2" + } + }, + "node_modules/public-encrypt/node_modules/bn.js": { + "version": "4.12.2", + "resolved": "https://registry.npmmirror.com/bn.js/-/bn.js-4.12.2.tgz", + "integrity": "sha512-n4DSx829VRTRByMRGdjQ9iqsN0Bh4OolPsFnaZBLcbi8iXcB+kJ9s7EnRt4wILZNV3kPLHkRVfOc/HvhC3ovDw==", + "dev": true, + "license": "MIT" + }, "node_modules/punycode": { "version": "2.3.1", "resolved": "https://registry.npmmirror.com/punycode/-/punycode-2.3.1.tgz", @@ -4373,6 +5745,22 @@ "node": ">=6" } }, + "node_modules/qs": { + "version": "6.14.0", + "resolved": "https://registry.npmmirror.com/qs/-/qs-6.14.0.tgz", + "integrity": "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/quansync": { "version": "0.2.10", "resolved": "https://registry.npmmirror.com/quansync/-/quansync-0.2.10.tgz", @@ -4390,6 +5778,15 @@ ], "license": "MIT" }, + "node_modules/querystring-es3": { + "version": "0.2.1", + "resolved": "https://registry.npmmirror.com/querystring-es3/-/querystring-es3-0.2.1.tgz", + "integrity": "sha512-773xhDQnZBMFobEiztv8LIl70ch5MSF/jUQVlhwFyBILqq96anmoctVIYz+ZRp0qbCKATTn6ev02M3r7Ga5vqA==", + "dev": true, + "engines": { + "node": ">=0.4.x" + } + }, "node_modules/queue-microtask": { "version": "1.2.3", "resolved": "https://registry.npmmirror.com/queue-microtask/-/queue-microtask-1.2.3.tgz", @@ -4430,6 +5827,27 @@ "node": ">=0.12" } }, + "node_modules/randombytes": { + "version": "2.1.0", + "resolved": "https://registry.npmmirror.com/randombytes/-/randombytes-2.1.0.tgz", + "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "safe-buffer": "^5.1.0" + } + }, + "node_modules/randomfill": { + "version": "1.0.4", + "resolved": "https://registry.npmmirror.com/randomfill/-/randomfill-1.0.4.tgz", + "integrity": "sha512-87lcbR8+MhcWcUiQ+9e+Rwx8MyR2P7qnt15ynUlbm3TU/fjbgz4GsvfSUDTemtCCtVCqb4ZcEFlyPNTh9bBTLw==", + "dev": true, + "license": "MIT", + "dependencies": { + "randombytes": "^2.0.5", + "safe-buffer": "^5.1.0" + } + }, "node_modules/rc9": { "version": "2.1.2", "resolved": "https://registry.npmmirror.com/rc9/-/rc9-2.1.2.tgz", @@ -4442,6 +5860,21 @@ "destr": "^2.0.3" } }, + "node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmmirror.com/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dev": true, + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/readdirp": { "version": "4.1.2", "resolved": "https://registry.npmmirror.com/readdirp/-/readdirp-4.1.2.tgz", @@ -4480,6 +5913,27 @@ "sprintf-js": "~1.0.2" } }, + "node_modules/resolve": { + "version": "1.22.10", + "resolved": "https://registry.npmmirror.com/resolve/-/resolve-1.22.10.tgz", + "integrity": "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-core-module": "^2.16.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/resolve-from": { "version": "4.0.0", "resolved": "https://registry.npmmirror.com/resolve-from/-/resolve-from-4.0.0.tgz", @@ -4516,6 +5970,17 @@ "integrity": "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==", "license": "MIT" }, + "node_modules/ripemd160": { + "version": "2.0.2", + "resolved": "https://registry.npmmirror.com/ripemd160/-/ripemd160-2.0.2.tgz", + "integrity": "sha512-ii4iagi25WusVoiC4B4lq7pbXfAp3D9v5CwfkY33vffw2+pkDjY1D8GaN7spsxvCSx8dkPqOZCEZyfxcmJG2IA==", + "dev": true, + "license": "MIT", + "dependencies": { + "hash-base": "^3.0.0", + "inherits": "^2.0.1" + } + }, "node_modules/rollup": { "version": "4.46.2", "resolved": "https://registry.npmmirror.com/rollup/-/rollup-4.46.2.tgz", @@ -4580,6 +6045,45 @@ "queue-microtask": "^1.2.2" } }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmmirror.com/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/safe-regex-test": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/safe-regex-test/-/safe-regex-test-1.1.0.tgz", + "integrity": "sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "is-regex": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/sass": { "version": "1.92.1", "resolved": "https://registry.npmmirror.com/sass/-/sass-1.92.1.tgz", @@ -4621,6 +6125,52 @@ "node": ">=10" } }, + "node_modules/set-function-length": { + "version": "1.2.2", + "resolved": "https://registry.npmmirror.com/set-function-length/-/set-function-length-1.2.2.tgz", + "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/setimmediate": { + "version": "1.0.5", + "resolved": "https://registry.npmmirror.com/setimmediate/-/setimmediate-1.0.5.tgz", + "integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==", + "dev": true, + "license": "MIT" + }, + "node_modules/sha.js": { + "version": "2.4.12", + "resolved": "https://registry.npmmirror.com/sha.js/-/sha.js-2.4.12.tgz", + "integrity": "sha512-8LzC5+bvI45BjpfXU8V5fdU2mfeKiQe1D1gIMn7XUlF3OTUrpdJpPPH4EMAnF0DsHHdSZqCdSss5qCmJKuiO3w==", + "dev": true, + "license": "(MIT AND BSD-3-Clause)", + "dependencies": { + "inherits": "^2.0.4", + "safe-buffer": "^5.2.1", + "to-buffer": "^1.2.0" + }, + "bin": { + "sha.js": "bin.js" + }, + "engines": { + "node": ">= 0.10" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmmirror.com/shebang-command/-/shebang-command-2.0.0.tgz", @@ -4644,6 +6194,82 @@ "node": ">=8" } }, + "node_modules/side-channel": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/side-channel/-/side-channel-1.1.0.tgz", + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3", + "side-channel-list": "^1.0.0", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/side-channel-list/-/side-channel-list-1.0.0.tgz", + "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/source-map-js": { "version": "1.2.1", "resolved": "https://registry.npmmirror.com/source-map-js/-/source-map-js-1.2.1.tgz", @@ -4689,6 +6315,40 @@ "optional": true, "peer": true }, + "node_modules/stream-browserify": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/stream-browserify/-/stream-browserify-3.0.0.tgz", + "integrity": "sha512-H73RAHsVBapbim0tU2JwwOiXUj+fikfiaoYAKHF3VJfA0pe2BCzkhAHBlLG6REzE+2WNZcxOXjK7lkso+9euLA==", + "dev": true, + "license": "MIT", + "dependencies": { + "inherits": "~2.0.4", + "readable-stream": "^3.5.0" + } + }, + "node_modules/stream-http": { + "version": "3.2.0", + "resolved": "https://registry.npmmirror.com/stream-http/-/stream-http-3.2.0.tgz", + "integrity": "sha512-Oq1bLqisTyK3TSCXpPbT4sdeYNdmyZJv1LxpEm2vu1ZhK89kSE5YXwZc3cWk0MagGaKriBh9mCFbVGtO+vY29A==", + "dev": true, + "license": "MIT", + "dependencies": { + "builtin-status-codes": "^3.0.0", + "inherits": "^2.0.4", + "readable-stream": "^3.6.0", + "xtend": "^4.0.2" + } + }, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmmirror.com/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "dev": true, + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, "node_modules/strip-json-comments": { "version": "3.1.1", "resolved": "https://registry.npmmirror.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz", @@ -4747,6 +6407,32 @@ "node": ">=8" } }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/timers-browserify": { + "version": "2.0.12", + "resolved": "https://registry.npmmirror.com/timers-browserify/-/timers-browserify-2.0.12.tgz", + "integrity": "sha512-9phl76Cqm6FhSX9Xe1ZUAMLtm1BLkKj2Qd5ApyWkXzsMRaA7dgr81kf4wJmQf/hAvg8EEyJxDo3du/0KlhPiKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "setimmediate": "^1.0.4" + }, + "engines": { + "node": ">=0.6.0" + } + }, "node_modules/tinyexec": { "version": "0.3.2", "resolved": "https://registry.npmmirror.com/tinyexec/-/tinyexec-0.3.2.tgz", @@ -4803,6 +6489,21 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, + "node_modules/to-buffer": { + "version": "1.2.1", + "resolved": "https://registry.npmmirror.com/to-buffer/-/to-buffer-1.2.1.tgz", + "integrity": "sha512-tB82LpAIWjhLYbqjx3X4zEeHN6M8CiuOEy2JY8SEQVdYRe3CCHOFaqrBW1doLDrfpWhplcW7BL+bO3/6S3pcDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "isarray": "^2.0.5", + "safe-buffer": "^5.2.1", + "typed-array-buffer": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmmirror.com/to-regex-range/-/to-regex-range-5.0.1.tgz", @@ -4849,6 +6550,13 @@ "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", "license": "0BSD" }, + "node_modules/tty-browserify": { + "version": "0.0.1", + "resolved": "https://registry.npmmirror.com/tty-browserify/-/tty-browserify-0.0.1.tgz", + "integrity": "sha512-C3TaO7K81YvjCgQH9Q1S3R3P3BtN3RIM8n+OvX4il1K1zgE8ZhI0op7kClgkxtutIE8hQrcrHBXvIheqKUUCxw==", + "dev": true, + "license": "MIT" + }, "node_modules/type-check": { "version": "0.4.0", "resolved": "https://registry.npmmirror.com/type-check/-/type-check-0.4.0.tgz", @@ -4862,6 +6570,21 @@ "node": ">= 0.8.0" } }, + "node_modules/typed-array-buffer": { + "version": "1.0.3", + "resolved": "https://registry.npmmirror.com/typed-array-buffer/-/typed-array-buffer-1.0.3.tgz", + "integrity": "sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-typed-array": "^1.1.14" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/typescript": { "version": "5.9.2", "resolved": "https://registry.npmmirror.com/typescript/-/typescript-5.9.2.tgz", @@ -5178,6 +6901,41 @@ "punycode": "^2.1.0" } }, + "node_modules/url": { + "version": "0.11.4", + "resolved": "https://registry.npmmirror.com/url/-/url-0.11.4.tgz", + "integrity": "sha512-oCwdVC7mTuWiPyjLUz/COz5TLk6wgp0RCsN+wHZ2Ekneac9w8uuV0njcbbie2ME+Vs+d6duwmYuR3HgQXs1fOg==", + "dev": true, + "license": "MIT", + "dependencies": { + "punycode": "^1.4.1", + "qs": "^6.12.3" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/url/node_modules/punycode": { + "version": "1.4.1", + "resolved": "https://registry.npmmirror.com/punycode/-/punycode-1.4.1.tgz", + "integrity": "sha512-jmYNElW7yvO7TV33CjSmvSiE2yco3bV2czu/OzDKdMNVZQWfxCblURLhf+47syQRBntjfLdd/H0egrzIG+oaFQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/util": { + "version": "0.12.5", + "resolved": "https://registry.npmmirror.com/util/-/util-0.12.5.tgz", + "integrity": "sha512-kZf/K6hEIrWHI6XqOFUiiMa+79wE/D8Q+NCNAWclkyg3b4d2k7s0QGepNjiABc+aR3N1PAyHL7p6UcLY6LmrnA==", + "dev": true, + "license": "MIT", + "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" + } + }, "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmmirror.com/util-deprecate/-/util-deprecate-1.0.2.tgz", @@ -5260,6 +7018,23 @@ } } }, + "node_modules/vite-plugin-node-polyfills": { + "version": "0.24.0", + "resolved": "https://registry.npmmirror.com/vite-plugin-node-polyfills/-/vite-plugin-node-polyfills-0.24.0.tgz", + "integrity": "sha512-GA9QKLH+vIM8NPaGA+o2t8PDfFUl32J8rUp1zQfMKVJQiNkOX4unE51tR6ppl6iKw5yOrDAdSH7r/UIFLCVhLw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@rollup/plugin-inject": "^5.0.5", + "node-stdlib-browser": "^1.2.0" + }, + "funding": { + "url": "https://github.com/sponsors/davidmyersdev" + }, + "peerDependencies": { + "vite": "^2.0.0 || ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" + } + }, "node_modules/vite/node_modules/fdir": { "version": "6.5.0", "resolved": "https://registry.npmmirror.com/fdir/-/fdir-6.5.0.tgz", @@ -5291,6 +7066,13 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, + "node_modules/vm-browserify": { + "version": "1.1.2", + "resolved": "https://registry.npmmirror.com/vm-browserify/-/vm-browserify-1.1.2.tgz", + "integrity": "sha512-2ham8XPWTONajOR0ohOKOHXkm3+gaBmGut3SRuu75xLd/RRaY6vqgh8NBYYk7+RW3u5AtzPQZG8F10LHkl0lAQ==", + "dev": true, + "license": "MIT" + }, "node_modules/vscode-uri": { "version": "3.1.0", "resolved": "https://registry.npmmirror.com/vscode-uri/-/vscode-uri-3.1.0.tgz", @@ -5449,6 +7231,28 @@ "node": ">= 8" } }, + "node_modules/which-typed-array": { + "version": "1.1.19", + "resolved": "https://registry.npmmirror.com/which-typed-array/-/which-typed-array-1.1.19.tgz", + "integrity": "sha512-rEvr90Bck4WZt9HHFC4DJMsjvu7x+r6bImz0/BrbWb7A2djJ8hnZMrWnHo9F8ssv0OMErasDhftrfROTyqSDrw==", + "dev": true, + "license": "MIT", + "dependencies": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "for-each": "^0.3.5", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/word-wrap": { "version": "1.2.5", "resolved": "https://registry.npmmirror.com/word-wrap/-/word-wrap-1.2.5.tgz", @@ -5469,6 +7273,16 @@ "node": ">=12" } }, + "node_modules/xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmmirror.com/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.4" + } + }, "node_modules/yocto-queue": { "version": "0.1.0", "resolved": "https://registry.npmmirror.com/yocto-queue/-/yocto-queue-0.1.0.tgz", diff --git a/frontend/package.json b/frontend/package.json index f315702..f782c0e 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -49,7 +49,11 @@ "colors-named-hex": "^1.0.2", "franc-min": "^6.2.0", "hsl-matcher": "^1.2.4", + "jsox": "^1.2.123", "lezer": "^0.13.5", + "linguist-languages": "^9.0.0", + "node-sql-parser": "^5.3.12", + "php-parser": "^3.2.5", "pinia": "^3.0.3", "pinia-plugin-persistedstate": "^4.5.0", "prettier": "^3.6.2", @@ -75,6 +79,7 @@ "typescript-eslint": "^8.43.0", "unplugin-vue-components": "^29.0.0", "vite": "^7.1.5", + "vite-plugin-node-polyfills": "^0.24.0", "vue-eslint-parser": "^10.2.0", "vue-tsc": "^3.0.6" } diff --git a/frontend/src/utils/prettier/plugins/php/index.d.ts b/frontend/src/utils/prettier/plugins/php/index.d.ts new file mode 100644 index 0000000..462b5f1 --- /dev/null +++ b/frontend/src/utils/prettier/plugins/php/index.d.ts @@ -0,0 +1,4 @@ +import type { Plugin } from "prettier"; + +declare const plugin: Plugin; +export default plugin; diff --git a/frontend/src/utils/prettier/plugins/php/index.mjs b/frontend/src/utils/prettier/plugins/php/index.mjs new file mode 100644 index 0000000..c69824e --- /dev/null +++ b/frontend/src/utils/prettier/plugins/php/index.mjs @@ -0,0 +1,19 @@ +export { + languages, + printers, + parsers, + options, + defaultOptions +} from './src/index.mjs'; + +import { languages, printers, parsers, options, defaultOptions } from './src/index.mjs'; + +const phpPlugin = { + languages, + printers, + parsers, + options, + defaultOptions +}; + +export default phpPlugin; diff --git a/frontend/src/utils/prettier/plugins/php/src/clean.mjs b/frontend/src/utils/prettier/plugins/php/src/clean.mjs new file mode 100644 index 0000000..3622417 --- /dev/null +++ b/frontend/src/utils/prettier/plugins/php/src/clean.mjs @@ -0,0 +1,111 @@ +import { printNumber, normalizeMagicMethodName } from "./util.mjs"; + +const ignoredProperties = new Set([ + "loc", + "range", + "raw", + "comments", + "leadingComments", + "trailingComments", + "parenthesizedExpression", + "parent", + "prev", + "start", + "end", + "tokens", + "errors", + "extra", +]); + +/** + * This function takes the existing ast node and a copy, by reference + * We use it for testing, so that we can compare pre-post versions of the AST, + * excluding things we don't care about (like node location, case that will be + * changed by the printer, etc.) + */ +function clean(node, newObj) { + if (node.kind === "string") { + // TODO if options are available in this method, replace with + // newObj.isDoubleQuote = !useSingleQuote(node, options); + delete newObj.isDoubleQuote; + } + + if (["array", "list"].includes(node.kind)) { + // TODO if options are available in this method, assign instead of delete + delete newObj.shortForm; + } + + if (node.kind === "inline") { + if (node.value.includes("___PSEUDO_INLINE_PLACEHOLDER___")) { + return null; + } + + newObj.value = newObj.value.replace(/\n/g, ""); + } + + // continue ((2)); -> continue 2; + // continue 1; -> continue; + if ((node.kind === "continue" || node.kind === "break") && node.level) { + const { level } = newObj; + + if (level.kind === "number") { + newObj.level = level.value === "1" ? null : level; + } + } + + // if () {{ }} -> if () {} + if (node.kind === "block") { + if (node.children.length === 1 && node.children[0].kind === "block") { + while (newObj.children[0].kind === "block") { + newObj.children = newObj.children[0].children; + } + } + } + + // Normalize numbers + if (node.kind === "number") { + newObj.value = printNumber(node.value); + } + + const statements = ["foreach", "for", "if", "while", "do"]; + + if (statements.includes(node.kind)) { + if (node.body && node.body.kind !== "block") { + newObj.body = { + kind: "block", + children: [newObj.body], + }; + } else { + newObj.body = newObj.body ? newObj.body : null; + } + + if (node.alternate && node.alternate.kind !== "block") { + newObj.alternate = { + kind: "block", + children: [newObj.alternate], + }; + } else { + newObj.alternate = newObj.alternate ? newObj.alternate : null; + } + } + + if (node.kind === "usegroup" && typeof node.name === "string") { + newObj.name = newObj.name.replace(/^\\/, ""); + } + + if (node.kind === "useitem") { + newObj.name = newObj.name.replace(/^\\/, ""); + } + + if (node.kind === "method" && node.name.kind === "identifier") { + newObj.name.name = normalizeMagicMethodName(newObj.name.name); + } + + if (node.kind === "noop") { + return null; + } +} + +clean.ignoredProperties = ignoredProperties; + +export default clean; diff --git a/frontend/src/utils/prettier/plugins/php/src/comments.mjs b/frontend/src/utils/prettier/plugins/php/src/comments.mjs new file mode 100644 index 0000000..828fc01 --- /dev/null +++ b/frontend/src/utils/prettier/plugins/php/src/comments.mjs @@ -0,0 +1,1044 @@ +import { util as prettierUtil, doc } from "prettier"; +import { isLookupNode } from "./util.mjs"; +import { locStart, locEnd } from "./loc.mjs"; + +const { + addLeadingComment, + addDanglingComment, + addTrailingComment, + skipNewline, + hasNewline, + hasNewlineInRange, + getNextNonSpaceNonCommentCharacterIndex, + isNextLineEmpty, + isPreviousLineEmpty, +} = prettierUtil; +const { join, indent, hardline, cursor, lineSuffix, breakParent } = + doc.builders; + +/* +Comment functions are meant to inspect various edge cases using given comment nodes, +with information about where those comment nodes exist in the tree (ie enclosingNode, +previousNode, followingNode), and then either call the built in functions to handle +certain cases (ie addLeadingComment, addTrailingComment, addDanglingComment), or just +let prettier core handle them. To signal that the plugin is taking over, the comment +handler function should return true, otherwise returning false signals that prettier +core should handle the comment + +args: + comment + text + options + ast + isLastComment +*/ + +function handleOwnLineComment(comment, text, options) { + const { precedingNode, enclosingNode, followingNode } = comment; + return ( + handleLastFunctionArgComments( + text, + precedingNode, + enclosingNode, + followingNode, + comment, + options + ) || + handleMemberExpressionComments(enclosingNode, followingNode, comment) || + handleIfStatementComments( + text, + precedingNode, + enclosingNode, + followingNode, + comment, + options + ) || + handleWhileComments( + text, + precedingNode, + enclosingNode, + followingNode, + comment, + options + ) || + handleTryComments(enclosingNode, followingNode, comment) || + handleClassComments(enclosingNode, followingNode, comment) || + handleFunctionParameter( + text, + precedingNode, + enclosingNode, + followingNode, + comment + ) || + handleFunction(text, enclosingNode, followingNode, comment, options) || + handleForComments(enclosingNode, precedingNode, followingNode, comment) || + handleInlineComments( + enclosingNode, + precedingNode, + followingNode, + comment + ) || + handleDeclareComments(enclosingNode, precedingNode, followingNode, comment) + ); +} + +function handleEndOfLineComment(comment, text, options) { + const { precedingNode, enclosingNode, followingNode } = comment; + return ( + handleArrayComments( + text, + precedingNode, + enclosingNode, + followingNode, + comment, + options + ) || + handleReturnComments( + text, + precedingNode, + enclosingNode, + followingNode, + comment, + options + ) || + handleLastFunctionArgComments( + text, + precedingNode, + enclosingNode, + followingNode, + comment, + options + ) || + handleRetifComments( + enclosingNode, + precedingNode, + followingNode, + comment, + text, + options + ) || + handleIfStatementComments( + text, + precedingNode, + enclosingNode, + followingNode, + comment, + options + ) || + handleWhileComments( + text, + precedingNode, + enclosingNode, + followingNode, + comment, + options + ) || + handleTryComments(enclosingNode, followingNode, comment) || + handleClassComments(enclosingNode, followingNode, comment) || + handleFunctionParameter( + text, + precedingNode, + enclosingNode, + followingNode, + comment + ) || + handleFunction(text, enclosingNode, followingNode, comment, options) || + handleEntryComments(enclosingNode, comment) || + handleCallComments(precedingNode, enclosingNode, comment) || + handleAssignComments(enclosingNode, followingNode, comment) || + handleInlineComments( + enclosingNode, + precedingNode, + followingNode, + comment + ) || + handleNamespaceComments( + enclosingNode, + precedingNode, + followingNode, + comment + ) || + handleDeclareComments( + enclosingNode, + precedingNode, + followingNode, + comment + ) || + handleGoto(enclosingNode, comment) + ); +} + +function handleRemainingComment(comment, text, options) { + const { precedingNode, enclosingNode, followingNode } = comment; + return ( + handleIfStatementComments( + text, + precedingNode, + enclosingNode, + followingNode, + comment, + options + ) || + handleWhileComments( + text, + precedingNode, + enclosingNode, + followingNode, + comment, + options + ) || + handleCommentInEmptyParens(text, enclosingNode, comment, options) || + handleClassComments(enclosingNode, followingNode, comment) || + handleTraitUseComments(enclosingNode, followingNode, comment) || + handleFunctionParameter( + text, + precedingNode, + enclosingNode, + followingNode, + comment + ) || + handleFunction(text, enclosingNode, followingNode, comment, options) || + handleGoto(enclosingNode, comment) || + handleHalt(precedingNode, enclosingNode, followingNode, comment) || + handleBreakAndContinueStatementComments(enclosingNode, comment) || + handleInlineComments( + enclosingNode, + precedingNode, + followingNode, + comment + ) || + handleNamespaceComments( + enclosingNode, + precedingNode, + followingNode, + comment + ) + ); +} + +function addBlockStatementFirstComment(node, comment) { + const { children } = node; + if (children.length === 0) { + addDanglingComment(node, comment); + } else { + addLeadingComment(children[0], comment); + } +} + +function addBlockOrNotComment(node, comment) { + if (node.kind === "block") { + addBlockStatementFirstComment(node, comment); + } else { + addLeadingComment(node, comment); + } +} + +function handleArrayComments( + text, + precedingNode, + enclosingNode, + followingNode, + comment +) { + if ( + !precedingNode && + !followingNode && + enclosingNode && + enclosingNode.kind === "array" + ) { + addTrailingComment(enclosingNode, comment); + return true; + } + + return false; +} + +function handleReturnComments( + text, + precedingNode, + enclosingNode, + followingNode, + comment +) { + if (enclosingNode && enclosingNode.kind === "return" && !enclosingNode.expr) { + addTrailingComment(enclosingNode, comment); + return true; + } + + return false; +} + +function handleLastFunctionArgComments( + text, + precedingNode, + enclosingNode, + followingNode, + comment + /* options */ +) { + const nextCharIndex = getNextNonSpaceNonCommentCharacterIndex( + text, + locEnd(comment) + ); + const nextCharacter = text.charAt(nextCharIndex); + + // Real functions + if ( + precedingNode && + precedingNode.kind === "identifier" && + enclosingNode && + (enclosingNode.kind === "function" || enclosingNode.kind === "method") && + nextCharacter === ")" + ) { + addTrailingComment(enclosingNode, comment); + return true; + } + + if ( + enclosingNode && + (enclosingNode.kind === "function" || enclosingNode.kind === "method") && + followingNode && + followingNode.kind === "block" + ) { + addBlockStatementFirstComment(followingNode, comment); + return true; + } + + return false; +} + +function handleMemberExpressionComments(enclosingNode, followingNode, comment) { + if ( + enclosingNode && + isLookupNode(enclosingNode) && + followingNode && + ["identifier", "variable", "encapsed"].includes(followingNode.kind) + ) { + addLeadingComment(enclosingNode, comment); + + return true; + } + + return false; +} + +function handleIfStatementComments( + text, + precedingNode, + enclosingNode, + followingNode, + comment + /* options */ +) { + if (!enclosingNode || enclosingNode.kind !== "if" || !followingNode) { + return false; + } + + const nextCharIndex = getNextNonSpaceNonCommentCharacterIndex( + text, + locEnd(comment) + ); + const nextCharacter = text.charAt(nextCharIndex); + + if (nextCharacter === ")") { + addTrailingComment(precedingNode, comment); + return true; + } + + // Comments before `else`/`else if` treat as a dangling comment + if ( + precedingNode === enclosingNode.body && + followingNode === enclosingNode.alternate + ) { + addDanglingComment(enclosingNode, comment); + return true; + } + + if (followingNode.kind === "if") { + addBlockOrNotComment(followingNode.body, comment); + return true; + } + + // For comments positioned after the condition parenthesis in an if statement + // before the consequent without brackets on, such as + // if (a) /* comment */ true, + // we look at the next character to see if the following node + // is the consequent for the if statement + if (enclosingNode.body === followingNode) { + addLeadingComment(followingNode, comment); + return true; + } + + return false; +} + +function handleRetifComments( + enclosingNode, + precedingNode, + followingNode, + comment, + text + /* options */ +) { + const isSameLineAsPrecedingNode = + precedingNode && + !hasNewlineInRange(text, locEnd(precedingNode), locStart(comment)); + + if ( + (!precedingNode || !isSameLineAsPrecedingNode) && + enclosingNode && + enclosingNode.kind === "retif" && + followingNode + ) { + addLeadingComment(followingNode, comment); + return true; + } + return false; +} + +function handleForComments( + enclosingNode, + precedingNode, + followingNode, + comment +) { + if ( + !followingNode && + enclosingNode && + (enclosingNode.kind === "for" || enclosingNode.kind === "foreach") + ) { + // For a shortform for loop (where the body is just one node), add + // this as a leading comment to the body + if (enclosingNode.body && enclosingNode.body.kind !== "block") { + addLeadingComment(followingNode, comment); + } else { + addLeadingComment(enclosingNode, comment); + } + return true; + } + + return false; +} + +function handleTraitUseComments(enclosingNode, followingNode, comment) { + if ( + enclosingNode && + enclosingNode.kind === "traituse" && + enclosingNode.adaptations && + !enclosingNode.adaptations.length + ) { + addDanglingComment(enclosingNode, comment); + return true; + } + return false; +} + +function handleClassComments(enclosingNode, followingNode, comment) { + if ( + enclosingNode && + ["class", "interface", "trait"].includes(enclosingNode.kind) + ) { + if (enclosingNode.__parent_new_arguments?.includes(followingNode)) { + return false; + } + + // for extends nodes that have leading comments, we can store them as + // dangling comments so we can handle them in the printer + + if (followingNode && enclosingNode.extends) { + if (!Array.isArray(enclosingNode.extends)) { + if (followingNode === enclosingNode.extends) { + addDanglingComment(followingNode, comment); + return true; + } + } else { + if ( + enclosingNode.extends.some((extendsNode) => { + if (followingNode && followingNode === extendsNode) { + addDanglingComment(followingNode, comment); + return true; + } + }) + ) { + return true; + } + } + } + + // check each implements node - if any of them have comments we can store + // them as dangling comments and handle them in the printer + if (followingNode && enclosingNode.implements) { + if ( + enclosingNode.implements.some((implementsNode) => { + if (followingNode && followingNode === implementsNode) { + addDanglingComment(followingNode, comment); + return true; + } + }) + ) { + return true; + } + } + + // For an empty class where the body is only made up of comments, we + // need to attach this as a dangling comment on the class node itself + if (!(enclosingNode.body && enclosingNode.body.length > 0)) { + addDanglingComment(enclosingNode, comment); + return true; + } + } + + if ( + followingNode && + followingNode.kind === "class" && + followingNode.isAnonymous && + followingNode.leadingComments && + comment.kind === "commentblock" + ) { + return true; + } + return false; +} + +function handleFunction( + text, + enclosingNode, + followingNode, + comment + /* options */ +) { + if ( + enclosingNode && + (enclosingNode.kind === "function" || enclosingNode.kind === "method") + ) { + // we need to figure out if there are any comments that should be assigned + // to the function return type. To do this we check if the comment location + // is between the last argument end location and the return type start location. + let argumentsLocEnd = 0; + for (let i = 0; i < enclosingNode.arguments.length; i++) { + argumentsLocEnd = + locEnd(enclosingNode.arguments[i]) > argumentsLocEnd + ? locEnd(enclosingNode.arguments[i]) + : argumentsLocEnd; + } + const commentIsBetweenArgumentsAndBody = + enclosingNode.body && + locStart(comment) > argumentsLocEnd && + locEnd(comment) < locStart(enclosingNode.body); + const nextCharIndex = getNextNonSpaceNonCommentCharacterIndex( + text, + locEnd(comment) + ); + // we additionally need to check if this isn't a trailing argument comment, + // by checking the next character isn't ")" + if ( + enclosingNode.type && + commentIsBetweenArgumentsAndBody && + text.charAt(nextCharIndex) !== ")" + ) { + if (locEnd(comment) < locStart(enclosingNode.type)) { + // we need to store this as a dangling comment in case the type is nullable + // ie function(): ?string {} - the "nullable" attribute is part of the + // function node, not the type. + addDanglingComment(enclosingNode.type, comment); + return true; + } + addTrailingComment(enclosingNode.type, comment); + return true; + } + } + return false; +} + +function handleFunctionParameter( + text, + precedingNode, + enclosingNode, + followingNode, + comment +) { + if ( + !enclosingNode || + !["function", "method", "parameter"].includes(enclosingNode.kind) + ) { + return false; + } + if ( + precedingNode.kind === "typereference" && + followingNode.kind === "identifier" + ) { + addTrailingComment(precedingNode, comment); + return true; + } + return false; +} + +function handleBreakAndContinueStatementComments(enclosingNode, comment) { + if ( + enclosingNode && + (enclosingNode.kind === "continue" || enclosingNode.kind === "break") && + !enclosingNode.label + ) { + addTrailingComment(enclosingNode, comment); + return true; + } + return false; +} + +function handleGoto(enclosingNode, comment) { + if (enclosingNode && ["label", "goto"].includes(enclosingNode.kind)) { + addTrailingComment(enclosingNode, comment); + return true; + } + return false; +} + +function handleHalt(precedingNode, enclosingNode, followingNode, comment) { + if (enclosingNode && enclosingNode.kind === "halt") { + addDanglingComment(enclosingNode, comment); + return true; + } + + if (precedingNode && precedingNode.kind === "halt") { + addDanglingComment(precedingNode, comment); + return true; + } + + return false; +} + +function handleCommentInEmptyParens( + text, + enclosingNode, + comment + /* options */ +) { + const nextCharIndex = getNextNonSpaceNonCommentCharacterIndex( + text, + locEnd(comment) + ); + + if (text.charAt(nextCharIndex) !== ")") { + return false; + } + + // Only add dangling comments to fix the case when no arguments are present, + // i.e. a function without any argument. + if ( + enclosingNode && + (enclosingNode.kind === "function" || + enclosingNode.kind === "closure" || + enclosingNode.kind === "method" || + enclosingNode.kind === "call" || + enclosingNode.kind === "new") && + enclosingNode.arguments.length === 0 + ) { + addDanglingComment(enclosingNode, comment); + return true; + } + return false; +} + +function handleInlineComments( + enclosingNode, + precedingNode, + followingNode, + comment +) { + if (followingNode && followingNode.kind === "inline") { + if (!followingNode.leadingComments) { + followingNode.leadingComments = []; + } + + if (!followingNode.leadingComments.includes(comment)) { + followingNode.leadingComments.push(comment); + } + + return true; + } else if ( + !enclosingNode && + !followingNode && + precedingNode && + precedingNode.kind === "inline" + ) { + addDanglingComment(precedingNode, comment); + return true; + } + return false; +} + +function handleEntryComments(enclosingNode, comment) { + if (enclosingNode && enclosingNode.kind === "entry") { + addLeadingComment(enclosingNode, comment); + return true; + } + return false; +} + +function handleAssignComments(enclosingNode, followingNode, comment) { + if (enclosingNode && enclosingNode.kind === "assign" && followingNode) { + const equalSignOffset = + enclosingNode.loc.start.offset + enclosingNode.loc.source.indexOf("="); + + if (comment.loc.start.offset > equalSignOffset) { + addLeadingComment(followingNode, comment); + return true; + } + } + + return false; +} + +function handleTryComments(enclosingNode, followingNode, comment) { + if (!enclosingNode || enclosingNode.kind !== "try" || !followingNode) { + return false; + } + + if (followingNode.kind === "block") { + addBlockStatementFirstComment(followingNode, comment); + return true; + } + + if (followingNode.kind === "try") { + addBlockOrNotComment(followingNode.always, comment); + return true; + } + + if (followingNode.kind === "catch") { + addBlockOrNotComment(followingNode.body, comment); + return true; + } + + return false; +} + +function handleCallComments(precedingNode, enclosingNode, comment) { + if ( + enclosingNode && + enclosingNode.kind === "call" && + precedingNode && + enclosingNode.what === precedingNode && + enclosingNode.arguments.length > 0 + ) { + addLeadingComment(enclosingNode.arguments[0], comment); + return true; + } + return false; +} + +function handleNamespaceComments( + enclosingNode, + precedingNode, + followingNode, + comment +) { + if ( + !followingNode && + !precedingNode && + enclosingNode && + enclosingNode.kind === "namespace" && + !enclosingNode.withBrackets + ) { + addTrailingComment(enclosingNode, comment); + return true; + } else if ( + !precedingNode && + enclosingNode && + enclosingNode.kind === "namespace" && + !enclosingNode.withBrackets + ) { + addDanglingComment(enclosingNode, comment); + return true; + } + + return false; +} + +function handleDeclareComments( + enclosingNode, + precedingNode, + followingNode, + comment +) { + if (!enclosingNode || enclosingNode.kind !== "declare") { + return false; + } + + if (precedingNode && precedingNode.kind === "noop") { + return false; + } + + if (!followingNode || enclosingNode.directives[0] === followingNode) { + if (enclosingNode.mode === "none") { + addTrailingComment(enclosingNode, comment); + } else { + addDanglingComment(enclosingNode, comment); + } + + return true; + } + + if (followingNode && precedingNode) { + addLeadingComment(followingNode, comment); + + return true; + } + + return false; +} + +function handleWhileComments( + text, + precedingNode, + enclosingNode, + followingNode, + comment + /* options */ +) { + if (!enclosingNode || enclosingNode.kind !== "while" || !followingNode) { + return false; + } + // We unfortunately have no way using the AST or location of nodes to know + // if the comment is positioned before the condition parenthesis: + // while (a /* comment */) {} + // The only workaround I found is to look at the next character to see if + // it is a ). + + const nextCharIndex = getNextNonSpaceNonCommentCharacterIndex( + text, + locEnd(comment) + ); + const nextCharacter = text.charAt(nextCharIndex); + + if (nextCharacter === ")") { + addTrailingComment(precedingNode, comment); + return true; + } + + if (followingNode.kind === "block") { + addBlockStatementFirstComment(followingNode, comment); + return true; + } + + return false; +} + +// https://github.com/prettier/prettier/blob/c01661f311a2e1e033f1f9cb127882cc13e293bd/src/main/comments/print.js#L23 +function printComment(path, options) { + const comment = path.node; + comment.printed = true; + return options.printer.printComment(path, options); +} + +// https://github.com/prettier/prettier/blob/6bff776efd0951b7812818d02d1cae3fda184737/src/main/comments/print.js#L119 +function printDanglingComments(path, options, sameIndent, filter) { + const parts = []; + const node = path.getValue(); + + if (!node || !node.comments) { + return ""; + } + + path.each(() => { + const comment = path.node; + if ( + comment && + !comment.leading && + !comment.trailing && + (!filter || filter(comment)) + ) { + parts.push(printComment(path, options)); + } + }, "comments"); + + if (parts.length === 0) { + return ""; + } + + if (sameIndent) { + return join(hardline, parts); + } + return indent([hardline, join(hardline, parts)]); +} + +function hasLeadingComment(node) { + return node.comments && node.comments.some((comment) => comment.leading); +} + +function hasTrailingComment(node) { + return node.comments && node.comments.some((comment) => comment.trailing); +} + +function hasLeadingOwnLineComment(text, node) { + return ( + node.comments && + node.comments.some( + (comment) => comment.leading && hasNewline(text, locEnd(comment)) + ) + ); +} + +function printComments(comments, options) { + const parts = []; + comments.forEach((comment, index, comments) => { + comment.printed = true; + const isLastComment = comments.length === index + 1; + parts.push(comment.value); + if (!isLastComment) { + parts.push(hardline); + } + if ( + isNextLineEmpty(options.originalText, locEnd(comment)) && + !isLastComment + ) { + parts.push(hardline); + } + }); + return parts; +} + +function isBlockComment(comment) { + return comment.kind === "commentblock"; +} + +function getCommentChildNodes(node) { + if (node.kind === "new" && node.what.kind === "class") { + // Pretend to be child of `class` + node.what.__parent_new_arguments = [...node.arguments]; + return [node.what]; + } +} + +function canAttachComment(node) { + return ( + node.kind && node.kind !== "commentblock" && node.kind !== "commentline" + ); +} + +// Based on https://github.com/prettier/prettier/blob/master/src/main/comments.js +// TODO remove after https://github.com/prettier/prettier/issues/5087 +function prependCursorPlaceholder(path, options, printed) { + const { node } = path; + if (node && node === options.cursorNode) { + return [cursor, printed, cursor]; + } + + return printed; +} + +function printLeadingComment(path, print, options) { + const contents = printComment(path, options); + + if (!contents) { + return ""; + } + + const comment = path.node; + const isBlock = + options.printer.isBlockComment && options.printer.isBlockComment(comment); + + // Leading block comments should see if they need to stay on the + // same line or not. + if (isBlock) { + return [ + contents, + hasNewline(options.originalText, locEnd(comment)) ? hardline : " ", + ]; + } + + return [contents, hardline]; +} + +function printTrailingComment(path, print, options) { + const contents = printComment(path, options); + if (!contents) { + return ""; + } + + const comment = path.node; + const isBlock = + options.printer.isBlockComment && options.printer.isBlockComment(comment); + + if ( + hasNewline(options.originalText, locStart(comment), { + backwards: true, + }) + ) { + // This allows comments at the end of nested structures: + // { + // x: 1, + // y: 2 + // // A comment + // } + // Those kinds of comments are almost always leading comments, but + // here it doesn't go "outside" the block and turns it into a + // trailing comment for `2`. We can simulate the above by checking + // if this a comment on its own line; normal trailing comments are + // always at the end of another expression. + + const isLineBeforeEmpty = isPreviousLineEmpty( + options.originalText, + locStart(comment) + ); + + return lineSuffix([hardline, isLineBeforeEmpty ? hardline : "", contents]); + } else if (isBlock) { + // Trailing block comments never need a newline + return [" ", contents]; + } + + return [lineSuffix([" ", contents]), !isBlock ? breakParent : ""]; +} + +function printAllComments(path, print, options, needsSemi) { + const { node } = path; + const printed = print(path); + const comments = node && node.comments; + + if (!comments || comments.length === 0) { + return prependCursorPlaceholder(path, options, printed); + } + + const leadingParts = []; + const trailingParts = [needsSemi ? ";" : "", printed]; + + path.each(({ node: comment }) => { + const { leading, trailing } = comment; + + if (leading) { + const contents = printLeadingComment(path, print, options); + if (!contents) { + return; + } + leadingParts.push(contents); + + const text = options.originalText; + if (hasNewline(text, skipNewline(text, locEnd(comment)))) { + leadingParts.push(hardline); + } + } else if (trailing) { + trailingParts.push(printTrailingComment(path, print, options)); + } + }, "comments"); + + return prependCursorPlaceholder( + path, + options, + leadingParts.concat(trailingParts) + ); +} + +export { + handleOwnLineComment, + handleEndOfLineComment, + handleRemainingComment, + getCommentChildNodes, + canAttachComment, + isBlockComment, + printDanglingComments, + hasLeadingComment, + hasTrailingComment, + hasLeadingOwnLineComment, + printComments, + printAllComments, +}; diff --git a/frontend/src/utils/prettier/plugins/php/src/index.mjs b/frontend/src/utils/prettier/plugins/php/src/index.mjs new file mode 100644 index 0000000..6a35c03 --- /dev/null +++ b/frontend/src/utils/prettier/plugins/php/src/index.mjs @@ -0,0 +1,173 @@ +import { doc } from "prettier"; +import LINGUIST_LANGUAGES_PHP from "linguist-languages/data/PHP"; +import LINGUIST_LANGUAGES_HTML_PHP from "linguist-languages/data/HTML_2b_PHP"; +import parse from "./parser.mjs"; +import print from "./printer.mjs"; +import clean from "./clean.mjs"; +import options from "./options.mjs"; +import { + handleOwnLineComment, + handleEndOfLineComment, + handleRemainingComment, + getCommentChildNodes, + canAttachComment, + isBlockComment, +} from "./comments.mjs"; +import { hasPragma, insertPragma } from "./pragma.mjs"; +import { locStart, locEnd } from "./loc.mjs"; + +const { join, hardline } = doc.builders; + +function createLanguage(linguistData, { extend, override }) { + const language = {}; + + for (const key in linguistData) { + const newKey = key === "languageId" ? "linguistLanguageId" : key; + language[newKey] = linguistData[key]; + } + + if (extend) { + for (const key in extend) { + language[key] = (language[key] || []).concat(extend[key]); + } + } + + for (const key in override) { + language[key] = override[key]; + } + + return language; +} + +const languages = [ + createLanguage(LINGUIST_LANGUAGES_PHP, { + override: { + parsers: ["php"], + vscodeLanguageIds: ["php"], + }, + }), + createLanguage(LINGUIST_LANGUAGES_HTML_PHP, { + override: { + parsers: ["php"], + vscodeLanguageIds: ["php"], + }, + }), +]; + +const parsers = { + php: { + parse, + astFormat: "php", + locStart, + locEnd, + hasPragma, + }, +}; + +const ignoredKeys = new Set([ + "kind", + "loc", + "errors", + "extra", + "comments", + "leadingComments", + "enclosingNode", + "precedingNode", + "followingNode", +]); +function getVisitorKeys(node, nonTraversableKeys) { + return Object.keys(node).filter( + (key) => !nonTraversableKeys.has(key) && !ignoredKeys.has(key) + ); +} + +const printers = { + php: { + print, + getVisitorKeys, + insertPragma, + massageAstNode: clean, + getCommentChildNodes, + canAttachComment, + isBlockComment, + handleComments: { + ownLine: handleOwnLineComment, + endOfLine: handleEndOfLineComment, + remaining: handleRemainingComment, + }, + willPrintOwnComments(path) { + const { node } = path; + + return node && node.kind === "noop"; + }, + printComment(path) { + const comment = path.node; + + switch (comment.kind) { + case "commentblock": { + // for now, don't touch single line block comments + if (!comment.value.includes("\n")) { + return comment.value; + } + + const lines = comment.value.split("\n"); + // if this is a block comment, handle indentation + if ( + lines + .slice(1, lines.length - 1) + .every((line) => line.trim()[0] === "*") + ) { + return join( + hardline, + lines.map( + (line, index) => + (index > 0 ? " " : "") + + (index < lines.length - 1 ? line.trim() : line.trimLeft()) + ) + ); + } + + // otherwise we can't be sure about indentation, so just print as is + return comment.value; + } + case "commentline": { + return comment.value.trimRight(); + } + /* c8 ignore next 2 */ + default: + throw new Error(`Not a comment: ${JSON.stringify(comment)}`); + } + }, + hasPrettierIgnore(path) { + const isSimpleIgnore = (comment) => + comment.value.includes("prettier-ignore") && + !comment.value.includes("prettier-ignore-start") && + !comment.value.includes("prettier-ignore-end"); + + const { node, parent: parentNode } = path; + + return ( + (node && + node.kind !== "classconstant" && + node.comments && + node.comments.length > 0 && + node.comments.some(isSimpleIgnore)) || + // For proper formatting, the classconstant ignore formatting should + // run on the "constant" child + (node && + node.kind === "constant" && + parentNode && + parentNode.kind === "classconstant" && + parentNode.comments && + parentNode.comments.length > 0 && + parentNode.comments.some(isSimpleIgnore)) + ); + }, + }, +}; + +const defaultOptions = { + tabWidth: 4, +}; + +export { languages, printers, parsers, options, defaultOptions }; diff --git a/frontend/src/utils/prettier/plugins/php/src/loc.mjs b/frontend/src/utils/prettier/plugins/php/src/loc.mjs new file mode 100644 index 0000000..c52168a --- /dev/null +++ b/frontend/src/utils/prettier/plugins/php/src/loc.mjs @@ -0,0 +1,4 @@ +const loc = (prop) => (node) => node.loc?.[prop]?.offset; + +export const locStart = loc("start"); +export const locEnd = loc("end"); diff --git a/frontend/src/utils/prettier/plugins/php/src/needs-parens.mjs b/frontend/src/utils/prettier/plugins/php/src/needs-parens.mjs new file mode 100644 index 0000000..c234c8d --- /dev/null +++ b/frontend/src/utils/prettier/plugins/php/src/needs-parens.mjs @@ -0,0 +1,250 @@ +import { getPrecedence, shouldFlatten, isBitwiseOperator } from "./util.mjs"; + +function needsParens(path, options) { + const { parent } = path; + + if (!parent) { + return false; + } + + const { key, node } = path; + + if ( + [ + // No need parens for top level children of this nodes + "program", + "expressionstatement", + "namespace", + "declare", + "block", + + // No need parens + "include", + "print", + "return", + "echo", + ].includes(parent.kind) + ) { + return false; + } + + switch (node.kind) { + case "pre": + case "post": + if (parent.kind === "unary") { + return ( + node.kind === "pre" && + ((node.type === "+" && parent.type === "+") || + (node.type === "-" && parent.type === "-")) + ); + } + // else fallthrough + case "unary": + switch (parent.kind) { + case "unary": + return ( + node.type === parent.type && + (node.type === "+" || node.type === "-") + ); + case "propertylookup": + case "nullsafepropertylookup": + case "staticlookup": + case "offsetlookup": + case "call": + return key === "what"; + case "bin": + return parent.type === "**" && key === "left"; + default: + return false; + } + case "bin": { + switch (parent.kind) { + case "assign": + case "retif": + return ["and", "xor", "or"].includes(node.type); + case "silent": + case "cast": + // TODO: bug https://github.com/glayzzle/php-parser/issues/172 + return node.parenthesizedExpression; + case "pre": + case "post": + case "unary": + return true; + case "call": + case "propertylookup": + case "nullsafepropertylookup": + case "staticlookup": + case "offsetlookup": + return key === "what"; + case "bin": { + const po = parent.type; + const pp = getPrecedence(po); + const no = node.type; + const np = getPrecedence(no); + + if (pp > np) { + return true; + } + + if (po === "||" && no === "&&") { + return true; + } + + if (pp === np && key === "right") { + return true; + } + + if (pp === np && !shouldFlatten(po, no)) { + return true; + } + + if (pp < np && no === "%") { + return po === "+" || po === "-"; + } + + // Add parenthesis when working with bitwise operators + // It's not stricly needed but helps with code understanding + if (isBitwiseOperator(po)) { + return true; + } + + return false; + } + + default: + return false; + } + } + case "propertylookup": + case "nullsafepropertylookup": + case "staticlookup": { + switch (parent.kind) { + case "call": + return key === "what" && node.parenthesizedExpression; + + default: + return false; + } + } + case "clone": + case "new": { + const requiresParens = + node.kind === "clone" || + (node.kind === "new" && options.phpVersion < 8.4); + switch (parent.kind) { + case "propertylookup": + case "nullsafepropertylookup": + case "staticlookup": + case "offsetlookup": + case "call": + return key === "what" && requiresParens; + default: + return false; + } + } + case "yield": { + switch (parent.kind) { + case "propertylookup": + case "nullsafepropertylookup": + case "staticlookup": + case "offsetlookup": + case "call": + return key === "what"; + + case "retif": + return key === "test"; + + default: + return !!(node.key || node.value); + } + } + case "assign": { + if ( + parent.kind === "for" && + (parent.init.includes(node) || parent.increment.includes(node)) + ) { + return false; + } else if (parent.kind === "assign") { + return false; + } else if (parent.kind === "static") { + return false; + } else if ( + ["if", "do", "while", "foreach", "switch"].includes(parent.kind) + ) { + return false; + } else if (parent.kind === "silent") { + return false; + } else if (parent.kind === "call") { + return false; + } + + return true; + } + case "retif": + switch (parent.kind) { + case "cast": + return true; + case "unary": + case "bin": + case "retif": + if (key === "test" && !parent.trueExpr) { + return false; + } + + return true; + case "propertylookup": + case "nullsafepropertylookup": + case "staticlookup": + case "offsetlookup": + case "call": + return key === "what"; + + default: + return false; + } + case "closure": + switch (parent.kind) { + case "call": + return key === "what"; + + // https://github.com/prettier/plugin-php/issues/1675 + case "propertylookup": + case "nullsafepropertylookup": + return true; + + default: + return false; + } + case "silence": + case "cast": + // TODO: bug https://github.com/glayzzle/php-parser/issues/172 + return node.parenthesizedExpression; + // else fallthrough + case "string": + case "array": + switch (parent.kind) { + case "propertylookup": + case "nullsafepropertylookup": + case "staticlookup": + case "offsetlookup": + case "call": + if ( + ["string", "array"].includes(node.kind) && + parent.kind === "offsetlookup" + ) { + return false; + } + + return key === "what"; + default: + return false; + } + case "print": + case "include": + return parent.kind === "bin"; + } + + return false; +} + +export default needsParens; diff --git a/frontend/src/utils/prettier/plugins/php/src/options.mjs b/frontend/src/utils/prettier/plugins/php/src/options.mjs new file mode 100644 index 0000000..c50a4ed --- /dev/null +++ b/frontend/src/utils/prettier/plugins/php/src/options.mjs @@ -0,0 +1,69 @@ +const CATEGORY_PHP = "PHP"; + +// prettier-ignore +const SUPPORTED_PHP_VERSIONS = [ + 5.0, 5.1, 5.2, 5.3, 5.4, 5.5, 5.6, + 7.0, 7.1, 7.2, 7.3, 7.4, + 8.0, 8.1, 8.2, 8.3, 8.4, +]; + +export const LATEST_SUPPORTED_PHP_VERSION = Math.max(...SUPPORTED_PHP_VERSIONS); + +/** + * Resolve the PHP version to a number based on the provided options. + */ +export function resolvePhpVersion(options) { + if (!options) { + return; + } + + if (options.phpVersion === "auto" || options.phpVersion === "composer") { + options.phpVersion = LATEST_SUPPORTED_PHP_VERSION; + } else { + options.phpVersion = parseFloat(options.phpVersion); + } +} + +export default { + phpVersion: { + since: "0.13.0", + category: CATEGORY_PHP, + type: "choice", + default: "auto", + description: "Minimum target PHP version.", + choices: [ + ...SUPPORTED_PHP_VERSIONS.map((v) => ({ value: v.toFixed(1) })), + { + value: "auto", + description: `Use latest PHP Version (${LATEST_SUPPORTED_PHP_VERSION})`, + }, + ], + }, + trailingCommaPHP: { + since: "0.0.0", + category: CATEGORY_PHP, + type: "boolean", + default: true, + description: "Print trailing commas wherever possible when multi-line.", + }, + braceStyle: { + since: "0.10.0", + category: CATEGORY_PHP, + type: "choice", + default: "per-cs", + description: + "Print one space or newline for code blocks (classes and functions).", + choices: [ + { value: "psr-2", description: "(deprecated) Use per-cs" }, + { value: "per-cs", description: "Use the PER Coding Style brace style." }, + { value: "1tbs", description: "Use 1tbs brace style." }, + ], + }, + singleQuote: { + since: "0.0.0", + category: CATEGORY_PHP, + type: "boolean", + default: false, + description: "Use single quotes instead of double quotes.", + }, +}; diff --git a/frontend/src/utils/prettier/plugins/php/src/parser.mjs b/frontend/src/utils/prettier/plugins/php/src/parser.mjs new file mode 100644 index 0000000..8df4987 --- /dev/null +++ b/frontend/src/utils/prettier/plugins/php/src/parser.mjs @@ -0,0 +1,67 @@ +import engine from "php-parser"; +import { LATEST_SUPPORTED_PHP_VERSION } from "./options.mjs"; +import { resolvePhpVersion } from "./options.mjs"; + +function parse(text, opts) { + const inMarkdown = opts && opts.parentParser === "markdown"; + + if (!text && inMarkdown) { + return ""; + } + resolvePhpVersion(opts); + + // Todo https://github.com/glayzzle/php-parser/issues/170 + text = text.replace(/\?>\n<\?/g, "?>\n___PSEUDO_INLINE_PLACEHOLDER___ { + if (comment.value[comment.value.length - 1] === "\n") { + comment.value = comment.value.slice(0, -1); + comment.loc.end.offset = comment.loc.end.offset - 1; + } + }); + + return ast; +} + +export default parse; diff --git a/frontend/src/utils/prettier/plugins/php/src/pragma.mjs b/frontend/src/utils/prettier/plugins/php/src/pragma.mjs new file mode 100644 index 0000000..976baa7 --- /dev/null +++ b/frontend/src/utils/prettier/plugins/php/src/pragma.mjs @@ -0,0 +1,92 @@ +import { memoize } from "./util.mjs"; +import parse from "./parser.mjs"; + +const reHasPragma = /@prettier|@format/; + +const getPageLevelDocBlock = memoize((text) => { + const parsed = parse(text); + + const [firstChild] = parsed.children; + const [firstDocBlock] = parsed.comments.filter( + (el) => el.kind === "commentblock" + ); + + if ( + firstChild && + firstDocBlock && + firstDocBlock.loc.start.line < firstChild.loc.start.line + ) { + return firstDocBlock; + } +}); + +function hasPragma(text) { + // fast path optimization - check if the pragma shows up in the file at all + if (!reHasPragma.test(text)) { + return false; + } + + const pageLevelDocBlock = getPageLevelDocBlock(text); + + if (pageLevelDocBlock) { + const { value } = pageLevelDocBlock; + + return reHasPragma.test(value); + } + + return false; +} + +function injectPragma(docblock) { + let lines = docblock.split("\n"); + + if (lines.length === 1) { + // normalize to multiline for simplicity + const [, line] = /\/*\*\*(.*)\*\//.exec(lines[0]); + + lines = ["/**", ` * ${line.trim()}`, " */"]; + } + + // find the first @pragma + // if there happens to be one on the opening line, just put it on the next line. + const pragmaIndex = lines.findIndex((line) => /@\S/.test(line)) || 1; + + // not found => index == -1, which conveniently will splice 1 from the end. + lines.splice(pragmaIndex, 0, " * @format"); + + return lines.join("\n"); +} + +function insertPragma(text) { + const pageLevelDocBlock = getPageLevelDocBlock(text); + + if (pageLevelDocBlock) { + const { + start: { offset: startOffset }, + end: { offset: endOffset }, + } = pageLevelDocBlock.loc; + const before = text.substring(0, startOffset); + const after = text.substring(endOffset); + + return `${before}${injectPragma(pageLevelDocBlock.value, text)}${after}`; + } + + const openTag = "= requiredVersion; +} + +function shouldPrintHardlineForOpenBrace(options) { + switch (options.braceStyle) { + case "1tbs": + return false; + case "psr-2": + case "per-cs": + default: + return true; + } +} + +function genericPrint(path, options, print) { + const { node } = path; + + if (typeof node === "string") { + return node; + } + + const printedWithoutParens = printNode(path, options, print); + + const parts = []; + const needsParens = pathNeedsParens(path, options); + + if (needsParens) { + parts.unshift("("); + } + + parts.push(printedWithoutParens); + + if (needsParens) { + parts.push(")"); + } + + if (lineShouldEndWithSemicolon(path)) { + parts.push(";"); + } + + if (fileShouldEndWithHardline(path)) { + parts.push(hardline); + } + + return parts; +} + +function printPropertyLookup(path, options, print, nullsafe = false) { + return [nullsafe ? "?" : "", "->", print("offset")]; +} + +function printNullsafePropertyLookup(path, options, print) { + return printPropertyLookup(path, options, print, true); +} + +function printStaticLookup(path, options, print) { + const { node } = path; + const needCurly = !["variable", "identifier"].includes(node.offset.kind); + + return ["::", needCurly ? "{" : "", print("offset"), needCurly ? "}" : ""]; +} + +function printOffsetLookup(path, options, print) { + const { node } = path; + const shouldInline = + (node.offset && node.offset.kind === "number") || + getAncestorNode(path, "encapsed"); + + return [ + "[", + node.offset + ? group([ + indent([shouldInline ? "" : softline, print("offset")]), + shouldInline ? "" : softline, + ]) + : "", + "]", + ]; +} + +// We detect calls on member expressions specially to format a +// common pattern better. The pattern we are looking for is this: +// +// $arr +// ->map(function(x) { return $x + 1; }) +// ->filter(function(x) { return $x > 10; }) +// ->some(function(x) { return $x % 2; }); +// +// The way it is structured in the AST is via a nested sequence of +// propertylookup, staticlookup, offsetlookup and call. +// We need to traverse the AST and make groups out of it +// to print it in the desired way. +function printMemberChain(path, options, print) { + // The first phase is to linearize the AST by traversing it down. + // + // Example: + // a()->b->c()->d(); + // has the AST structure + // call (isLookupNode d ( + // call (isLookupNode c ( + // isLookupNode b ( + // call (variable a) + // ) + // )) + // )) + // and we transform it into (notice the reversed order) + // [identifier a, call, isLookupNode b, isLookupNode c, call, + // isLookupNode d, call] + const printedNodes = []; + + // Here we try to retain one typed empty line after each call expression or + // the first group whether it is in parentheses or not + // + // Example: + // $a + // ->call() + // + // ->otherCall(); + // + // ($foo ? $a : $b) + // ->call() + // ->otherCall(); + function shouldInsertEmptyLineAfter(node) { + const { originalText } = options; + const nextCharIndex = getNextNonSpaceNonCommentCharacterIndex( + originalText, + locEnd(node) + ); + const nextChar = originalText.charAt(nextCharIndex); + + // if it is cut off by a parenthesis, we only account for one typed empty + // line after that parenthesis + if (nextChar === ")") { + return isNextLineEmptyAfterIndex( + originalText, + nextCharIndex + 1, + options + ); + } + + return isNextLineEmpty(originalText, locEnd(node)); + } + + function traverse(path) { + const { node } = path; + + if ( + node.kind === "call" && + (isLookupNode(node.what) || node.what.kind === "call") + ) { + printedNodes.unshift({ + node, + printed: [ + printAllComments( + path, + () => printArgumentsList(path, options, print), + options + ), + shouldInsertEmptyLineAfter(node) ? hardline : "", + ], + }); + path.call((what) => traverse(what), "what"); + } else if (isLookupNode(node)) { + // Print *lookup nodes as we standard print them outside member chain + let printedMemberish = null; + + if (node.kind === "propertylookup") { + printedMemberish = printPropertyLookup(path, options, print); + } else if (node.kind === "nullsafepropertylookup") { + printedMemberish = printNullsafePropertyLookup(path, options, print); + } else if (node.kind === "staticlookup") { + printedMemberish = printStaticLookup(path, options, print); + } else { + printedMemberish = printOffsetLookup(path, options, print); + } + + printedNodes.unshift({ + node, + needsParens: pathNeedsParens(path, options), + printed: printAllComments(path, () => printedMemberish, options), + }); + path.call((what) => traverse(what), "what"); + } else { + printedNodes.unshift({ + node, + printed: print(), + }); + } + } + + const { node } = path; + + printedNodes.unshift({ + node, + printed: printArgumentsList(path, options, print), + }); + path.call((what) => traverse(what), "what"); + + // Restore parens around `propertylookup` and `staticlookup` nodes with call. + // $value = ($object->foo)(); + // $value = ($object::$foo)(); + for (let i = 0; i < printedNodes.length; ++i) { + if ( + printedNodes[i].node.kind === "call" && + printedNodes[i - 1] && + ["propertylookup", "nullsafepropertylookup", "staticlookup"].includes( + printedNodes[i - 1].node.kind + ) && + printedNodes[i - 1].needsParens + ) { + printedNodes[0].printed = ["(", printedNodes[0].printed]; + printedNodes[i - 1].printed = [printedNodes[i - 1].printed, ")"]; + } + } + + // create groups from list of nodes, i.e. + // [identifier a, call, isLookupNode b, isLookupNode c, call, + // isLookupNode d, call] + // will be grouped as + // [ + // [identifier a, Call], + // [isLookupNode b, isLookupNode c, call], + // [isLookupNode d, call] + // ] + // so that we can print it as + // a() + // ->b->c() + // ->d(); + const groups = []; + + let currentGroup = [printedNodes[0]]; + let i = 1; + + for (; i < printedNodes.length; ++i) { + if ( + printedNodes[i].node.kind === "call" || + (isLookupNode(printedNodes[i].node) && + printedNodes[i].node.offset && + printedNodes[i].node.offset.kind === "number") + ) { + currentGroup.push(printedNodes[i]); + } else { + break; + } + } + + if (printedNodes[0].node.kind !== "call") { + for (; i + 1 < printedNodes.length; ++i) { + if ( + isLookupNode(printedNodes[i].node) && + isLookupNode(printedNodes[i + 1].node) + ) { + currentGroup.push(printedNodes[i]); + } else { + break; + } + } + } + + groups.push(currentGroup); + currentGroup = []; + + // Then, each following group is a sequence of propertylookup followed by + // a sequence of call. To compute it, we keep adding things to the + // group until we have seen a call in the past and reach a + // propertylookup + let hasSeenCallExpression = false; + + for (; i < printedNodes.length; ++i) { + if (hasSeenCallExpression && isLookupNode(printedNodes[i].node)) { + // [0] should be appended at the end of the group instead of the + // beginning of the next one + if ( + printedNodes[i].node.kind === "offsetlookup" && + printedNodes[i].node.offset && + printedNodes[i].node.offset.kind === "number" + ) { + currentGroup.push(printedNodes[i]); + continue; + } + + groups.push(currentGroup); + currentGroup = []; + hasSeenCallExpression = false; + } + + if (printedNodes[i].node.kind === "call") { + hasSeenCallExpression = true; + } + currentGroup.push(printedNodes[i]); + + if ( + printedNodes[i].node.comments && + hasTrailingComment(printedNodes[i].node) + ) { + groups.push(currentGroup); + currentGroup = []; + hasSeenCallExpression = false; + } + } + + if (currentGroup.length > 0) { + groups.push(currentGroup); + } + + // Merge next nodes when: + // + // 1. We have `$this` variable before + // + // Example: + // $this->method()->property; + // + // 2. When we have offsetlookup after *lookup node + // + // Example: + // $foo->Data['key']("foo") + // ->method(); + // + // 3. expression statements with variable names shorter than the tab width + // + // Example: + // $foo->bar() + // ->baz() + // ->buzz() + + function shouldNotWrap(groups) { + const hasComputed = + groups[1].length && groups[1][0].node.kind === "offsetlookup"; + + if (groups[0].length === 1) { + const firstNode = groups[0][0].node; + + return ( + (firstNode.kind === "variable" && + (firstNode.name === "this" || + (isExpressionStatement && isShort(firstNode.name)))) || + isReferenceLikeNode(firstNode) + ); + } + + function isShort(name) { + return name.length < options.tabWidth; + } + + const lastNode = getLast(groups[0]).node; + + return ( + isLookupNode(lastNode) && + (lastNode.offset.kind === "identifier" || + lastNode.offset.kind === "variable") && + hasComputed + ); + } + + const isExpressionStatement = path.parent.kind === "expressionstatement"; + const shouldMerge = + groups.length >= 2 && !groups[1][0].node.comments && shouldNotWrap(groups); + + function printGroup(printedGroup) { + const result = []; + + for (let i = 0; i < printedGroup.length; i++) { + // Checks if the next node (i.e. the parent node) needs parens + // and print accordingl y + if (printedGroup[i + 1] && printedGroup[i + 1].needsParens) { + result.push( + "(", + printedGroup[i].printed, + printedGroup[i + 1].printed, + ")" + ); + i++; + } else { + result.push(printedGroup[i].printed); + } + } + + return result; + } + + function printIndentedGroup(groups) { + if (groups.length === 0) { + return ""; + } + + return indent(group([hardline, join(hardline, groups.map(printGroup))])); + } + + const printedGroups = groups.map(printGroup); + const oneLine = printedGroups; + + // Indicates how many we should merge + // + // Example (true): + // $this->method()->otherMethod( + // 'argument' + // ); + // + // Example (false): + // $foo + // ->method() + // ->otherMethod(); + const cutoff = shouldMerge ? 3 : 2; + const flatGroups = groups.slice(0, cutoff).flat(); + + const hasComment = + flatGroups.slice(1, -1).some((node) => hasLeadingComment(node.node)) || + flatGroups.slice(0, -1).some((node) => hasTrailingComment(node.node)) || + (groups[cutoff] && hasLeadingComment(groups[cutoff][0].node)); + + const hasEncapsedAncestor = getAncestorNode(path, "encapsed"); + + // If we only have a single `->`, we shouldn't do anything fancy and just + // render everything concatenated together. + // In `encapsed` node we always print in one line. + if ((groups.length <= cutoff && !hasComment) || hasEncapsedAncestor) { + return group(oneLine); + } + + // Find out the last node in the first group and check if it has an + // empty line after + const lastNodeBeforeIndent = getLast( + shouldMerge ? groups.slice(1, 2)[0] : groups[0] + ).node; + const shouldHaveEmptyLineBeforeIndent = + lastNodeBeforeIndent.kind !== "call" && + shouldInsertEmptyLineAfter(lastNodeBeforeIndent); + + const expanded = [ + printGroup(groups[0]), + shouldMerge ? groups.slice(1, 2).map(printGroup) : "", + shouldHaveEmptyLineBeforeIndent ? hardline : "", + printIndentedGroup(groups.slice(shouldMerge ? 2 : 1)), + ]; + + const callExpressions = printedNodes.filter( + (tuple) => tuple.node.kind === "call" + ); + + // We don't want to print in one line if there's: + // * A comment. + // * 3 or more chained calls. + // * Any group but the last one has a hard line. + // If the last group is a function it's okay to inline if it fits. + if ( + hasComment || + (callExpressions.length > 2 && + callExpressions.some( + (exp) => !exp.node.arguments.every((arg) => isSimpleCallArgument(arg)) + )) || + printedGroups.slice(0, -1).some(willBreak) + ) { + return group(expanded); + } + + return [ + // We only need to check `oneLine` because if `expanded` is chosen + // that means that the parent group has already been broken + // naturally + willBreak(oneLine) || shouldHaveEmptyLineBeforeIndent ? breakParent : "", + conditionalGroup([oneLine, expanded]), + ]; +} + +function couldGroupArg(arg) { + return ( + (arg.kind === "array" && (arg.items.length > 0 || arg.comments)) || + arg.kind === "function" || + arg.kind === "method" || + arg.kind === "closure" + ); +} + +function shouldGroupLastArg(args) { + const lastArg = getLast(args); + const penultimateArg = getPenultimate(args); + + return ( + !hasLeadingComment(lastArg) && + !hasTrailingComment(lastArg) && + couldGroupArg(lastArg) && + // If the last two arguments are of the same type, + // disable last element expansion. + (!penultimateArg || penultimateArg.kind !== lastArg.kind) + ); +} + +function shouldGroupFirstArg(args) { + if (args.length !== 2) { + return false; + } + + const [firstArg, secondArg] = args; + + return ( + (!firstArg.comments || !firstArg.comments.length) && + (firstArg.kind === "function" || + firstArg.kind === "method" || + firstArg.kind === "closure") && + secondArg.kind !== "retif" && + !couldGroupArg(secondArg) + ); +} + +function printArgumentsList(path, options, print, argumentsKey = "arguments") { + const args = path.node[argumentsKey]; + + if (args.length === 0) { + return [ + "(", + printDanglingComments(path, options, /* sameIndent */ true), + ")", + ]; + } + + let anyArgEmptyLine = false; + let hasEmptyLineFollowingFirstArg = false; + + const printedArguments = path.map(({ node: arg, isLast, isFirst }) => { + const parts = [print()]; + + if (isLast) { + // do nothing + } else if (isNextLineEmpty(options.originalText, locEnd(arg))) { + if (isFirst) { + hasEmptyLineFollowingFirstArg = true; + } + + anyArgEmptyLine = true; + parts.push(",", hardline, hardline); + } else { + parts.push(",", line); + } + + return parts; + }, argumentsKey); + + const { node } = path; + const lastArg = getLast(args); + + const maybeTrailingComma = + (shouldPrintComma(options, 7.3) && + ["call", "new", "unset", "isset"].includes(node.kind)) || + (shouldPrintComma(options, 8.0) && + ["function", "closure", "method", "arrowfunc", "attribute"].includes( + node.kind + )) + ? indent([ + lastArg && shouldPrintHardlineBeforeTrailingComma(lastArg) + ? hardline + : "", + ",", + ]) + : ""; + + function allArgsBrokenOut() { + return group( + ["(", indent([line, ...printedArguments]), maybeTrailingComma, line, ")"], + { shouldBreak: true } + ); + } + + const shouldGroupFirst = shouldGroupFirstArg(args); + const shouldGroupLast = shouldGroupLastArg(args); + + if (shouldGroupFirst || shouldGroupLast) { + const shouldBreak = + (shouldGroupFirst + ? printedArguments.slice(1).some(willBreak) + : printedArguments.slice(0, -1).some(willBreak)) || anyArgEmptyLine; + + // We want to print the last argument with a special flag + let printedExpanded; + + path.each(({ isLast, isFirst }) => { + if (shouldGroupFirst && isFirst) { + printedExpanded = [ + print([], { expandFirstArg: true }), + printedArguments.length > 1 ? "," : "", + hasEmptyLineFollowingFirstArg ? hardline : line, + hasEmptyLineFollowingFirstArg ? hardline : "", + printedArguments.slice(1), + ]; + } + + if (shouldGroupLast && isLast) { + printedExpanded = [ + ...printedArguments.slice(0, -1), + print([], { expandLastArg: true }), + ]; + } + }, argumentsKey); + + const somePrintedArgumentsWillBreak = printedArguments.some(willBreak); + const simpleConcat = ["(", ...printedExpanded, ")"]; + + return [ + somePrintedArgumentsWillBreak ? breakParent : "", + conditionalGroup( + [ + !somePrintedArgumentsWillBreak + ? simpleConcat + : ifBreak(allArgsBrokenOut(), simpleConcat), + shouldGroupFirst + ? [ + "(", + group(printedExpanded[0], { shouldBreak: true }), + ...printedExpanded.slice(1), + ")", + ] + : [ + "(", + ...printedArguments.slice(0, -1), + group(getLast(printedExpanded), { + shouldBreak: true, + }), + ")", + ], + group( + [ + "(", + indent([line, ...printedArguments]), + ifBreak(maybeTrailingComma), + line, + ")", + ], + { shouldBreak: true } + ), + ], + { shouldBreak } + ), + ]; + } + + return group( + [ + "(", + indent([softline, ...printedArguments]), + ifBreak(maybeTrailingComma), + softline, + ")", + ], + { + shouldBreak: printedArguments.some(willBreak) || anyArgEmptyLine, + } + ); +} + +function shouldInlineRetifFalseExpression(node) { + return node.kind === "array" && node.items.length !== 0; +} + +function shouldInlineLogicalExpression(node) { + return node.right.kind === "array" && node.right.items.length !== 0; +} + +// For binary expressions to be consistent, we need to group +// subsequent operators with the same precedence level under a single +// group. Otherwise they will be nested such that some of them break +// onto new lines but not all. Operators with the same precedence +// level should either all break or not. Because we group them by +// precedence level and the AST is structured based on precedence +// level, things are naturally broken up correctly, i.e. `&&` is +// broken before `+`. +function printBinaryExpression( + path, + print, + options, + isNested, + isInsideParenthesis +) { + let parts = []; + const { node } = path; + + if (node.kind === "bin") { + // Put all operators with the same precedence level in the same + // group. The reason we only need to do this with the `left` + // expression is because given an expression like `1 + 2 - 3`, it + // is always parsed like `((1 + 2) - 3)`, meaning the `left` side + // is where the rest of the expression will exist. Binary + // expressions on the right side mean they have a difference + // precedence level and should be treated as a separate group, so + // print them normally. (This doesn't hold for the `**` operator, + // which is unique in that it is right-associative.) + if (shouldFlatten(node.type, node.left.type)) { + // Flatten them out by recursively calling this function. + parts = parts.concat( + path.call( + () => + printBinaryExpression( + path, + print, + options, + /* isNested */ true, + isInsideParenthesis + ), + "left" + ) + ); + } else { + parts.push(print("left")); + } + + const shouldInline = shouldInlineLogicalExpression(node); + + const right = shouldInline + ? [node.type, " ", print("right")] + : [node.type, line, print("right")]; + + // If there's only a single binary expression, we want to create a group + // in order to avoid having a small right part like -1 be on its own line. + const { parent } = path; + const shouldGroup = + !(isInsideParenthesis && ["||", "&&"].includes(node.type)) && + getNodeKindIncludingLogical(parent) !== + getNodeKindIncludingLogical(node) && + getNodeKindIncludingLogical(node.left) !== + getNodeKindIncludingLogical(node) && + getNodeKindIncludingLogical(node.right) !== + getNodeKindIncludingLogical(node); + + const shouldNotHaveWhitespace = + isDocNode(node.left) || + (node.left.kind === "bin" && isDocNode(node.left.right)); + + parts.push( + shouldNotHaveWhitespace ? "" : " ", + shouldGroup ? group(right) : right + ); + + // The root comments are already printed, but we need to manually print + // the other ones since we don't call the normal print on bin, + // only for the left and right parts + if (isNested && node.comments) { + parts = printAllComments(path, () => parts, options); + } + } else { + // Our stopping case. Simply print the node normally. + parts.push(print()); + } + + return parts; +} + +function printLookupNodes(path, options, print) { + const { node } = path; + + switch (node.kind) { + case "propertylookup": + return printPropertyLookup(path, options, print); + case "nullsafepropertylookup": + return printNullsafePropertyLookup(path, options, print); + case "staticlookup": + return printStaticLookup(path, options, print); + case "offsetlookup": + return printOffsetLookup(path, options, print); + /* c8 ignore next 2 */ + default: + throw new Error(`Have not implemented lookup kind ${node.kind} yet.`); + } +} + +function getEncapsedQuotes(node, { opening = true } = {}) { + if (node.type === "heredoc") { + return opening ? `<<<${node.label}` : node.label; + } + + const quotes = { + string: '"', + shell: "`", + }; + + if (quotes[node.type]) { + return quotes[node.type]; + } + + /* c8 ignore next */ + throw new Error(`Unimplemented encapsed type ${node.type}`); +} + +function printArrayItems(path, options, print) { + const printedElements = []; + let separatorParts = []; + + path.each(({ node }) => { + printedElements.push(separatorParts); + printedElements.push(group(print())); + + separatorParts = [",", line]; + + if (node && isNextLineEmpty(options.originalText, locEnd(node))) { + separatorParts.push(softline); + } + }, "items"); + + return printedElements; +} + +// Wrap parts into groups by indexes. +// It is require to have same indent on lines for all parts into group. +// The value of `alignment` option indicates how many spaces must be before each part. +// +// Example: +//
+// +//
+function wrapPartsIntoGroups(parts, indexes) { + if (indexes.length === 0) { + return parts; + } + + let lastEnd = 0; + + return indexes.reduce((accumulator, index) => { + const { start, end, alignment, before, after } = index; + const printedPartsForGrouping = [ + before || "", + ...parts.slice(start, end), + after || "", + ]; + const newArray = accumulator.concat( + parts.slice(lastEnd, start), + alignment + ? dedentToRoot( + group( + align(new Array(alignment).join(" "), printedPartsForGrouping) + ) + ) + : group(printedPartsForGrouping), + end === parts.length - 1 ? parts.slice(end) : "" + ); + + lastEnd = end; + + return newArray; + }, []); +} + +function printLines(path, options, print, childrenAttribute = "children") { + const { node, parent: parentNode } = path; + + let lastInlineIndex = -1; + + const parts = []; + const groupIndexes = []; + + path.map(() => { + const { + node: childNode, + next: nextNode, + isFirst: isFirstNode, + isLast: isLastNode, + index, + } = path; + + const isInlineNode = childNode.kind === "inline"; + const printedPath = print(); + const canPrintBlankLine = + !isLastNode && + !isInlineNode && + (nextNode && nextNode.kind === "case" + ? !isFirstChildrenInlineNode(path) + : nextNode && nextNode.kind !== "inline"); + + let printed = [ + printedPath, + canPrintBlankLine ? hardline : "", + canPrintBlankLine && + isNextLineEmpty(options.originalText, locEnd(childNode)) + ? hardline + : "", + ]; + + const isBlockNestedNode = + node.kind === "block" && + parentNode && + ["function", "closure", "method", "try", "catch"].includes( + parentNode.kind + ); + + let beforeCloseTagInlineNode = isBlockNestedNode && isFirstNode ? "" : " "; + + if (isInlineNode || (!isInlineNode && isLastNode && lastInlineIndex >= 0)) { + const prevLastInlineIndex = lastInlineIndex; + + if (isInlineNode) { + lastInlineIndex = index; + } + + const shouldCreateGroup = + (isInlineNode && !isFirstNode) || (!isInlineNode && isLastNode); + + if (shouldCreateGroup) { + const start = + (isInlineNode ? prevLastInlineIndex : lastInlineIndex) + 1; + const end = isLastNode && !isInlineNode ? index + 1 : index; + const prevInlineNode = + path.siblings[isInlineNode ? prevLastInlineIndex : lastInlineIndex]; + const alignment = prevInlineNode + ? getAlignment(prevInlineNode.raw) + : ""; + const shouldBreak = end - start > 1; + const before = shouldBreak + ? (isBlockNestedNode && !prevInlineNode) || + (isProgramLikeNode(node) && start === 0) + ? "" + : hardline + : ""; + const after = + shouldBreak && childNode.kind !== "halt" + ? isBlockNestedNode && isLastNode + ? "" + : hardline + : ""; + + if (shouldBreak) { + beforeCloseTagInlineNode = ""; + } + + groupIndexes.push({ start, end, alignment, before, after }); + } + } + + if (isInlineNode) { + const openTag = + nextNode && nextNode.kind === "echo" && nextNode.shortForm + ? "", + ] + : isProgramLikeNode(node) && isFirstNode && node.kind !== "namespace" + ? "" + : [beforeCloseTagInlineNode, "?>"]; + + //FIXME getNode is used to get ancestors, but it seems this means to get next sibling? + const nextV = path.getNode(index + 1); + const skipLastComment = nextV && nextV.children && nextV.children.length; + + const afterInline = + childNode.comments && childNode.comments.length + ? [ + openTag, + hardline, + skipLastComment ? printComments(childNode.comments, options) : "", + hardline, + ] + : isProgramLikeNode(node) && isLastNode + ? "" + : [openTag, " "]; + + printed = [beforeInline, printed, afterInline]; + } + + parts.push(printed); + }, childrenAttribute); + + const wrappedParts = wrapPartsIntoGroups(parts, groupIndexes); + + if (node.kind === "program" && !node.extra.parseAsEval) { + const parts = []; + + const [firstNode] = node.children; + const hasStartTag = !firstNode || firstNode.kind !== "inline"; + + if (hasStartTag) { + const between = options.originalText.trim().match(/^<\?(php|=)(\s+)?\S/); + const afterOpenTag = [ + between && between[2] && between[2].includes("\n") + ? [hardline, between[2].split("\n").length > 2 ? hardline : ""] + : " ", + node.comments ? printComments(node.comments, options) : "", + ]; + + const shortEcho = + firstNode && firstNode.kind === "echo" && firstNode.shortForm; + parts.push([shortEcho ? "\n?$/.test(options.originalText); + + if (hasEndTag) { + const lastNode = getLast(node.children); + const beforeCloseTag = lastNode + ? [ + hasNewlineInRange( + options.originalText.trimEnd(), + locEnd(lastNode), + locEnd(node) + ) + ? !( + lastNode.kind === "inline" && + lastNode.comments && + lastNode.comments.length + ) + ? hardline + : "" + : " ", + isNextLineEmpty(options.originalText, locEnd(lastNode)) + ? hardline + : "", + ] + : node.comments + ? hardline + : ""; + + parts.push(lineSuffix([beforeCloseTag, "?>"])); + } + + return parts; + } + + return wrappedParts; +} + +function printStatements(path, options, print, childrenAttribute) { + return path.map(({ node, isLast }) => { + const parts = []; + + parts.push(print()); + + if (!isLast) { + parts.push(hardline); + + if (isNextLineEmpty(options.originalText, locEnd(node))) { + parts.push(hardline); + } + } + + return parts; + }, childrenAttribute); +} + +function printClassPart( + path, + options, + print, + part = "extends", + beforePart = " ", + afterPart = " " +) { + const value = path.node[part]; + const printedBeforePart = hasDanglingComments(value) + ? [ + hardline, + path.call(() => printDanglingComments(path, options, true), part), + hardline, + ] + : beforePart; + const printedPartItems = Array.isArray(value) + ? group( + join( + ",", + path.map(({ node }) => { + const printedPart = print(); + // Check if any of the implements nodes have comments + return hasDanglingComments(node) + ? [ + hardline, + printDanglingComments(path, options, true), + hardline, + printedPart, + ] + : [afterPart, printedPart]; + }, part) + ) + ) + : [afterPart, print(part)]; + + return indent([ + printedBeforePart, + part, + willBreak(printedBeforePart) ? indent(printedPartItems) : printedPartItems, + ]); +} + +function printAttrs(path, options, print, { inline = false } = {}) { + const allAttrs = []; + if (!path.node.attrGroups) { + return []; + } + path.each(() => { + const attrGroup = ["#["]; + if (!inline && allAttrs.length > 0) { + allAttrs.push(hardline); + } + attrGroup.push(softline); + path.each(() => { + const attrNode = path.node; + if (attrGroup.length > 2) { + attrGroup.push(",", line); + } + const attrStmt = [attrNode.name]; + if (attrNode.args.length > 0) { + attrStmt.push(printArgumentsList(path, options, print, "args")); + } + attrGroup.push(group(attrStmt)); + }, "attrs"); + allAttrs.push( + group([ + indent(attrGroup), + ifBreak(shouldPrintComma(options, 8.0) ? "," : ""), + softline, + "]", + inline ? ifBreak(softline, " ") : "", + ]) + ); + }, "attrGroups"); + if (allAttrs.length === 0) { + return []; + } + return [...allAttrs, inline ? "" : hardline]; +} + +function printClass(path, options, print) { + const { node } = path; + const isAnonymousClass = node.kind === "class" && node.isAnonymous; + const attrs = printAttrs(path, options, print, { inline: isAnonymousClass }); + const declaration = isAnonymousClass ? [] : [...attrs]; + + if (node.isFinal) { + declaration.push("final "); + } + + if (node.isAbstract) { + declaration.push("abstract "); + } + + if (node.isReadonly) { + declaration.push("readonly "); + } + + // `new` print `class` keyword with arguments + declaration.push(isAnonymousClass ? "" : node.kind); + + if (node.name) { + declaration.push(" ", print("name")); + } + + if (node.kind === "enum" && node.valueType) { + declaration.push(": ", print("valueType")); + } + + // Only `class` can have `extends` and `implements` + if (node.extends && node.implements) { + declaration.push( + conditionalGroup( + [ + [ + printClassPart(path, options, print, "extends"), + printClassPart(path, options, print, "implements"), + ], + [ + printClassPart(path, options, print, "extends"), + printClassPart(path, options, print, "implements", " ", hardline), + ], + [ + printClassPart(path, options, print, "extends", hardline, " "), + printClassPart( + path, + options, + print, + "implements", + hardline, + node.implements.length > 1 ? hardline : " " + ), + ], + ], + { + shouldBreak: hasDanglingComments(node.extends), + } + ) + ); + } else { + if (node.extends) { + declaration.push( + conditionalGroup([ + printClassPart(path, options, print, "extends"), + printClassPart(path, options, print, "extends", " ", hardline), + printClassPart( + path, + options, + print, + "extends", + hardline, + node.extends.length > 1 ? hardline : " " + ), + ]) + ); + } + + if (node.implements) { + declaration.push( + conditionalGroup([ + printClassPart(path, options, print, "implements"), + printClassPart(path, options, print, "implements", " ", hardline), + printClassPart( + path, + options, + print, + "implements", + hardline, + node.implements.length > 1 ? hardline : " " + ), + ]) + ); + } + } + + const hasEmptyClassBody = + node.body && node.body.length === 0 && !hasDanglingComments(node); + + const printedDeclaration = group([ + group(declaration), + shouldPrintHardlineForOpenBrace(options) && !hasEmptyClassBody + ? isAnonymousClass + ? line + : hardline + : " ", + ]); + + const printedBody = [ + "{", + indent([ + hasEmptyClassBody ? "" : hardline, + printStatements(path, options, print, "body"), + ]), + printDanglingComments(path, options, true), + hasEmptyClassBody ? "" : hardline, + "}", + ]; + + return [printedDeclaration, printedBody]; +} + +function printFunction(path, options, print) { + const { node } = path; + const declAttrs = printAttrs(path, options, print, { + inline: node.kind === "closure", + }); + const declaration = []; + + if (node.isFinal) { + declaration.push("final "); + } + + if (node.isAbstract) { + declaration.push("abstract "); + } + + if (node.visibility) { + declaration.push(node.visibility, " "); + } + + if (node.isStatic) { + declaration.push("static "); + } + + declaration.push("function "); + + if (node.byref) { + declaration.push("&"); + } + + if (node.name) { + declaration.push(print("name")); + } + + declaration.push(printArgumentsList(path, options, print)); + + if (node.uses && node.uses.length > 0) { + declaration.push( + group([" use ", printArgumentsList(path, options, print, "uses")]) + ); + } + + if (node.type) { + declaration.push([ + ": ", + hasDanglingComments(node.type) + ? [ + path.call(() => printDanglingComments(path, options, true), "type"), + " ", + ] + : "", + node.nullable ? "?" : "", + print("type"), + ]); + } + + const printedDeclaration = declaration; + + if (!node.body) { + return [...declAttrs, printedDeclaration]; + } + + const printedBody = [ + "{", + indent([hasEmptyBody(path) ? "" : hardline, print("body")]), + hasEmptyBody(path) ? "" : hardline, + "}", + ]; + + const isClosure = node.kind === "closure"; + if (isClosure) { + return [...declAttrs, printedDeclaration, " ", printedBody]; + } + + if (node.arguments.length === 0) { + return [ + ...declAttrs, + printedDeclaration, + shouldPrintHardlineForOpenBrace(options) && !hasEmptyBody(path) + ? hardline + : " ", + printedBody, + ]; + } + + const willBreakDeclaration = declaration.some(willBreak); + + if (willBreakDeclaration) { + return [...declAttrs, printedDeclaration, " ", printedBody]; + } + + return [ + ...declAttrs, + conditionalGroup([ + [ + printedDeclaration, + shouldPrintHardlineForOpenBrace(options) && !hasEmptyBody(path) + ? hardline + : " ", + printedBody, + ], + [printedDeclaration, " ", printedBody], + ]), + ]; +} + +function printBodyControlStructure( + path, + options, + print, + bodyProperty = "body" +) { + const { node } = path; + + if (!node[bodyProperty]) { + return ";"; + } + + const printedBody = print(bodyProperty); + + return [ + node.shortForm ? ":" : " {", + indent( + node[bodyProperty].kind !== "block" || + (node[bodyProperty].children && + node[bodyProperty].children.length > 0) || + (node[bodyProperty].comments && node[bodyProperty].comments.length > 0) + ? [ + shouldPrintHardLineAfterStartInControlStructure(path) + ? node.kind === "switch" + ? " " + : "" + : hardline, + printedBody, + ] + : "" + ), + node.kind === "if" && bodyProperty === "body" + ? "" + : [ + shouldPrintHardLineBeforeEndInControlStructure(path) ? hardline : "", + node.shortForm ? ["end", node.kind, ";"] : "}", + ], + ]; +} + +function printAssignment( + leftNode, + printedLeft, + operator, + rightNode, + printedRight, + hasRef, + options +) { + if (!rightNode) { + return printedLeft; + } + + const printed = printAssignmentRight( + leftNode, + rightNode, + printedRight, + hasRef, + options + ); + + return group([printedLeft, operator, printed]); +} + +function isLookupNodeChain(node) { + if (!isLookupNode(node)) { + return false; + } + + if (node.what.kind === "variable" || isReferenceLikeNode(node.what)) { + return true; + } + + return isLookupNodeChain(node.what); +} + +function printAssignmentRight( + leftNode, + rightNode, + printedRight, + hasRef, + options +) { + const ref = hasRef ? "&" : ""; + + if (hasLeadingOwnLineComment(options.originalText, rightNode)) { + return indent([hardline, ref, printedRight]); + } + + const pureRightNode = rightNode.kind === "cast" ? rightNode.expr : rightNode; + + const canBreak = + (pureRightNode.kind === "bin" && + !shouldInlineLogicalExpression(pureRightNode)) || + (pureRightNode.kind === "retif" && + ((!pureRightNode.trueExpr && + !shouldInlineRetifFalseExpression(pureRightNode.falseExpr)) || + (pureRightNode.test.kind === "bin" && + !shouldInlineLogicalExpression(pureRightNode.test)))) || + ((leftNode.kind === "variable" || + leftNode.kind === "string" || + isLookupNode(leftNode)) && + ((pureRightNode.kind === "string" && !stringHasNewLines(pureRightNode)) || + isLookupNodeChain(pureRightNode))); + + if (canBreak) { + return group(indent([line, ref, printedRight])); + } + + return [" ", ref, printedRight]; +} + +function needsHardlineAfterDanglingComment(node) { + if (!node.comments) { + return false; + } + + const lastDanglingComment = getLast( + node.comments.filter((comment) => !comment.leading && !comment.trailing) + ); + + return lastDanglingComment && !isBlockComment(lastDanglingComment); +} + +function stringHasNewLines(node) { + return node.raw.includes("\n"); +} + +function isStringOnItsOwnLine(node, text) { + return ( + (node.kind === "string" || + (node.kind === "encapsed" && + (node.type === "string" || node.type === "shell"))) && + stringHasNewLines(node) && + !hasNewline(text, locStart(node), { backwards: true }) + ); +} + +function printComposedTypes(path, print, glue) { + return group( + path.map(({ isFirst }) => (isFirst ? [print()] : [glue, print()]), "types") + ); +} + +function printNode(path, options, print) { + const { node } = path; + + switch (node.kind) { + case "program": { + return group([ + printLines(path, options, print), + printDanglingComments( + path, + options, + /* sameIndent */ true, + (c) => !c.printed + ), + ]); + } + case "expressionstatement": + return print("expression"); + case "block": + return [ + printLines(path, options, print), + printDanglingComments(path, options, true), + ]; + case "declare": { + const printDeclareArguments = (path) => { + return join(", ", path.map(print, "directives")); + }; + + if (["block", "short"].includes(node.mode)) { + return [ + "declare(", + printDeclareArguments(path), + ")", + node.mode === "block" ? " {" : ":", + node.children.length > 0 + ? indent([hardline, printLines(path, options, print)]) + : "", + printDanglingComments(path, options), + hardline, + node.mode === "block" ? "}" : "enddeclare;", + ]; + } + + return [ + "declare(", + printDeclareArguments(path), + ")", + path.next?.kind === "inline" ? "" : ";", + ]; + } + case "declaredirective": + return [print("key"), "=", print("value")]; + case "namespace": + return [ + "namespace ", + node.name && typeof node.name === "string" + ? [node.name, node.withBrackets ? " " : ""] + : "", + node.withBrackets ? "{" : ";", + hasDanglingComments(node) + ? [" ", printDanglingComments(path, options, true)] + : "", + node.children.length > 0 + ? node.withBrackets + ? indent([hardline, printLines(path, options, print)]) + : [ + node.children[0].kind === "inline" + ? "" + : [ + hardline, + isNextLineEmptyAfterNamespace(options.originalText, node) + ? hardline + : "", + ], + printLines(path, options, print), + ] + : "", + node.withBrackets ? [hardline, "}"] : "", + ]; + case "usegroup": + return group([ + "use ", + node.type ? [node.type, " "] : "", + indent([ + node.name + ? [maybeStripLeadingSlashFromUse(node.name), "\\{", softline] + : "", + join([",", line], path.map(print, "items")), + ]), + node.name + ? [ifBreak(shouldPrintComma(options, 7.2) ? "," : ""), softline, "}"] + : "", + ]); + case "useitem": + return [ + node.type ? [node.type, " "] : "", + maybeStripLeadingSlashFromUse(node.name), + hasDanglingComments(node) + ? [" ", printDanglingComments(path, options, true)] + : "", + node.alias ? [" as ", print("alias")] : "", + ]; + case "class": + case "enum": + case "interface": + case "trait": + return printClass(path, options, print); + case "traitprecedence": + return [ + print("trait"), + "::", + print("method"), + " insteadof ", + join(", ", path.map(print, "instead")), + ]; + case "traitalias": + return [ + node.trait ? [print("trait"), "::"] : "", + node.method ? print("method") : "", + " as ", + join(" ", [ + ...(node.visibility ? [node.visibility] : []), + ...(node.as ? [print("as")] : []), + ]), + ]; + case "traituse": + return group([ + "use ", + indent(group(join([",", line], path.map(print, "traits")))), + node.adaptations + ? [ + " {", + node.adaptations.length > 0 + ? [ + indent([ + hardline, + printStatements(path, options, print, "adaptations"), + ]), + hardline, + ] + : hasDanglingComments(node) + ? [line, printDanglingComments(path, options, true), line] + : "", + "}", + ] + : "", + ]); + case "function": + case "closure": + case "method": + return printFunction(path, options, print); + case "arrowfunc": + return [ + node.parenthesizedExpression ? "(" : "", + ...printAttrs(path, options, print, { inline: true }), + node.isStatic ? "static " : "", + "fn", + printArgumentsList(path, options, print), + node.type ? [": ", node.nullable ? "?" : "", print("type")] : "", + " => ", + print("body"), + node.parenthesizedExpression ? ")" : "", + ]; + case "parameter": { + let promoted = ""; + if (node.flags === 1) { + promoted = "public "; + } else if (node.flags === 2) { + promoted = "protected "; + } else if (node.flags === 4) { + promoted = "private "; + } + const name = [ + ...printAttrs(path, options, print, { inline: true }), + promoted, + node.readonly ? "readonly " : "", + node.nullable ? "?" : "", + node.type ? [print("type"), " "] : "", + node.byref ? "&" : "", + node.variadic ? "..." : "", + "$", + print("name"), + ]; + + if (node.value) { + return group([ + name, + // see handleFunctionParameter() in ./comments.mjs - since there's + // no node to attach comments that fall in between the parameter name + // and value, we store them as dangling comments + hasDanglingComments(node) ? " " : "", + printDanglingComments(path, options, true), + " =", + printAssignmentRight( + node.name, + node.value, + print("value"), + false, + options + ), + ]); + } + + return name; + } + case "variadic": + return ["...", print("what")]; + case "property": + return group([ + node.readonly ? "readonly " : "", + node.type ? [node.nullable ? "?" : "", print("type"), " "] : "", + "$", + print("name"), + node.value + ? [ + " =", + printAssignmentRight( + node.name, + node.value, + print("value"), + false, + options + ), + ] + : "", + ]); + case "propertystatement": { + const attrs = []; + path.each(() => { + attrs.push(...printAttrs(path, options, print)); + }, "properties"); + const printed = path.map(print, "properties"); + + const hasValue = node.properties.some((property) => property.value); + + let firstProperty; + + if (printed.length === 1 && !node.properties[0].comments) { + [firstProperty] = printed; + } else if (printed.length > 0) { + // Indent first property + firstProperty = indent(printed[0]); + } + + const hasVisibility = node.visibility || node.visibility === null; + + return group([ + ...attrs, + hasVisibility + ? [node.visibility === null ? "var" : node.visibility, ""] + : "", + node.isStatic ? [hasVisibility ? " " : "", "static"] : "", + firstProperty ? [" ", firstProperty] : "", + indent( + printed.slice(1).map((p) => [",", hasValue ? hardline : line, p]) + ), + ]); + } + case "if": { + const parts = []; + const body = printBodyControlStructure(path, options, print, "body"); + const opening = group([ + "if (", + group([indent([softline, print("test")]), softline]), + ")", + body, + ]); + + parts.push( + opening, + isFirstChildrenInlineNode(path) || !node.body ? "" : hardline + ); + + if (node.alternate) { + parts.push(node.shortForm ? "" : "} "); + + const commentOnOwnLine = + (hasTrailingComment(node.body) && + node.body.comments.some( + (comment) => comment.trailing && !isBlockComment(comment) + )) || + needsHardlineAfterDanglingComment(node); + const elseOnSameLine = !commentOnOwnLine; + parts.push(elseOnSameLine ? "" : hardline); + + if (hasDanglingComments(node)) { + parts.push( + isNextLineEmpty(options.originalText, locEnd(node.body)) + ? hardline + : "", + printDanglingComments(path, options, true), + commentOnOwnLine ? hardline : " " + ); + } + + parts.push( + "else", + group( + node.alternate.kind === "if" + ? print("alternate") + : printBodyControlStructure(path, options, print, "alternate") + ) + ); + } else { + parts.push(node.body ? (node.shortForm ? "endif;" : "}") : ""); + } + + return parts; + } + case "do": + return [ + "do", + printBodyControlStructure(path, options, print, "body"), + " while (", + group([indent([softline, print("test")]), softline]), + ")", + ]; + case "while": + case "switch": + return group([ + node.kind, + " (", + group([indent([softline, print("test")]), softline]), + ")", + printBodyControlStructure(path, options, print, "body"), + ]); + case "for": { + const body = printBodyControlStructure(path, options, print, "body"); + + // We want to keep dangling comments above the loop to stay consistent. + // Any comment positioned between the for statement and the parentheses + // is going to be printed before the statement. + const dangling = printDanglingComments( + path, + options, + /* sameLine */ true + ); + const printedComments = dangling ? [dangling, softline] : ""; + + if (!node.init.length && !node.test.length && !node.increment.length) { + return [printedComments, group(["for (;;)", body])]; + } + + return [ + printedComments, + group([ + "for (", + group([ + indent([ + softline, + group(join([",", line], path.map(print, "init"))), + ";", + line, + group(join([",", line], path.map(print, "test"))), + ";", + line, + group(join([",", line], path.map(print, "increment"))), + ]), + softline, + ]), + ")", + body, + ]), + ]; + } + case "foreach": { + const body = printBodyControlStructure(path, options, print, "body"); + + // We want to keep dangling comments above the loop to stay consistent. + // Any comment positioned between the for statement and the parentheses + // is going to be printed before the statement. + const dangling = printDanglingComments( + path, + options, + /* sameLine */ true + ); + const printedComments = dangling ? [dangling, softline] : ""; + + return [ + printedComments, + group([ + "foreach (", + group([ + indent([ + softline, + print("source"), + line, + "as ", + group( + node.key + ? indent(join([" =>", line], [print("key"), print("value")])) + : print("value") + ), + ]), + softline, + ]), + ")", + body, + ]), + ]; + } + case "try": { + const parts = []; + + parts.push( + "try", + printBodyControlStructure(path, options, print, "body") + ); + + if (node.catches) { + parts.push(path.map(print, "catches")); + } + + if (node.always) { + parts.push( + " finally", + printBodyControlStructure(path, options, print, "always") + ); + } + + return parts; + } + case "catch": { + return [ + " catch", + node.what + ? [ + " (", + join(" | ", path.map(print, "what")), + node.variable ? [" ", print("variable")] : "", + ")", + ] + : "", + printBodyControlStructure(path, options, print, "body"), + ]; + } + case "case": + return [ + node.test + ? [ + "case ", + node.test.comments ? indent(print("test")) : print("test"), + ":", + ] + : "default:", + node.body + ? node.body.children && node.body.children.length + ? indent([ + isFirstChildrenInlineNode(path) ? "" : hardline, + print("body"), + ]) + : "" + : "", + ]; + case "break": + case "continue": + if (node.level) { + if (node.level.kind === "number" && node.level.value !== "1") { + return [`${node.kind} `, print("level")]; + } + + return node.kind; + } + + return node.kind; + case "call": { + // Multiline strings as single arguments + if ( + node.arguments.length === 1 && + isStringOnItsOwnLine(node.arguments[0], options.originalText) + ) { + return [ + print("what"), + "(", + join(", ", path.map(print, "arguments")), + ")", + ]; + } + + // chain: Call (*LookupNode (Call (*LookupNode (...)))) + if (isLookupNode(node.what)) { + return printMemberChain(path, options, print); + } + + return [print("what"), printArgumentsList(path, options, print)]; + } + case "new": { + const isAnonymousClassNode = + node.what && node.what.kind === "class" && node.what.isAnonymous; + + // Multiline strings as single arguments + if ( + !isAnonymousClassNode && + node.arguments.length === 1 && + isStringOnItsOwnLine(node.arguments[0], options.originalText) + ) { + return [ + "new ", + ...path.call(printAttrs, "what"), + print("what"), + "(", + join(", ", path.map(print, "arguments")), + ")", + ]; + } + + const parts = []; + + parts.push("new "); + + if (isAnonymousClassNode) { + parts.push( + node.what.leadingComments && + node.what.leadingComments[0].kind === "commentblock" + ? [printComments(node.what.leadingComments, options), " "] + : "", + ...path.call( + () => printAttrs(path, options, print, { inline: true }), + "what" + ), + "class", + node.arguments.length > 0 + ? [" ", printArgumentsList(path, options, print)] + : "", + group(print("what")) + ); + } else { + const isExpression = ["call", "offsetlookup"].includes(node.what.kind); + const printed = [ + isExpression ? "(" : "", + print("what"), + isExpression ? ")" : "", + printArgumentsList(path, options, print), + ]; + + parts.push(hasLeadingComment(node.what) ? indent(printed) : printed); + } + + return parts; + } + case "clone": + return [ + "clone ", + node.what.comments ? indent(print("what")) : print("what"), + ]; + case "propertylookup": + case "nullsafepropertylookup": + case "staticlookup": + case "offsetlookup": { + const { parent } = path; + + // TODO: Use `AstPath.findAncestor` when it's stable + let firstNonMemberParent; + let i = 0; + do { + firstNonMemberParent = path.getParentNode(i); + i++; + } while (firstNonMemberParent && isLookupNode(firstNonMemberParent)); + + const hasEncapsedAncestor = getAncestorNode(path, "encapsed"); + const shouldInline = + hasEncapsedAncestor || + (firstNonMemberParent && + (firstNonMemberParent.kind === "new" || + (firstNonMemberParent.kind === "assign" && + firstNonMemberParent.left.kind !== "variable"))) || + node.kind === "offsetlookup" || + ((isReferenceLikeNode(node.what) || node.what.kind === "variable") && + ["identifier", "variable", "encapsedpart"].includes( + node.offset.kind + ) && + parent && + !isLookupNode(parent)); + + return [ + print("what"), + shouldInline + ? printLookupNodes(path, options, print) + : group(indent([softline, printLookupNodes(path, options, print)])), + ]; + } + case "exit": + return group([ + node.useDie ? "die" : "exit", + "(", + node.expression + ? isStringOnItsOwnLine(node.expression, options.originalText) + ? print("expression") + : [indent([softline, print("expression")]), softline] + : printDanglingComments(path, options), + ")", + ]); + case "global": + return group([ + "global ", + indent(join([",", line], path.map(print, "items"))), + ]); + case "include": + return [ + node.require ? "require" : "include", + node.once ? "_once" : "", + " ", + node.target.comments ? indent(print("target")) : print("target"), + ]; + case "label": + return [print("name"), ":"]; + case "goto": + return ["goto ", print("label")]; + case "throw": + return [ + "throw ", + node.what.comments ? indent(print("what")) : print("what"), + ]; + case "silent": + return ["@", print("expr")]; + case "halt": + return [ + hasDanglingComments(node) + ? [ + printDanglingComments(path, options, /* sameIndent */ true), + hardline, + ] + : "", + "__halt_compiler();", + node.after, + ]; + case "eval": + return group([ + "eval(", + isStringOnItsOwnLine(node.source, options.originalText) + ? print("source") + : [indent([softline, print("source")]), softline], + ")", + ]); + case "echo": { + const printedArguments = path.map(print, "expressions"); + + let firstVariable; + + if (printedArguments.length === 1 && !node.expressions[0].comments) { + [firstVariable] = printedArguments; + } else if (printedArguments.length > 0) { + firstVariable = + isDocNode(node.expressions[0]) || node.expressions[0].comments + ? indent(printedArguments[0]) + : dedent(printedArguments[0]); + } + + return group([ + node.shortForm ? "" : "echo ", + firstVariable ? firstVariable : "", + indent(printedArguments.slice(1).map((p) => [",", line, p])), + ]); + } + case "print": { + return [ + "print ", + node.expression.comments + ? indent(print("expression")) + : print("expression"), + ]; + } + case "return": { + const parts = []; + + parts.push("return"); + + if (node.expr) { + const printedExpr = print("expr"); + + parts.push(" ", node.expr.comments ? indent(printedExpr) : printedExpr); + } + + if (hasDanglingComments(node)) { + parts.push( + " ", + printDanglingComments(path, options, /* sameIndent */ true) + ); + } + + return parts; + } + case "isset": + case "unset": + return group([ + node.kind, + printArgumentsList(path, options, print, "variables"), + ]); + case "empty": + return group([ + "empty(", + indent([softline, print("expression")]), + softline, + ")", + ]); + case "variable": { + const { parent, grandparent: parentParent } = path; + const ampersand = parent.kind === "assign" ? "" : node.byref ? "&" : ""; + const dollar = + (parent.kind === "encapsedpart" && + parent.syntax === "simple" && + parent.curly) || + (parentParent && + parent.kind === "offsetlookup" && + parentParent.kind === "encapsedpart" && + parentParent.syntax === "simple" && + parentParent.curly) + ? "" + : "$"; + const openCurly = node.curly ? "{" : ""; + const closeCurly = node.curly ? "}" : ""; + + return [ampersand, dollar, openCurly, print("name"), closeCurly]; + } + case "constantstatement": + case "classconstant": { + const attrs = printAttrs(path, options, print); + const printed = path.map(print, "constants"); + + let firstVariable; + + if (printed.length === 1 && !node.constants[0].comments) { + [firstVariable] = printed; + } else if (printed.length > 0) { + // Indent first item + firstVariable = indent(printed[0]); + } + + return group([ + ...attrs, + node.final ? "final " : "", + node.visibility ? [node.visibility, " "] : "", + "const", + node.type ? [node.nullable ? " ?" : " ", print("type")] : "", + firstVariable ? [" ", firstVariable] : "", + indent(printed.slice(1).map((p) => [",", hardline, p])), + ]); + } + case "constant": + return printAssignment( + node.name, + print("name"), + " =", + node.value, + print("value"), + false, + options + ); + case "static": { + const printed = path.map(print, "variables"); + + const hasValue = node.variables.some((item) => item.defaultValue); + + let firstVariable; + + if (printed.length === 1 && !node.variables[0].comments) { + [firstVariable] = printed; + } else if (printed.length > 0) { + // Indent first item + firstVariable = indent(printed[0]); + } + + return group([ + "static", + firstVariable ? [" ", firstVariable] : "", + indent( + printed.slice(1).map((p) => [",", hasValue ? hardline : line, p]) + ), + ]); + } + case "staticvariable": { + return printAssignment( + node.variable, + print("variable"), + " =", + node.defaultValue, + print("defaultValue"), + false, + options + ); + } + case "list": + case "array": { + const useShortForm = + (node.kind === "array" && options.phpVersion >= 5.4) || + (node.kind === "list" && (node.shortForm || options.phpVersion >= 7.1)); + const open = useShortForm ? "[" : [node.kind, "("]; + const close = useShortForm ? "]" : ")"; + + if (node.items.length === 0) { + if (!hasDanglingComments(node)) { + return [open, close]; + } + + return group([ + open, + printDanglingComments(path, options), + softline, + close, + ]); + } + + const lastElem = getLast(node.items); + + // PHP allows you to have empty elements in an array which + // changes its length based on the number of commas. The algorithm + // is that if the last argument is null, we need to force insert + // a comma to ensure PHP recognizes it. + // [,] === $arr; + // [1,] === $arr; + // [1,,] === $arr; + // + // Note that getLast returns null if the array is empty, but + // we already check for an empty array just above so we are safe + const needsForcedTrailingComma = lastElem && lastElem.kind === "noop"; + + const [firstProperty] = node.items + .filter((node) => node.kind !== "noop") + .sort((a, b) => locStart(a) - locStart(b)); + const isAssociative = !!(firstProperty && firstProperty.key); + const shouldBreak = + isAssociative && + firstProperty && + hasNewlineInRange( + options.originalText, + locStart(node), + locStart(firstProperty) + ); + + return group( + [ + open, + indent([softline, printArrayItems(path, options, print)]), + needsForcedTrailingComma ? "," : "", + ifBreak( + !needsForcedTrailingComma && shouldPrintComma(options, 5.0) + ? [ + lastElem && shouldPrintHardlineBeforeTrailingComma(lastElem) + ? hardline + : "", + ",", + ] + : "" + ), + printDanglingComments(path, options, true), + softline, + close, + ], + { shouldBreak } + ); + } + case "entry": { + const ref = node.byRef ? "&" : ""; + const unpack = node.unpack ? "..." : ""; + return node.key + ? printAssignment( + node.key, + print("key"), + " =>", + node.value, + print("value"), + ref, + options + ) + : [ref, unpack, print("value")]; + } + case "yield": { + const printedKeyAndValue = [ + node.key ? [print("key"), " => "] : "", + print("value"), + ]; + + return [ + "yield", + node.key || node.value ? " " : "", + node.value && node.value.comments + ? indent(printedKeyAndValue) + : printedKeyAndValue, + ]; + } + case "yieldfrom": + return [ + "yield from ", + node.value.comments ? indent(print("value")) : print("value"), + ]; + case "unary": + return [node.type, print("what")]; + case "pre": + return [node.type + node.type, print("what")]; + case "post": + return [print("what"), node.type + node.type]; + case "cast": + return [ + "(", + node.type, + ") ", + node.expr.comments ? indent(print("expr")) : print("expr"), + ]; + case "assignref": + case "assign": { + const hasRef = node.kind === "assignref"; + + return printAssignment( + node.left, + print("left"), + [" ", hasRef ? "=" : node.operator], + node.right, + print("right"), + hasRef, + options + ); + } + case "bin": { + const { parent, grandparent: parentParent } = path; + const isInsideParenthesis = + node !== parent.body && + (parent.kind === "if" || + parent.kind === "while" || + parent.kind === "switch" || + parent.kind === "do"); + + const parts = printBinaryExpression( + path, + print, + options, + /* isNested */ false, + isInsideParenthesis + ); + + // if ( + // $this->hasPlugin('dynamicImports') && $this->lookahead()->type === tt->parenLeft + // ) { + // + // looks super weird, we want to break the children if the parent breaks + // + // if ( + // $this->hasPlugin('dynamicImports') && + // $this->lookahead()->type === tt->parenLeft + // ) { + if (isInsideParenthesis) { + return parts; + } + + // Break between the parens in unaries or in a member expression, i.e. + // + // ( + // a && + // b && + // c + // )->call() + if ( + parent.kind === "unary" || + (isLookupNode(parent) && parent.kind !== "offsetlookup") + ) { + return group([indent([softline, ...parts]), softline]); + } + + // Avoid indenting sub-expressions in some cases where the first sub-expression is already + // indented accordingly. We should indent sub-expressions where the first case isn't indented. + const shouldNotIndent = + (node !== parent.body && parent.kind === "for") || + (parent.kind === "retif" && + parentParent && + parentParent.kind !== "return"); + + const shouldIndentIfInlining = [ + "assign", + "property", + "constant", + "staticvariable", + "entry", + ].includes(parent.kind); + + const samePrecedenceSubExpression = + node.left.kind === "bin" && shouldFlatten(node.type, node.left.type); + + if ( + shouldNotIndent || + (shouldInlineLogicalExpression(node) && !samePrecedenceSubExpression) || + (!shouldInlineLogicalExpression(node) && shouldIndentIfInlining) + ) { + return group(parts); + } + + const rest = parts.slice(1); + + return group([ + // Don't include the initial expression in the indentation + // level. The first item is guaranteed to be the first + // left-most expression. + parts.length > 0 ? parts[0] : "", + indent(rest), + ]); + } + case "retif": { + const parts = []; + const { parent } = path; + + // TODO: Use `AstPath.findAncestor` when it's stable + // Find the outermost non-retif parent, and the outermost retif parent. + let currentParent; + let i = 0; + + do { + currentParent = path.getParentNode(i); + i++; + } while (currentParent && currentParent.kind === "retif"); + const firstNonRetifParent = currentParent || parent; + + const printedFalseExpr = + node.falseExpr.kind === "bin" + ? indent(print("falseExpr")) + : print("falseExpr"); + const part = [ + node.trueExpr ? line : " ", + "?", + node.trueExpr + ? [ + " ", + node.trueExpr.kind === "bin" + ? indent(print("trueExpr")) + : print("trueExpr"), + line, + ] + : "", + ":", + node.trueExpr + ? [" ", printedFalseExpr] + : [ + shouldInlineRetifFalseExpression(node.falseExpr) ? " " : line, + printedFalseExpr, + ], + ]; + + parts.push(part); + + // We want a whole chain of retif to all break if any of them break. + const maybeGroup = (doc) => + parent === firstNonRetifParent ? group(doc) : doc; + + // Break the closing parens to keep the chain right after it: + // ($a + // ? $b + // : $c + // )->call() + const parentParent = path.grandparent; + const pureParent = + parent.kind === "cast" && parentParent ? parentParent : parent; + const breakLookupNodes = [ + "propertylookup", + "nullsafepropertylookup", + "staticlookup", + ]; + const breakClosingParens = breakLookupNodes.includes(pureParent.kind); + + const printedTest = print("test"); + + if (!node.trueExpr) { + const printed = [ + printedTest, + pureParent.kind === "bin" || + ["print", "echo", "return", "include"].includes( + firstNonRetifParent.kind + ) + ? indent(parts) + : parts, + ]; + + // Break between the parens in unaries or in a lookup nodes, i.e. + // + // ( + // a ?: + // b ?: + // c + // )->call() + if ( + (pureParent.kind === "call" && pureParent.what === node) || + pureParent.kind === "unary" || + (isLookupNode(pureParent) && pureParent.kind !== "offsetlookup") + ) { + return group([indent([softline, printed]), softline]); + } + + return maybeGroup(printed); + } + + return maybeGroup([ + node.test.kind === "retif" ? indent(printedTest) : printedTest, + indent(parts), + breakClosingParens ? softline : "", + ]); + } + case "boolean": + return node.value ? "true" : "false"; + case "number": + return printNumber(node.value); + case "string": { + const { parent } = path; + + if (parent.kind === "encapsedpart") { + const parentParent = path.grandparent; + let closingTagIndentation = 0; + const flexible = options.phpVersion >= 7.3; + let linebreak = literalline; + if (parentParent.type === "heredoc") { + linebreak = flexible ? hardline : literalline; + const lines = parentParent.raw.split("\n"); + closingTagIndentation = lines[lines.length - 1].search(/\S/); + if (closingTagIndentation === -1) { + closingTagIndentation = lines[lines.length - 2].search(/\S/); + } + } + return join( + linebreak, + node.raw + .split("\n") + .map((s, i) => + i > 0 || node.loc.start.column === 0 + ? s.substring(closingTagIndentation) + : s + ) + ); + } + + const quote = useDoubleQuote(node, options) ? '"' : "'"; + + let stringValue = node.raw; + + if (node.raw[0] === "b") { + stringValue = stringValue.slice(1); + } + + // We need to strip out the quotes from the raw value + if (['"', "'"].includes(stringValue[0])) { + stringValue = stringValue.substr(1); + } + + if (['"', "'"].includes(stringValue[stringValue.length - 1])) { + stringValue = stringValue.substr(0, stringValue.length - 1); + } + + return [ + node.raw[0] === "b" ? "b" : "", + quote, + join(literalline, stringValue.split("\n")), + quote, + ]; + } + case "intersectiontype": { + return printComposedTypes(path, print, "&"); + } + case "uniontype": { + return printComposedTypes(path, print, "|"); + } + case "encapsedpart": { + const open = + (node.syntax === "simple" && node.curly) || node.syntax === "complex" + ? [node.curly ? "$" : "", "{"] + : ""; + const close = + (node.syntax === "simple" && node.curly) || node.syntax === "complex" + ? "}" + : ""; + + return [open, print("expression"), close]; + } + case "encapsed": + switch (node.type) { + case "string": + case "shell": + case "heredoc": { + const flexible = options.phpVersion >= 7.3; + const linebreak = flexible ? hardline : literalline; + return [ + getEncapsedQuotes(node), + // Respect `indent` for `heredoc` nodes + node.type === "heredoc" ? linebreak : "", + ...path.map(print, "value"), + getEncapsedQuotes(node, { opening: false }), + node.type === "heredoc" && docShouldHaveTrailingNewline(path) + ? hardline + : "", + ]; + } + /* c8 ignore next 2 */ + default: + throw new Error(`Have not implemented kind ${node.type} yet.`); + } + case "inline": + return join( + literalline, + node.raw.replace("___PSEUDO_INLINE_PLACEHOLDER___", "").split("\n") + ); + case "magic": + return node.value; + case "nowdoc": { + const flexible = options.phpVersion >= 7.3; + const linebreak = flexible ? hardline : literalline; + return [ + "<<<'", + node.label, + "'", + linebreak, + join(linebreak, node.value.split("\n")), + linebreak, + node.label, + docShouldHaveTrailingNewline(path) ? hardline : "", + ]; + } + case "name": + return [node.resolution === "rn" ? "namespace\\" : "", node.name]; + case "literal": + return print("value"); + case "parentreference": + return "parent"; + case "selfreference": + return "self"; + case "staticreference": + return "static"; + case "typereference": + return node.name; + case "nullkeyword": + return "null"; + case "identifier": { + const { parent } = path; + + if (parent.kind === "method") { + node.name = normalizeMagicMethodName(node.name); + } + + return print("name"); + } + case "match": { + const arms = path.map(() => { + const armNode = path.node; + + const maybeLeadingComment = hasLeadingComment(armNode) + ? [printComments(armNode.leadingComments, options), hardline] + : []; + const maybeTrailingComma = + !path.isLast || options.trailingCommaPHP ? "," : ""; + const maybeTrailingComment = hasTrailingComment(armNode) + ? [ + " ", + printComments( + armNode.comments.filter((c) => c.trailing), + options + ), + ] + : []; + + const conds = + armNode.conds === null + ? "default" + : path.map( + ({ isFirst }) => [",", line, print()].slice(isFirst ? 2 : 0), + "conds" + ); + const body = print("body"); + const maybeEmptyLineBetweenArms = + !path.isFirst && + isPreviousLineEmpty(options.originalText, locStart(armNode)) + ? hardline + : ""; + + return [ + "", + hardline, + maybeEmptyLineBetweenArms, + ...maybeLeadingComment, + group([ + group([conds, indent(line)]), + "=> ", + body, + maybeTrailingComma, + ...maybeTrailingComment, + ]), + ].slice(!path.isFirst ? 0 : 1); + }, "arms"); + return group([ + "match (", + group([indent([softline, print("cond")]), softline]), + ") {", + group(indent([...arms])), + " ", + softline, + "}", + ]); + } + + case "noop": + return node.comments ? printComments(node.comments, options) : ""; + case "namedargument": + return [node.name, ": ", print("value")]; + + case "enumcase": + return group([ + "case ", + print("name"), + node.value + ? [ + " =", + printAssignmentRight( + node.name, + node.value, + print("value"), + false, + options + ), + ] + : "", + ]); + case "variadicplaceholder": + return "..."; + + /* c8 ignore next 3 */ + case "error": + default: + throw new Error(`Have not implemented kind '${node.kind}' yet.`); + } +} + +export default genericPrint; diff --git a/frontend/src/utils/prettier/plugins/php/src/util.mjs b/frontend/src/utils/prettier/plugins/php/src/util.mjs new file mode 100644 index 0000000..b4bf9e4 --- /dev/null +++ b/frontend/src/utils/prettier/plugins/php/src/util.mjs @@ -0,0 +1,743 @@ +import { util as prettierUtil } from "prettier"; +import { locStart } from "./loc.mjs"; + +const { hasNewline, skipEverythingButNewLine, skipNewline } = prettierUtil; + +function printNumber(rawNumber) { + return ( + rawNumber + .toLowerCase() + // Remove unnecessary plus and zeroes from scientific notation. + .replace(/^([+-]?[\d.]+e)(?:\+|(-))?0*(\d)/, "$1$2$3") + // Remove unnecessary scientific notation (1e0). + .replace(/^([+-]?[\d.]+)e[+-]?0+$/, "$1") + // Make sure numbers always start with a digit. + .replace(/^([+-])?\./, "$10.") + // Remove extraneous trailing decimal zeroes. + .replace(/(\.\d+?)0+(?=e|$)/, "$1") + // Remove unnecessary .e notation + .replace(/\.(?=e)/, "") + ); +} + +// http://php.net/manual/en/language.operators.precedence.php +const PRECEDENCE = new Map( + [ + ["or"], + ["xor"], + ["and"], + [ + "=", + "+=", + "-=", + "*=", + "**=", + "/=", + ".=", + "%=", + "&=", + "|=", + "^=", + "<<=", + ">>=", + ], + ["??"], + ["||"], + ["&&"], + ["|"], + ["^"], + ["&"], + ["==", "===", "!=", "!==", "<>", "<=>"], + ["<", ">", "<=", ">="], + [">>", "<<"], + ["+", "-", "."], + ["*", "/", "%"], + ["!"], + ["instanceof"], + ["++", "--", "~"], + ["**"], + ].flatMap((operators, index) => + operators.map((operator) => [operator, index]) + ) +); +function getPrecedence(operator) { + return PRECEDENCE.get(operator); +} + +const equalityOperators = ["==", "!=", "===", "!==", "<>", "<=>"]; +const multiplicativeOperators = ["*", "/", "%"]; +const bitshiftOperators = [">>", "<<"]; + +function isBitwiseOperator(operator) { + return ( + !!bitshiftOperators[operator] || + operator === "|" || + operator === "^" || + operator === "&" + ); +} + +function shouldFlatten(parentOp, nodeOp) { + if (getPrecedence(nodeOp) !== getPrecedence(parentOp)) { + return false; + } + + // ** is right-associative + // x ** y ** z --> x ** (y ** z) + if (parentOp === "**") { + return false; + } + + // x == y == z --> (x == y) == z + if ( + equalityOperators.includes(parentOp) && + equalityOperators.includes(nodeOp) + ) { + return false; + } + + // x * y % z --> (x * y) % z + if ( + (nodeOp === "%" && multiplicativeOperators.includes(parentOp)) || + (parentOp === "%" && multiplicativeOperators.includes(nodeOp)) + ) { + return false; + } + + // x * y / z --> (x * y) / z + // x / y * z --> (x / y) * z + if ( + nodeOp !== parentOp && + multiplicativeOperators.includes(nodeOp) && + multiplicativeOperators.includes(parentOp) + ) { + return false; + } + + // x << y << z --> (x << y) << z + if ( + bitshiftOperators.includes(parentOp) && + bitshiftOperators.includes(nodeOp) + ) { + return false; + } + + return true; +} + +function nodeHasStatement(node) { + return [ + "block", + "program", + "namespace", + "class", + "enum", + "interface", + "trait", + "traituse", + "declare", + ].includes(node.kind); +} + +function getBodyFirstChild({ body }) { + if (!body) { + return null; + } + if (body.kind === "block") { + body = body.children; + } + return body[0]; +} + +function getNodeListProperty(node) { + const body = node.children || node.body || node.adaptations; + return Array.isArray(body) ? body : null; +} + +function getLast(arr) { + if (arr.length > 0) { + return arr[arr.length - 1]; + } + return null; +} + +function getPenultimate(arr) { + if (arr.length > 1) { + return arr[arr.length - 2]; + } + return null; +} + +function isFirstChildrenInlineNode(path) { + const { node } = path; + + if (node.kind === "program") { + const children = getNodeListProperty(node); + + if (!children || children.length === 0) { + return false; + } + + return children[0].kind === "inline"; + } + + if (node.kind === "switch") { + if (!node.body) { + return false; + } + + const children = getNodeListProperty(node.body); + + if (children.length === 0) { + return false; + } + + const [firstCase] = children; + + if (!firstCase.body) { + return false; + } + + const firstCaseChildren = getNodeListProperty(firstCase.body); + + if (firstCaseChildren.length === 0) { + return false; + } + + return firstCaseChildren[0].kind === "inline"; + } + + const firstChild = getBodyFirstChild(node); + + if (!firstChild) { + return false; + } + + return firstChild.kind === "inline"; +} + +function isDocNode(node) { + return ( + node.kind === "nowdoc" || + (node.kind === "encapsed" && node.type === "heredoc") + ); +} + +/** + * Heredoc/Nowdoc nodes need a trailing linebreak if they + * appear as function arguments or array elements + */ +function docShouldHaveTrailingNewline(path, recurse = 0) { + const node = path.getNode(recurse); + const parent = path.getNode(recurse + 1); + const parentParent = path.getNode(recurse + 2); + + if (!parent) { + return false; + } + + if ( + (parentParent && + ["call", "new", "echo"].includes(parentParent.kind) && + !["call", "array"].includes(parent.kind)) || + parent.kind === "parameter" + ) { + const lastIndex = parentParent.arguments.length - 1; + const index = parentParent.arguments.indexOf(parent); + + return index !== lastIndex; + } + + if (parentParent && parentParent.kind === "for") { + const initIndex = parentParent.init.indexOf(parent); + + if (initIndex !== -1) { + return initIndex !== parentParent.init.length - 1; + } + + const testIndex = parentParent.test.indexOf(parent); + + if (testIndex !== -1) { + return testIndex !== parentParent.test.length - 1; + } + + const incrementIndex = parentParent.increment.indexOf(parent); + + if (incrementIndex !== -1) { + return incrementIndex !== parentParent.increment.length - 1; + } + } + + if (parent.kind === "bin") { + return ( + parent.left === node || docShouldHaveTrailingNewline(path, recurse + 1) + ); + } + + if (parent.kind === "case" && parent.test === node) { + return true; + } + + if (parent.kind === "staticvariable") { + const lastIndex = parentParent.variables.length - 1; + const index = parentParent.variables.indexOf(parent); + + return index !== lastIndex; + } + + if (parent.kind === "entry") { + if (parent.key === node) { + return true; + } + + const lastIndex = parentParent.items.length - 1; + const index = parentParent.items.indexOf(parent); + + return index !== lastIndex; + } + + if (["call", "new"].includes(parent.kind)) { + const lastIndex = parent.arguments.length - 1; + const index = parent.arguments.indexOf(node); + + return index !== lastIndex; + } + + if (parent.kind === "echo") { + const lastIndex = parent.expressions.length - 1; + const index = parent.expressions.indexOf(node); + + return index !== lastIndex; + } + + if (parent.kind === "array") { + const lastIndex = parent.items.length - 1; + const index = parent.items.indexOf(node); + + return index !== lastIndex; + } + + if (parent.kind === "retif") { + return docShouldHaveTrailingNewline(path, recurse + 1); + } + + return false; +} + +function lineShouldEndWithSemicolon(path) { + const { node, parent: parentNode } = path; + if (!parentNode) { + return false; + } + // for single line control structures written in a shortform (ie without a block), + // we need to make sure the single body node gets a semicolon + if ( + ["for", "foreach", "while", "do", "if", "switch"].includes( + parentNode.kind + ) && + node.kind !== "block" && + node.kind !== "if" && + (parentNode.body === node || parentNode.alternate === node) + ) { + return true; + } + if (!nodeHasStatement(parentNode)) { + return false; + } + if (node.kind === "echo" && node.shortForm) { + return false; + } + if (node.kind === "traituse") { + return !node.adaptations; + } + if (node.kind === "method" && node.isAbstract) { + return true; + } + if (node.kind === "method") { + const { parent } = path; + if (parent && parent.kind === "interface") { + return true; + } + } + return [ + "expressionstatement", + "do", + "usegroup", + "classconstant", + "propertystatement", + "traitprecedence", + "traitalias", + "goto", + "constantstatement", + "enumcase", + "global", + "static", + "echo", + "unset", + "return", + "break", + "continue", + "throw", + ].includes(node.kind); +} + +function fileShouldEndWithHardline(path) { + const { node } = path; + const isProgramNode = node.kind === "program"; + const lastNode = node.children && getLast(node.children); + + if (!isProgramNode) { + return false; + } + + if (lastNode && ["halt", "inline"].includes(lastNode.kind)) { + return false; + } + + if ( + lastNode && + (lastNode.kind === "declare" || lastNode.kind === "namespace") + ) { + const lastNestedNode = + lastNode.children.length > 0 && getLast(lastNode.children); + + if (lastNestedNode && ["halt", "inline"].includes(lastNestedNode.kind)) { + return false; + } + } + + return true; +} + +function maybeStripLeadingSlashFromUse(name) { + const nameWithoutLeadingSlash = name.replace(/^\\/, ""); + if (nameWithoutLeadingSlash.indexOf("\\") !== -1) { + return nameWithoutLeadingSlash; + } + return name; +} + +function hasDanglingComments(node) { + return ( + node.comments && + node.comments.some((comment) => !comment.leading && !comment.trailing) + ); +} + +function isLookupNode(node) { + return ( + node.kind === "propertylookup" || + node.kind === "nullsafepropertylookup" || + node.kind === "staticlookup" || + node.kind === "offsetlookup" + ); +} + +function shouldPrintHardLineAfterStartInControlStructure(path) { + const { node } = path; + + if (["try", "catch"].includes(node.kind)) { + return false; + } + + return isFirstChildrenInlineNode(path); +} + +function shouldPrintHardLineBeforeEndInControlStructure(path) { + const { node } = path; + + if (["try", "catch"].includes(node.kind)) { + return true; + } + + if (node.kind === "switch") { + const children = getNodeListProperty(node.body); + + if (children.length === 0) { + return true; + } + + const lastCase = getLast(children); + + if (!lastCase.body) { + return true; + } + + const childrenInCase = getNodeListProperty(lastCase.body); + + if (childrenInCase.length === 0) { + return true; + } + + return childrenInCase[0].kind !== "inline"; + } + + return !isFirstChildrenInlineNode(path); +} + +function getAlignment(text) { + const lines = text.split("\n"); + const lastLine = lines.pop(); + + return lastLine.length - lastLine.trimLeft().length + 1; +} + +function isProgramLikeNode(node) { + return ["program", "declare", "namespace"].includes(node.kind); +} + +function isReferenceLikeNode(node) { + return [ + "name", + "parentreference", + "selfreference", + "staticreference", + ].includes(node.kind); +} + +// Return `logical` value for `bin` node containing `||` or `&&` type otherwise return kind of node. +// Require for grouping logical and binary nodes in right way. +function getNodeKindIncludingLogical(node) { + if (node.kind === "bin" && ["||", "&&"].includes(node.type)) { + return "logical"; + } + + return node.kind; +} + +/** + * Check if string can safely be converted from double to single quotes and vice-versa, i.e. + * + * - no embedded variables ("foo $bar") + * - no linebreaks + * - no special characters like \n, \t, ... + * - no octal/hex/unicode characters + * + * See https://php.net/manual/en/language.types.string.php#language.types.string.syntax.double + */ +function useDoubleQuote(node, options) { + if (node.isDoubleQuote === options.singleQuote) { + // We have a double quote and the user passed singleQuote:true, or the other way around. + const rawValue = node.raw.slice(node.raw[0] === "b" ? 2 : 1, -1); + const isComplex = rawValue.match( + /\\([$nrtfve]|[xX][0-9a-fA-F]{1,2}|[0-7]{1,3}|u{([0-9a-fA-F]+)})|\r?\n|'|"|\$/ + ); + return node.isDoubleQuote ? isComplex : !isComplex; + } + return node.isDoubleQuote; +} + +function hasEmptyBody(path, name = "body") { + const { node } = path; + + return ( + node[name] && + node[name].children && + node[name].children.length === 0 && + (!node[name].comments || node[name].comments.length === 0) + ); +} + +function isNextLineEmptyAfterNamespace(text, node) { + let idx = locStart(node); + idx = skipEverythingButNewLine(text, idx); + idx = skipNewline(text, idx); + return hasNewline(text, idx); +} + +function shouldPrintHardlineBeforeTrailingComma(lastElem) { + if ( + lastElem.kind === "nowdoc" || + (lastElem.kind === "encapsed" && lastElem.type === "heredoc") + ) { + return true; + } + + if ( + lastElem.kind === "entry" && + (lastElem.value.kind === "nowdoc" || + (lastElem.value.kind === "encapsed" && lastElem.value.type === "heredoc")) + ) { + return true; + } + + return false; +} + +function getAncestorCounter(path, typeOrTypes) { + const types = [].concat(typeOrTypes); + let counter = -1; + let ancestorNode; + while ((ancestorNode = path.getParentNode(++counter))) { + if (types.indexOf(ancestorNode.kind) !== -1) { + return counter; + } + } + return -1; +} + +function getAncestorNode(path, typeOrTypes) { + const counter = getAncestorCounter(path, typeOrTypes); + return counter === -1 ? null : path.getParentNode(counter); +} + +const magicMethods = [ + "__construct", + "__destruct", + "__call", + "__callStatic", + "__get", + "__set", + "__isset", + "__unset", + "__sleep", + "__wakeup", + "__toString", + "__invoke", + "__set_state", + "__clone", + "__debugInfo", +]; +const magicMethodsMap = new Map( + magicMethods.map((name) => [name.toLowerCase(), name]) +); + +function normalizeMagicMethodName(name) { + const loweredName = name.toLowerCase(); + + if (magicMethodsMap.has(loweredName)) { + return magicMethodsMap.get(loweredName); + } + + return name; +} + +/** + * @param {string[]} kindsArray + * @returns {(node: Node | Comment) => Boolean} + */ +function createTypeCheckFunction(kindsArray) { + const kinds = new Set(kindsArray); + return (node) => kinds.has(node?.kind); +} + +const isSingleWordType = createTypeCheckFunction([ + "variadicplaceholder", + "namedargument", + "nullkeyword", + "identifier", + "parameter", + "variable", + "variadic", + "boolean", + "literal", + "number", + "string", + "clone", + "cast", +]); + +const isArrayExpression = createTypeCheckFunction(["array"]); +const isCallLikeExpression = createTypeCheckFunction([ + "nullsafepropertylookup", + "propertylookup", + "staticlookup", + "offsetlookup", + "call", + "new", +]); +const isArrowFuncExpression = createTypeCheckFunction(["arrowfunc"]); + +function getChainParts(node, prev = []) { + const parts = prev; + if (isCallLikeExpression(node)) { + parts.push(node); + } + + if (!node.what) { + return parts; + } + + return getChainParts(node.what, parts); +} + +function isSimpleCallArgument(node, depth = 2) { + if (depth <= 0) { + return false; + } + + const isChildSimple = (child) => isSimpleCallArgument(child, depth - 1); + + if (isSingleWordType(node)) { + return true; + } + + if (isArrayExpression(node)) { + return node.items.every((x) => x === null || isChildSimple(x)); + } + + if (isCallLikeExpression(node)) { + const parts = getChainParts(node); + parts.unshift(); + + return ( + parts.length <= depth && + parts.every((node) => + isLookupNode(node) + ? isChildSimple(node.offset) + : node.arguments.every(isChildSimple) + ) + ); + } + + if (isArrowFuncExpression(node)) { + return ( + node.arguments.length <= depth && node.arguments.every(isChildSimple) + ); + } + + return false; +} + +function memoize(fn) { + const cache = new Map(); + return (key) => { + if (!cache.has(key)) { + cache.set(key, fn(key)); + } + return cache.get(key); + }; +} + +export { + printNumber, + getPrecedence, + isBitwiseOperator, + shouldFlatten, + nodeHasStatement, + getLast, + getPenultimate, + getBodyFirstChild, + lineShouldEndWithSemicolon, + fileShouldEndWithHardline, + maybeStripLeadingSlashFromUse, + hasDanglingComments, + docShouldHaveTrailingNewline, + isLookupNode, + isFirstChildrenInlineNode, + shouldPrintHardLineAfterStartInControlStructure, + shouldPrintHardLineBeforeEndInControlStructure, + getAlignment, + isProgramLikeNode, + isReferenceLikeNode, + getNodeKindIncludingLogical, + useDoubleQuote, + hasEmptyBody, + isNextLineEmptyAfterNamespace, + shouldPrintHardlineBeforeTrailingComma, + isDocNode, + getAncestorNode, + normalizeMagicMethodName, + isSimpleCallArgument, + memoize, +}; diff --git a/frontend/src/utils/prettier/plugins/sql/languages.js b/frontend/src/utils/prettier/plugins/sql/languages.js new file mode 100644 index 0000000..5d09c1b --- /dev/null +++ b/frontend/src/utils/prettier/plugins/sql/languages.js @@ -0,0 +1,30 @@ +// SQL语言定义 +export const languages = [ + { + name: "SQL", + parsers: ["sql"], + extensions: [ + ".sql", + ".ddl", + ".dml", + ".hql", + ".psql", + ".plsql", + ".mysql", + ".mssql", + ".pgsql", + ".sqlite", + ".bigquery", + ".snowflake", + ".redshift", + ".db2", + ".n1ql", + ".cql" + ], + filenames: [ + "*.sql", + "*.ddl", + "*.dml" + ] + } +]; diff --git a/frontend/src/utils/prettier/plugins/sql/sql.d.ts b/frontend/src/utils/prettier/plugins/sql/sql.d.ts index 5208bac..c658172 100644 --- a/frontend/src/utils/prettier/plugins/sql/sql.d.ts +++ b/frontend/src/utils/prettier/plugins/sql/sql.d.ts @@ -1,11 +1,40 @@ -import { Parser, Plugin } from "prettier"; +import { AST, Option } from 'node-sql-parser'; +import { Options, ParserOptions, Plugin } from 'prettier'; +import { + FormatOptions, + FormatOptionsWithLanguage, +} from 'sql-formatter'; +export type SqlBaseOptions = Option & + Partial< + | (FormatOptions & { dialect: string }) + | (FormatOptionsWithLanguage & { dialect?: never }) + > & { + formatter?: 'sql-formatter' | 'node-sql-parser' | 'sql-cst'; + params?: string; + paramTypes?: string; + autoDetectDialect?: boolean; + }; + +export type SqlOptions = ParserOptions & SqlBaseOptions; +export type SqlFormatOptions = Options & SqlBaseOptions; export declare const languages: Plugin["languages"]; + export declare const parsers: { - sql: Parser; + sql: { + parse(text: string, options?: SqlOptions): AST | string; + astFormat: 'sql'; + locStart(): number; + locEnd(): number; + }; }; + export declare const printers: Plugin["printers"]; + export declare const options: Plugin["options"]; -declare const plugin: Plugin; -export default plugin; +export declare function detectDialect(sql: string): string; + +declare const SqlPlugin: Plugin; + +export default SqlPlugin; diff --git a/frontend/src/utils/prettier/plugins/sql/sql.mjs b/frontend/src/utils/prettier/plugins/sql/sql.mjs index a32b6d4..57ff62c 100644 --- a/frontend/src/utils/prettier/plugins/sql/sql.mjs +++ b/frontend/src/utils/prettier/plugins/sql/sql.mjs @@ -1,60 +1,459 @@ -import { format } from 'sql-formatter'; +import { JSOX } from 'jsox'; +import nodeSqlParser from 'node-sql-parser'; +import { + format, + formatDialect, +} from 'sql-formatter'; import { detectDialect } from './detect.mjs'; +import { languages } from './languages.js'; -// Languages -export const languages = [{ - name: "SQL", - parsers: ["sql"], - extensions: [".sql"] -}]; +const parser = new nodeSqlParser.Parser(); + +const SQL_FORMATTER = 'sql-formatter'; +const NODE_SQL_PARSER = 'node-sql-parser'; // Parsers export const parsers = { sql: { - parse: (text) => text, - astFormat: "sql-format", - locStart: (node) => 0, - locEnd: (node) => node.length - } + parse(text, options = {}) { + const { formatter, type, database } = options; + + return formatter === SQL_FORMATTER + ? text + : parser.astify(text, { type, database }); + }, + astFormat: 'sql', + locStart: () => -1, + locEnd: () => -1, + }, }; // Printers export const printers = { - "sql-format": { - print: (path) => { - const text = path.getValue(); - - if (!text || typeof text !== 'string') { - return text; - } + sql: { + print(path, options = {}) { + const value = path.node; + const { + formatter = SQL_FORMATTER, + type, + database, + dialect, + language, + params, + paramTypes, + autoDetectDialect = true, + ...formatOptions + } = options; - try { - // 自动检测SQL方言 - const dialect = detectDialect(text); - - // 格式化配置 - 使用固定的最佳实践配置 - const formatOptions = { - language: dialect, - tabWidth: 2, - useTabs: true, - keywordCase: 'upper', - dataTypeCase: 'upper', - functionCase: 'upper', - identifierCase: 'preserve' - }; + let formatted; - return format(text, formatOptions); - } catch (error) { - return text; - } + if (typeof value === 'string') { + // 准备sql-formatter选项 + const sqlFormatterOptions = { + ...formatOptions, + params: + params == null + ? undefined + : JSOX.parse(params), + paramTypes: + paramTypes == null + ? undefined + : JSOX.parse(paramTypes), + }; + + let finalLanguage = language; + let finalDialect = dialect; + + if (autoDetectDialect && !language && !dialect) { + const detectedDialect = detectDialect(value); + finalLanguage = detectedDialect; } - } + + // 使用适当的格式化方法 + if (finalDialect != null) { + // 使用formatDialect方法 + formatted = formatDialect(value, { + ...sqlFormatterOptions, + dialect: JSOX.parse(finalDialect), + }); + } else { + // 使用format方法 + formatted = format(value, { + ...sqlFormatterOptions, + language: finalLanguage, + }); + } + } else { + // 使用node-sql-parser进行格式化 + formatted = parser.sqlify(value, { type, database }); + } + + return formatted + '\n'; + }, + }, }; +// 插件选项 +export const options = { + formatter: { + category: 'Config', + type: 'choice', + default: SQL_FORMATTER, + description: 'Choose which formatter to be used', + choices: [ + { + value: SQL_FORMATTER, + description: 'use `sql-formatter` as formatter', + }, + { + value: NODE_SQL_PARSER, + description: 'use `node-sql-parser` as formatter', + }, + ], + }, + autoDetectDialect: { + category: 'Config', + type: 'boolean', + default: true, + description: 'Automatically detect SQL dialect if language/dialect is not specified', + }, + dialect: { + category: 'Config', + type: 'string', + description: 'SQL dialect for `sql-formatter` formatDialect()', + }, + language: { + category: 'Config', + type: 'choice', + default: 'sql', + description: 'SQL dialect for `sql-formatter` format()', + choices: [ + { + value: 'sql', + description: 'Standard SQL: https://en.wikipedia.org/wiki/SQL:2011', + }, + { + value: 'bigquery', + description: + 'Google Standard SQL (Bigquery): https://cloud.google.com/bigquery', + }, + { + value: 'db2', + description: 'IBM DB2: https://www.ibm.com/products/db2', + }, + { + value: 'db2i', + description: + 'IBM DB2i (experimental): https://www.ibm.com/docs/en/i/7.5?topic=overview-db2-i', + }, + { + value: 'hive', + description: 'Apache Hive: https://hive.apache.org', + }, + { + value: 'mariadb', + description: 'MariaDB: https://mariadb.com', + }, + { + value: 'mysql', + description: 'MySQL: https://www.mysql.com', + }, + { + value: 'n1ql', + description: + 'Couchbase N1QL: https://www.couchbase.com/products/n1ql', + }, + { + value: 'plsql', + description: + 'Oracle PL/SQL: https://www.oracle.com/database/technologies/appdev/plsql.html', + }, + { + value: 'postgresql', + description: 'PostgreSQL: https://www.postgresql.org', + }, + { + value: 'redshift', + description: + 'Amazon Redshift: https://docs.aws.amazon.com/redshift/latest/dg/cm_chap_SQLCommandRef.html', + }, + { + value: 'singlestoredb', + description: + 'SingleStoreDB: https://docs.singlestore.com/db/v7.8/en/introduction/singlestore-documentation.html', + }, + { + value: 'snowflake', + description: 'Snowflake: https://docs.snowflake.com', + }, + { + value: 'spark', + description: 'Spark: https://spark.apache.org', + }, + { + value: 'sqlite', + description: 'SQLite: https://www.sqlite.org', + }, + { + value: 'transactsql', + description: + 'SQL Server Transact-SQL: https://docs.microsoft.com/en-us/sql/sql-server/', + }, + { + value: 'tsql', + description: + 'SQL Server Transact-SQL: https://docs.microsoft.com/en-us/sql/sql-server/', + }, + { + value: 'trino', + description: 'Trino: https://trino.io', + }, + ], + }, + keywordCase: { + category: 'Output', + type: 'choice', + default: 'preserve', + description: + 'Converts reserved keywords to upper- or lowercase for `sql-formatter`', + choices: [ + { + value: 'preserve', + description: 'preserves the original case of reserved keywords', + }, + { + value: 'upper', + description: 'converts reserved keywords to uppercase', + }, + { + value: 'lower', + description: 'converts reserved keywords to lowercase', + }, + ], + }, + dataTypeCase: { + category: 'Output', + type: 'choice', + default: 'preserve', + description: + 'Converts data types to upper- or lowercase for `sql-formatter`', + choices: [ + { + value: 'preserve', + description: 'preserves the original case of data types', + }, + { + value: 'upper', + description: 'converts data types to uppercase', + }, + { + value: 'lower', + description: 'converts data types to lowercase', + }, + ], + }, + functionCase: { + category: 'Output', + type: 'choice', + default: 'preserve', + description: + 'Converts functions to upper- or lowercase for `sql-formatter`', + choices: [ + { + value: 'preserve', + description: 'preserves the original case of functions', + }, + { + value: 'upper', + description: 'converts functions to uppercase', + }, + { + value: 'lower', + description: 'converts functions to lowercase', + }, + ], + }, + identifierCase: { + category: 'Output', + type: 'choice', + default: 'preserve', + description: + 'Converts identifiers to upper- or lowercase for `sql-formatter`. Only unquoted identifiers are converted. (experimental)', + choices: [ + { + value: 'preserve', + description: 'preserves the original case of identifiers', + }, + { + value: 'upper', + description: 'converts identifiers to uppercase', + }, + { + value: 'lower', + description: 'converts identifiers to lowercase', + }, + ], + }, + uppercase: { + category: 'Output', + type: 'boolean', + deprecated: '0.7.0', + description: 'Use `keywordCase` option instead', + }, + indentStyle: { + category: 'Format', + type: 'choice', + default: 'standard', + description: `Switches between different indentation styles for \`sql-formatter\`. -// Default export -export default { + Caveats of using \`"tabularLeft"\` and \`"tabularRight"\`: + + - \`tabWidth\` option is ignored. Indentation will always be 10 spaces, regardless of what is specified by \`tabWidth\``, + choices: [ + { + value: 'standard', + description: + 'indents code by the amount specified by `tabWidth` option', + }, + { + value: 'tabularLeft', + description: + 'indents in tabular style with 10 spaces, aligning keywords to left', + }, + { + value: 'tabularRight', + description: + 'indents in tabular style with 10 spaces, aligning keywords to right', + }, + ], + }, + logicalOperatorNewline: { + category: 'Format', + type: 'choice', + default: 'before', + description: + 'Decides newline placement before or after logical operators (AND, OR, XOR)', + choices: [ + { + value: 'before', + description: 'adds newline before the operator', + }, + { + value: 'after', + description: 'adds newline after the operator', + }, + ], + }, + expressionWidth: { + category: 'Format', + type: 'int', + default: 50, + description: + 'Determines maximum length of parenthesized expressions for `sql-formatter`', + }, + linesBetweenQueries: { + category: 'Format', + type: 'int', + default: 1, + description: + 'Decides how many empty lines to leave between SQL statements for `sql-formatter`', + }, + denseOperators: { + category: 'Format', + type: 'boolean', + default: false, + description: + 'Decides whitespace around operators for `sql-formatter`. Does not apply to logical operators (AND, OR, XOR).', + }, + newlineBeforeSemicolon: { + category: 'Format', + type: 'boolean', + default: false, + description: + 'Whether to place query separator (`;`) on a separate line for `sql-formatter`', + }, + params: { + category: 'Format', + type: 'string', + description: + 'Specifies `JSOX` **stringified** parameter values to fill in for placeholders inside SQL for `sql-formatter`. This option is designed to be used through API (though nothing really prevents usage from command line).', + }, + paramTypes: { + category: 'Config', + type: 'string', + description: + 'Specifies `JSOX` **stringified** parameter types to support when parsing SQL prepared statements for `sql-formatter`.', + }, + type: { + category: 'Config', + type: 'choice', + default: 'table', + description: 'Check the SQL with Authority List for `node-sql-parser`', + choices: [ + { + value: 'table', + description: '`table` mode', + }, + { + value: 'column', + description: '`column` mode', + }, + ], + }, + database: { + category: 'Config', + type: 'choice', + default: 'mysql', + description: 'SQL dialect for `node-sql-parser`', + choices: [ + { + value: 'bigquery', + description: 'BigQuery: https://cloud.google.com/bigquery', + }, + { + value: 'db2', + description: 'IBM DB2: https://www.ibm.com/analytics/db2', + }, + { + value: 'hive', + description: 'Hive: https://hive.apache.org', + }, + { + value: 'mariadb', + description: 'MariaDB: https://mariadb.com', + }, + { + value: 'mysql', + description: 'MySQL: https://www.mysql.com', + }, + { + value: 'postgresql', + description: 'PostgreSQL: https://www.postgresql.org', + }, + { + value: 'transactsql', + description: + 'TransactSQL: https://docs.microsoft.com/en-us/sql/t-sql', + }, + { + value: 'flinksql', + description: + 'FlinkSQL: https://ci.apache.org/projects/flink/flink-docs-stable', + }, + { + value: 'snowflake', + description: 'Snowflake (alpha): https://docs.snowflake.com', + }, + ], + }, +}; + +const SqlPlugin = { languages, parsers, - printers -}; \ No newline at end of file + printers, + options, +}; + +export { languages }; +export default SqlPlugin; \ No newline at end of file diff --git a/frontend/src/views/editor/extensions/codeblock/lang-parser/languages.ts b/frontend/src/views/editor/extensions/codeblock/lang-parser/languages.ts index c89bfab..5b6922e 100644 --- a/frontend/src/views/editor/extensions/codeblock/lang-parser/languages.ts +++ b/frontend/src/views/editor/extensions/codeblock/lang-parser/languages.ts @@ -38,6 +38,7 @@ import markdownPrettierPlugin from "prettier/plugins/markdown" import yamlPrettierPlugin from "prettier/plugins/yaml" import goPrettierPlugin from "@/utils/prettier/plugins/go/go" import sqlPrettierPlugin from "@/utils/prettier/plugins/sql/sql" +import phpPrettierPlugin from "@/utils/prettier/plugins/php" import * as prettierPluginEstree from "prettier/plugins/estree"; /** @@ -78,7 +79,10 @@ export const LANGUAGES: LanguageInfo[] = [ plugins: [markdownPrettierPlugin] }), new LanguageInfo("java", "Java", javaLanguage.parser), - new LanguageInfo("php", "PHP", phpLanguage.configure({top: "Program"}).parser), + new LanguageInfo("php", "PHP", phpLanguage.configure({top: "Program"}).parser, { + parser: "php", + plugins: [phpPrettierPlugin] + }), new LanguageInfo("css", "CSS", cssLanguage.parser, { parser: "css", plugins: [cssPrettierPlugin] diff --git a/frontend/vite.config.ts b/frontend/vite.config.ts index c179484..00997b8 100644 --- a/frontend/vite.config.ts +++ b/frontend/vite.config.ts @@ -2,6 +2,7 @@ import {defineConfig, loadEnv} from 'vite'; import vue from '@vitejs/plugin-vue'; import Components from 'unplugin-vue-components/vite'; import * as path from 'path'; +import { nodePolyfills } from 'vite-plugin-node-polyfills' export default defineConfig(({mode}: { mode: string }): object => { const env: Record = loadEnv(mode, process.cwd()); @@ -15,6 +16,7 @@ export default defineConfig(({mode}: { mode: string }): object => { }, plugins: [ vue(), + nodePolyfills(), Components({ dts: true, dirs: ['src/components'],