diff --git a/.vscode/launch.json b/.vscode/launch.json index dda897afd2..0ad69b7cdf 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -7,6 +7,7 @@ "type": "extensionHost", "request": "launch", "runtimeExecutable": "${execPath}", + "debugWebviews": true, "args": ["--extensionDevelopmentPath=${workspaceRoot}" ], "env": { "DEBUG_VSCODE_JAVA":"true" @@ -14,7 +15,11 @@ "stopOnEntry": false, "sourceMaps": true, "outFiles": [ "${workspaceRoot}/dist/**/*.js" ], - "preLaunchTask": "npm: watch" + "preLaunchTask": "npm: watch", + "rendererDebugOptions": { + "urlFilter": "*redhat.java*", + "sourceMaps": true, + } }, { "name": "Launch Extension - Remote Server", @@ -36,6 +41,7 @@ "type": "extensionHost", "request": "launch", "runtimeExecutable": "${execPath}", + "debugWebviews": true, "args": ["--extensionDevelopmentPath=${workspaceRoot}" ], "stopOnEntry": false, "sourceMaps": true, @@ -44,7 +50,11 @@ "JDTLS_CLIENT_PORT": "5036", "DEBUG_VSCODE_JAVA":"true" }, - "preLaunchTask": "npm: watch" + "preLaunchTask": "npm: watch", + "rendererDebugOptions": { + "urlFilter": "*redhat.java*", + "sourceMaps": true, + } }, { "name": "Launch Extension - SyntaxLS Client", diff --git a/document/_java.learnMoreAboutRefactorings.md b/document/_java.learnMoreAboutRefactorings.md index eec0591ee5..1af09cf3dd 100644 --- a/document/_java.learnMoreAboutRefactorings.md +++ b/document/_java.learnMoreAboutRefactorings.md @@ -18,7 +18,9 @@ - [Inline constant](#inline-constant) - [Inline local variable](#inline-local-variable) - [Inline method](#inline-method) -- [Introduce Parameter](#introduce-parameter) +- Method signature + - [Introduce Parameter](#introduce-parameter) + - [Change method signature](#change-method-signature) - Invert boolean - [Invert conditions](#invert-conditions) - [Invert local variable](#invert-local-variable) @@ -469,6 +471,41 @@ public void addUser(String name) { } ``` +--- + +## Change Method Signature +Changes the method visibility, return type, name, and updates the parameters and exceptions. The above changes can be applied through the call hierarchy of the method. + +### Example +Let's change signature for the method `public void setAddress(String address)`. + +#### Before + +```java +public void setAddress(String address) { + this.address = address; +} + +public void setAddr() { + this.setAddress("Addr"); +} +``` + +#### Refactor configuration + +![change_signature](./refactoring_change_signature.png) +#### After +```java +public void setAddress1(Object newParam, String address) { + this.address = address; +} + +public void setAddr() { + this.setAddress1(null, "Addr"); +} +``` +--- + ## Invert conditions Inverts the boolean expression in the conditions. diff --git a/document/refactoring_change_signature.png b/document/refactoring_change_signature.png new file mode 100644 index 0000000000..6690f7471f Binary files /dev/null and b/document/refactoring_change_signature.png differ diff --git a/package-lock.json b/package-lock.json index 173ea5fbf4..1303968070 100644 --- a/package-lock.json +++ b/package-lock.json @@ -142,6 +142,46 @@ "@jridgewell/sourcemap-codec": "1.4.14" } }, + "@microsoft/fast-element": { + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/@microsoft/fast-element/-/fast-element-1.11.0.tgz", + "integrity": "sha512-VKJYMkS5zgzHHb66sY7AFpYv6IfFhXrjQcAyNgi2ivD65My1XOhtjfKez5ELcLFRJfgZNAxvI8kE69apXERTkw==" + }, + "@microsoft/fast-foundation": { + "version": "2.47.0", + "resolved": "https://registry.npmjs.org/@microsoft/fast-foundation/-/fast-foundation-2.47.0.tgz", + "integrity": "sha512-EyFuioaZQ9ngjUNRQi8R3dIPPsaNQdUOS+tP0G7b1MJRhXmQWIitBM6IeveQA6ZvXG6H21dqgrfEWlsYrUZ2sw==", + "requires": { + "@microsoft/fast-element": "^1.11.0", + "@microsoft/fast-web-utilities": "^5.4.1", + "tabbable": "^5.2.0", + "tslib": "^1.13.0" + }, + "dependencies": { + "tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" + } + } + }, + "@microsoft/fast-react-wrapper": { + "version": "0.1.48", + "resolved": "https://registry.npmjs.org/@microsoft/fast-react-wrapper/-/fast-react-wrapper-0.1.48.tgz", + "integrity": "sha512-9NvEjru9Kn5ZKjomAMX6v+eF0DR+eDkxKDwDfi+Wb73kTbrNzcnmlwd4diN15ygH97kldgj2+lpvI4CKLQQWLg==", + "requires": { + "@microsoft/fast-element": "^1.9.0", + "@microsoft/fast-foundation": "^2.41.1" + } + }, + "@microsoft/fast-web-utilities": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/@microsoft/fast-web-utilities/-/fast-web-utilities-5.4.1.tgz", + "integrity": "sha512-ReWYncndjV3c8D8iq9tp7NcFNc1vbVHvcBFPME2nNFKNbS1XCesYZGlIlf3ot5EmuOXPlrzUHOWzQ2vFpIkqDg==", + "requires": { + "exenv-es6": "^1.1.1" + } + }, "@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -293,6 +333,38 @@ "integrity": "sha512-vzLe5NaNMjIE3mcddFVGlAXN1LEWueUsMsOJWaT6wWMJGyljHAWHznqfnKUQWGzu7TLPrGvWdNAsvQYW+C0xtw==", "dev": true }, + "@types/prop-types": { + "version": "15.7.5", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.5.tgz", + "integrity": "sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w==", + "dev": true + }, + "@types/react": { + "version": "17.0.53", + "resolved": "https://registry.npmjs.org/@types/react/-/react-17.0.53.tgz", + "integrity": "sha512-1yIpQR2zdYu1Z/dc1OxC+MA6GR240u3gcnP4l6mvj/PJiVaqHsQPmWttsvHsfnhfPbU2FuGmo0wSITPygjBmsw==", + "dev": true, + "requires": { + "@types/prop-types": "*", + "@types/scheduler": "*", + "csstype": "^3.0.2" + } + }, + "@types/react-dom": { + "version": "17.0.19", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-17.0.19.tgz", + "integrity": "sha512-PiYG40pnQRdPHnlf7tZnp0aQ6q9tspYr72vD61saO6zFCybLfMqwUCN0va1/P+86DXn18ZWeW30Bk7xlC5eEAQ==", + "dev": true, + "requires": { + "@types/react": "^17" + } + }, + "@types/scheduler": { + "version": "0.16.2", + "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.2.tgz", + "integrity": "sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew==", + "dev": true + }, "@types/semver": { "version": "7.3.8", "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.3.8.tgz", @@ -320,6 +392,12 @@ "integrity": "sha512-emg7wdsTFzdi+elvoyoA+Q8keEautdQHyY5LNmHVM4PTpY8JgOTVADrGVyXGepJ6dVW2OS5/xnLUWh+nZxvdiA==", "dev": true }, + "@types/vscode-webview": { + "version": "1.57.1", + "resolved": "https://registry.npmjs.org/@types/vscode-webview/-/vscode-webview-1.57.1.tgz", + "integrity": "sha512-ghW5SfuDmsGDS2A4xkvGsLwDRNc3Vj5rS6rPOyPm/IryZuf3wceZKxgYaUoW+k9f0f/CB7y2c1rRsdOWZWn0PQ==", + "dev": true + }, "@types/winreg": { "version": "1.2.30", "resolved": "https://registry.npmjs.org/@types/winreg/-/winreg-1.2.30.tgz", @@ -539,6 +617,11 @@ "integrity": "sha512-sL/cEvJWAnClXw0wHk85/2L0G6Sj8UB0Ctc1TEMbKSsmpRosqhwj9gWgFRZSrBr2f9tiXISwNhCPmlfqUqyb9Q==", "dev": true }, + "@vscode/codicons": { + "version": "0.0.32", + "resolved": "https://registry.npmjs.org/@vscode/codicons/-/codicons-0.0.32.tgz", + "integrity": "sha512-3lgSTWhAzzWN/EPURoY4ZDBEA80OPmnaknNujA3qnI4Iu7AONWd9xF3iE4L+4prIe8E3TUnLQ4pxoaFTEEZNwg==" + }, "@vscode/test-electron": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@vscode/test-electron/-/test-electron-2.1.5.tgz", @@ -607,6 +690,16 @@ } } }, + "@vscode/webview-ui-toolkit": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@vscode/webview-ui-toolkit/-/webview-ui-toolkit-1.2.2.tgz", + "integrity": "sha512-xIQoF4FC3Xh6d7KNKIoIezSiFWYFuf6gQMdDyKueKBFGeKwaHWEn+dY2g3makvvEsNMEDji/woEwvg9QSbuUsw==", + "requires": { + "@microsoft/fast-element": "^1.6.2", + "@microsoft/fast-foundation": "^2.38.0", + "@microsoft/fast-react-wrapper": "^0.1.18" + } + }, "@webassemblyjs/ast": { "version": "1.11.0", "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.11.0.tgz", @@ -1799,6 +1892,45 @@ } } }, + "css-loader": { + "version": "6.7.3", + "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-6.7.3.tgz", + "integrity": "sha512-qhOH1KlBMnZP8FzRO6YCH9UHXQhVMcEGLyNdb7Hv2cpcmJbW0YrddO+tG1ab5nT41KpHIYGsbeHqxB9xPu1pKQ==", + "dev": true, + "requires": { + "icss-utils": "^5.1.0", + "postcss": "^8.4.19", + "postcss-modules-extract-imports": "^3.0.0", + "postcss-modules-local-by-default": "^4.0.0", + "postcss-modules-scope": "^3.0.0", + "postcss-modules-values": "^4.0.0", + "postcss-value-parser": "^4.2.0", + "semver": "^7.3.8" + }, + "dependencies": { + "semver": { + "version": "7.3.8", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", + "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==", + "dev": true, + "requires": { + "lru-cache": "^6.0.0" + } + } + } + }, + "cssesc": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", + "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", + "dev": true + }, + "csstype": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.1.tgz", + "integrity": "sha512-DJR/VvkAvSZW9bTouZue2sSxDwdTN92uHjqeKVm+0dAqdfNykRzQ95tay8aXMBAAPpUiq4Qcug2L7neoRh2Egw==", + "dev": true + }, "cycle": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/cycle/-/cycle-1.0.3.tgz", @@ -2208,9 +2340,9 @@ } }, "enhanced-resolve": { - "version": "5.8.3", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.8.3.tgz", - "integrity": "sha512-EGAbGvH7j7Xt2nc0E7D99La1OiEs8LnyimkRgwExpUMScN6O+3x9tIWs7PLQZVNx4YD+00skHXPXi1yQHpAmZA==", + "version": "5.12.0", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.12.0.tgz", + "integrity": "sha512-QHTXI/sZQmko1cbDoNAa3mJ5qhWUUNAq3vR0/YiD379fWQrcfuoX1+HW2S0MTt7XmoPLapdaDKUtelUSPic7hQ==", "dev": true, "requires": { "graceful-fs": "^4.2.4", @@ -2218,9 +2350,9 @@ }, "dependencies": { "graceful-fs": { - "version": "4.2.8", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.8.tgz", - "integrity": "sha512-qkIilPUYcNhJpd33n0GBXTB1MMPp14TxEsEs0pTrsSVucApsYzW5V+Q8Qxhik6KU3evy+qkAAowTByymK0avdg==", + "version": "4.2.10", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz", + "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==", "dev": true } } @@ -2781,6 +2913,11 @@ } } }, + "exenv-es6": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/exenv-es6/-/exenv-es6-1.1.1.tgz", + "integrity": "sha512-vlVu3N8d6yEMpMsEm+7sUBAI81aqYYuEvfK0jNqmdb/OPXzzH7QWDDnVjMvDSY47JdHEqx/dfC/q8WkfoTmpGQ==" + }, "expand-brackets": { "version": "2.1.4", "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz", @@ -3953,6 +4090,12 @@ "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", "dev": true }, + "icss-utils": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/icss-utils/-/icss-utils-5.1.0.tgz", + "integrity": "sha512-soFhflCVWLfRNOPU3iv5Z9VUdT44xFRbzjLsEzSr5AQmgqPMTHdU3PMT1Cf1ssx8fLNJDA1juftYl+PUcv3MqA==", + "dev": true + }, "ieee754": { "version": "1.1.12", "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.12.tgz", @@ -4313,6 +4456,11 @@ } } }, + "js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" + }, "js-yaml": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", @@ -4721,6 +4869,14 @@ } } }, + "loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "requires": { + "js-tokens": "^3.0.0 || ^4.0.0" + } + }, "lru-cache": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", @@ -5303,8 +5459,7 @@ "object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", - "dev": true + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=" }, "object-copy": { "version": "0.1.0", @@ -5627,6 +5782,12 @@ "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=", "dev": true }, + "picocolors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", + "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", + "dev": true + }, "picomatch": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.0.tgz", @@ -5725,6 +5886,76 @@ "integrity": "sha1-AerA/jta9xoqbAL+q7jB/vfgDqs=", "dev": true }, + "postcss": { + "version": "8.4.21", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.21.tgz", + "integrity": "sha512-tP7u/Sn/dVxK2NnruI4H9BG+x+Wxz6oeZ1cJ8P6G/PZY0IKk4k/63TDsQf2kQq3+qoJeLm2kIBUNlZe3zgb4Zg==", + "dev": true, + "requires": { + "nanoid": "^3.3.4", + "picocolors": "^1.0.0", + "source-map-js": "^1.0.2" + }, + "dependencies": { + "nanoid": { + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.4.tgz", + "integrity": "sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==", + "dev": true + } + } + }, + "postcss-modules-extract-imports": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/postcss-modules-extract-imports/-/postcss-modules-extract-imports-3.0.0.tgz", + "integrity": "sha512-bdHleFnP3kZ4NYDhuGlVK+CMrQ/pqUm8bx/oGL93K6gVwiclvX5x0n76fYMKuIGKzlABOy13zsvqjb0f92TEXw==", + "dev": true + }, + "postcss-modules-local-by-default": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/postcss-modules-local-by-default/-/postcss-modules-local-by-default-4.0.0.tgz", + "integrity": "sha512-sT7ihtmGSF9yhm6ggikHdV0hlziDTX7oFoXtuVWeDd3hHObNkcHRo9V3yg7vCAY7cONyxJC/XXCmmiHHcvX7bQ==", + "dev": true, + "requires": { + "icss-utils": "^5.0.0", + "postcss-selector-parser": "^6.0.2", + "postcss-value-parser": "^4.1.0" + } + }, + "postcss-modules-scope": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/postcss-modules-scope/-/postcss-modules-scope-3.0.0.tgz", + "integrity": "sha512-hncihwFA2yPath8oZ15PZqvWGkWf+XUfQgUGamS4LqoP1anQLOsOJw0vr7J7IwLpoY9fatA2qiGUGmuZL0Iqlg==", + "dev": true, + "requires": { + "postcss-selector-parser": "^6.0.4" + } + }, + "postcss-modules-values": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/postcss-modules-values/-/postcss-modules-values-4.0.0.tgz", + "integrity": "sha512-RDxHkAiEGI78gS2ofyvCsu7iycRv7oqw5xMWn9iMoR0N/7mf9D50ecQqUo5BZ9Zh2vH4bCUR/ktCqbB9m8vJjQ==", + "dev": true, + "requires": { + "icss-utils": "^5.0.0" + } + }, + "postcss-selector-parser": { + "version": "6.0.11", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.11.tgz", + "integrity": "sha512-zbARubNdogI9j7WY4nQJBiNqQf3sLS3wCP4WfOidu+p28LofJqDH1tcXypGrcmMHhDk2t9wGhCsYe/+szLTy1g==", + "dev": true, + "requires": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + } + }, + "postcss-value-parser": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", + "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", + "dev": true + }, "prelude-ls": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", @@ -5797,6 +6028,25 @@ "safe-buffer": "^5.1.0" } }, + "react": { + "version": "17.0.2", + "resolved": "https://registry.npmjs.org/react/-/react-17.0.2.tgz", + "integrity": "sha512-gnhPt75i/dq/z3/6q/0asP78D0u592D5L1pd7M8P+dck6Fu/jJeL6iVVK23fptSUZj8Vjf++7wXA8UNclGQcbA==", + "requires": { + "loose-envify": "^1.1.0", + "object-assign": "^4.1.1" + } + }, + "react-dom": { + "version": "17.0.2", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-17.0.2.tgz", + "integrity": "sha512-s4h96KtLDUQlsENhMn1ar8t2bEa+q/YAtj8pPPdIjPDGBDIVNsrD9aXNWqspUe6AzKCIG0C1HZZLqLV7qpOBGA==", + "requires": { + "loose-envify": "^1.1.0", + "object-assign": "^4.1.1", + "scheduler": "^0.20.2" + } + }, "read-pkg": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-1.1.0.tgz", @@ -6073,6 +6323,15 @@ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", "dev": true }, + "scheduler": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.20.2.tgz", + "integrity": "sha512-2eWfGgAqqWFGqtdMmcL5zCMK1U8KlXv8SQFGglL3CEtd0aDVDWgeF/YoCmvln55m5zSk3J/20hTaSBeSObsQDQ==", + "requires": { + "loose-envify": "^1.1.0", + "object-assign": "^4.1.1" + } + }, "schema-utils": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.0.0.tgz", @@ -6381,6 +6640,12 @@ "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", "dev": true }, + "source-map-js": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", + "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==", + "dev": true + }, "source-map-resolve": { "version": "0.5.3", "resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.5.3.tgz", @@ -6578,6 +6843,12 @@ "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", "dev": true }, + "style-loader": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/style-loader/-/style-loader-3.3.1.tgz", + "integrity": "sha512-GPcQ+LDJbrcxHORTRes6Jy2sfvK2kS6hpSfI/fXhPt+spVzxF6LJ1dHLN9zIGmVaaP044YKaIatFaufENRiDoQ==", + "dev": true + }, "supports-color": { "version": "2.0.0", "resolved": "https://repository.engineering.redhat.com/nexus/repository/registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", @@ -6594,6 +6865,11 @@ "es6-symbol": "^3.1.1" } }, + "tabbable": { + "version": "5.3.3", + "resolved": "https://registry.npmjs.org/tabbable/-/tabbable-5.3.3.tgz", + "integrity": "sha512-QD9qKY3StfbZqWOPLp0++pOrAVb/HbUi5xCc8cUo4XjP19808oaMiDzn0leBY5mCespIBM0CIZePzZjgzR83kA==" + }, "tapable": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", @@ -6813,9 +7089,9 @@ "integrity": "sha512-XrHUvV5HpdLmIj4uVMxHggLbFSZYIn7HEWsqePZcI50pco+MPqJ50wMGY794X7AOOhxOBAjbkqfAbEe/QMp2Lw==" }, "ts-loader": { - "version": "9.2.6", - "resolved": "https://registry.npmjs.org/ts-loader/-/ts-loader-9.2.6.tgz", - "integrity": "sha512-QMTC4UFzHmu9wU2VHZEmWWE9cUajjfcdcws+Gh7FhiO+Dy0RnR1bNz0YCHqhI0yRowCE9arVnNxYHqELOy9Hjw==", + "version": "9.4.2", + "resolved": "https://registry.npmjs.org/ts-loader/-/ts-loader-9.4.2.tgz", + "integrity": "sha512-OmlC4WVmFv5I0PpaxYb+qGeGOdm5giHU7HwDDUjw59emP2UYMHy9fFSDcYgSNoH8sXcj4hGCSEhlDZ9ULeDraA==", "dev": true, "requires": { "chalk": "^4.1.0", @@ -6876,12 +7152,6 @@ "to-regex-range": "^5.0.1" } }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, "is-number": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", @@ -6889,15 +7159,21 @@ "dev": true }, "micromatch": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.4.tgz", - "integrity": "sha512-pRmzw/XUcwXGpD9aI9q/0XOwLNygjETJ8y0ao0wdqprrzDa4YnxLcz7fQRZr8voh8V10kGhABbNcHVk5wHgWwg==", + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", + "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", "dev": true, "requires": { - "braces": "^3.0.1", - "picomatch": "^2.2.3" + "braces": "^3.0.2", + "picomatch": "^2.3.1" } }, + "picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true + }, "supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", diff --git a/package.json b/package.json index e70da57df3..8fd8500645 100644 --- a/package.json +++ b/package.json @@ -1398,6 +1398,9 @@ "@types/lodash.findindex": "^4.6.6", "@types/mocha": "^5.2.5", "@types/node": "^16.11.7", + "@types/react": "^17.0.37", + "@types/react-dom": "^17.0.11", + "@types/vscode-webview": "^1.57.0", "@types/semver": "^7.3.8", "@types/sinon": "^10.0.12", "@types/vscode": "^1.74.0", @@ -1406,6 +1409,7 @@ "@vscode/test-electron": "^2.1.5", "@typescript-eslint/eslint-plugin": "^5.18.0", "@typescript-eslint/parser": "^5.18.0", + "css-loader": "^6.7.3", "eslint": "^8.13.0", "eslint-webpack-plugin": "^3.2.0", "gulp": "^4.0.2", @@ -1417,12 +1421,15 @@ "mocha": "^9.2.2", "request": "^2.88.2", "sinon": "^14.0.0", - "ts-loader": "^9.2.6", + "style-loader": "^3.3.1", + "ts-loader": "^9.4.2", "typescript": "^4.6.4", "webpack": "^5.28.0", "webpack-cli": "^4.6.0" }, "dependencies": { + "@vscode/codicons": "^0.0.32", + "@vscode/webview-ui-toolkit": "1.2.2", "chokidar": "^3.5.3", "expand-home-dir": "^0.0.3", "fmtr": "^1.1.2", @@ -1430,6 +1437,8 @@ "glob": "^7.1.3", "htmlparser2": "6.0.1", "jdk-utils": "^0.4.4", + "react": "^17.0.2", + "react-dom": "^17.0.2", "semver": "^7.3.5", "vscode-languageclient": "8.1.0", "winreg-utf8": "^0.1.1", diff --git a/src/log.ts b/src/log.ts index ce340c3d96..30ffea336f 100644 --- a/src/log.ts +++ b/src/log.ts @@ -1,5 +1,5 @@ import { createLogger, format, transports } from 'winston'; -import * as DailyRotateFile from 'winston-daily-rotate-file'; +import DailyRotateFile from 'winston-daily-rotate-file'; export function initializeLogFile(filename: string) { logger.add(new DailyRotateFile({ diff --git a/src/markdownPreviewProvider.ts b/src/markdownPreviewProvider.ts index 9dbe94ea16..ad43e535cb 100644 --- a/src/markdownPreviewProvider.ts +++ b/src/markdownPreviewProvider.ts @@ -2,6 +2,7 @@ import { Disposable, WebviewPanel, window, ViewColumn, commands, Uri, Webview, E import * as fse from 'fs-extra'; import * as path from 'path'; import { Commands } from "./commands"; +import { getNonce } from "./webviewUtils"; class MarkdownPreviewProvider implements Disposable { private panel: WebviewPanel | undefined; @@ -42,7 +43,7 @@ class MarkdownPreviewProvider implements Disposable { } protected async getHtmlContent(webview: Webview, markdownFilePath: string, section: string, context: ExtensionContext): Promise { - const nonce: string = this.getNonce(); + const nonce: string = getNonce(); const styles: string = this.getStyles(webview, context); let body: string | undefined = this.documentCache.get(markdownFilePath); if (!body) { @@ -93,15 +94,6 @@ class MarkdownPreviewProvider implements Disposable { ]; return styles.map((styleUri: Uri) => ``).join('\n'); } - - private getNonce(): string { - let text = ""; - const possible = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; - for (let i = 0; i < 32; i++) { - text += possible.charAt(Math.floor(Math.random() * possible.length)); - } - return text; - } } export const markdownPreviewProvider: MarkdownPreviewProvider = new MarkdownPreviewProvider(); diff --git a/src/refactorAction.ts b/src/refactorAction.ts index bc90e103dc..068998ccb7 100644 --- a/src/refactorAction.ts +++ b/src/refactorAction.ts @@ -7,6 +7,7 @@ import { FormattingOptions, WorkspaceEdit, RenameFile, DeleteFile, TextDocumentE import { LanguageClient } from 'vscode-languageclient/node'; import { Commands as javaCommands } from './commands'; import { GetRefactorEditRequest, MoveRequest, RefactorWorkspaceEdit, RenamePosition, GetMoveDestinationsRequest, SearchSymbols, SelectionInfo, InferSelectionRequest } from './protocol'; +import { ChangeSignaturePanel } from './refactoring/changeSignaturePanel'; import { getExtractInterfaceArguments, revealExtractedInterface } from './refactoring/extractInterface'; export function registerCommands(languageClient: LanguageClient, context: ExtensionContext) { @@ -40,6 +41,7 @@ function registerApplyRefactorCommand(languageClient: LanguageClient, context: E || command === 'extractMethod' || command === 'extractField' || command === 'extractInterface' + || command === 'changeSignature' || command === 'assignField' || command === 'convertVariableToField' || command === 'invertVariable' @@ -109,6 +111,9 @@ function registerApplyRefactorCommand(languageClient: LanguageClient, context: E return; } commandArguments.push(...args); + } else if (command === 'changeSignature') { + ChangeSignaturePanel.render(context.extensionUri, languageClient, command, params, formattingOptions, commandInfo); + return; } const result: RefactorWorkspaceEdit = await languageClient.sendRequest(GetRefactorEditRequest.type, { diff --git a/src/refactoring/changeSignaturePanel.ts b/src/refactoring/changeSignaturePanel.ts new file mode 100644 index 0000000000..66c98f7d38 --- /dev/null +++ b/src/refactoring/changeSignaturePanel.ts @@ -0,0 +1,192 @@ +import * as path from "path"; +import { Disposable, Webview, WebviewPanel, window, Uri, ViewColumn, workspace, WorkspaceEdit } from "vscode"; +import { LanguageClient } from "vscode-languageclient/node"; +import { GetRefactorEditRequest, RefactorWorkspaceEdit } from "../protocol"; +import { getNonce, getUri } from "../webviewUtils"; + +interface MethodParameter { + type: string; + name: string; + defaultValue: string; + originalIndex: number; +} + +interface MethodException { + type: string; + typeHandleIdentifier: string; +} + +export class ChangeSignaturePanel { + public static type = "java.refactor.changeSignature"; + public static title = "Refactor: Change Method Signature"; + public static currentPanel: ChangeSignaturePanel | undefined; + private readonly panel: WebviewPanel; + private disposables: Disposable[] = []; + + // method matadata + private methodIdentifier: string | undefined; + private methodName: string | undefined; + private modifier: string | undefined; + private returnType: string | undefined; + private parameters: MethodParameter[] | undefined; + private exceptions: MethodException[] | undefined; + + // refactor metadata + private languageClient: LanguageClient; + private params: any; + private formattingOptions: any; + private command: any; + + private constructor(panel: WebviewPanel, extensionUri: Uri) { + this.panel = panel; + this.panel.onDidDispose(() => this.dispose(), null, this.disposables); + this.panel.webview.html = this.getWebviewContent(this.panel.webview, extensionUri); + this.setWebviewMessageListener(this.panel.webview); + } + + public static render(extensionUri: Uri, languageClient: LanguageClient, command: any, params: any, formattingOptions: any, commandInfo: any) { + if (ChangeSignaturePanel.currentPanel) { + if (!commandInfo?.methodIdentifier) { + // the new method to do refactoring is not available, just reveal the current panel. + ChangeSignaturePanel.currentPanel.panel.reveal(ViewColumn.Beside); + return; + } + if (commandInfo.methodIdentifier === ChangeSignaturePanel.currentPanel.methodIdentifier) { + // the new method to do refactoring is the same as the old one, just reveal the current panel. + ChangeSignaturePanel.currentPanel.panel.reveal(ViewColumn.Beside); + return; + } else { + window.showInformationMessage(`Change Method Signature for '${ChangeSignaturePanel.currentPanel.methodName}' is in process. Would you like to reveal or abort it?`, "Reveal", "Abort").then(async (selection) => { + if (selection === "Reveal") { + ChangeSignaturePanel.currentPanel.panel.reveal(ViewColumn.Beside); + } else if (selection === "Abort") { + ChangeSignaturePanel.currentPanel.dispose(); + ChangeSignaturePanel.render(extensionUri, languageClient, command, params, formattingOptions, commandInfo); + } + }); + } + } else { + const panel = window.createWebviewPanel( + ChangeSignaturePanel.type, + ChangeSignaturePanel.title, + ViewColumn.Beside, + { + enableCommandUris: true, + enableScripts: true, + localResourceRoots: [Uri.joinPath(extensionUri, "dist")], + retainContextWhenHidden: true, + } + ); + panel.iconPath = Uri.file(path.join(extensionUri.fsPath, "icons", "icon128.png")); + ChangeSignaturePanel.currentPanel = new ChangeSignaturePanel(panel, extensionUri); + ChangeSignaturePanel.currentPanel.setMetadata(languageClient, command, params, formattingOptions, commandInfo); + } + } + + public setMetadata(languageClient: LanguageClient, command: any, params: any, formattingOptions: any, commandInfo: any) { + this.languageClient = languageClient; + this.command = command; + this.params = params; + this.formattingOptions = formattingOptions; + this.methodIdentifier = commandInfo.methodIdentifier; + this.methodName = commandInfo.methodName as string; + this.modifier = commandInfo.modifier as string; + this.returnType = commandInfo.returnType as string; + this.parameters = commandInfo.parameters as MethodParameter[]; + this.exceptions = commandInfo.exceptions as MethodException[]; + } + + public dispose() { + ChangeSignaturePanel.currentPanel = undefined; + this.panel.dispose(); + while (this.disposables.length) { + const disposable = this.disposables.pop(); + if (disposable) { + disposable.dispose(); + } + } + } + + private getWebviewContent(webview: Webview, extensionUri: Uri) { + + const scriptUri = getUri(webview, extensionUri, [ + "dist", + "changeSignature.js", + ]); + + const nonce = getNonce(); + + return /* html*/ ` + + + + + + + Change Signature + + +
+ + + + `; + } + + private setWebviewMessageListener(webview: Webview) { + webview.onDidReceiveMessage( + async (message: any) => { + const command = message.command; + switch (command) { + case "webviewReady": + await this.panel.webview.postMessage({ + command: "setInitialState", + methodIdentifier: this.methodIdentifier, + methodName: this.methodName, + modifier: this.modifier, + returnType: this.returnType, + parameters: this.parameters, + exceptions: this.exceptions + }); + break; + case "doRefactor": + if (await this.doRefactor(message.methodIdentifier, message.isDelegate, message.methodName, message.accessType, message.returnType, message.parameters, message.exceptions, message.preview)) { + this.dispose(); + } + break; + } + }, + undefined, + this.disposables + ); + } + + /** + * refactor the method with the given parameters. + * + * @param methodIdentifier the handle identifier of the method to refactor. + * @param isDelegate if true, the method will be refactored to a delegate method. + * @param methodName the new name of the method. + * @param accessType the new access type of the method. + * @param returnType the new return type of the method. + * @param parameters the new parameters of the method. + * @param exceptions the new exceptions of the method. + * @param preview if true, the refactoring will be previewed before applied. + * @returns true if the refactoring is successful, false otherwise. + */ + private async doRefactor(methodIdentifier: string, isDelegate: boolean, methodName: string, accessType: string, returnType: string, parameters: MethodParameter[], exceptions: MethodException[], preview: boolean): Promise { + const clientWorkspaceEdit: RefactorWorkspaceEdit = await this.languageClient.sendRequest(GetRefactorEditRequest.type, { + command: this.command, + context: this.params, + options: this.formattingOptions, + commandArguments: [methodIdentifier, isDelegate, methodName, accessType, returnType, parameters, exceptions, preview] + }); + if (clientWorkspaceEdit?.edit) { + const codeEdit: WorkspaceEdit = await this.languageClient.protocol2CodeConverter.asWorkspaceEdit(clientWorkspaceEdit.edit); + if (codeEdit) { + return workspace.applyEdit(codeEdit); + } + } + return false; + } +} diff --git a/src/webview/changeSignature/App.css b/src/webview/changeSignature/App.css new file mode 100644 index 0000000000..2feef25eaa --- /dev/null +++ b/src/webview/changeSignature/App.css @@ -0,0 +1,157 @@ +@import "../../../node_modules/@vscode/codicons/dist/codicon.css"; + +main { + max-width: 600px; + margin-left: auto; + margin-right: auto; +} + +.section { + margin-bottom: 0; +} + +.section-columns { + display: flex; + width: 100%; + padding-left: calc(var(--design-unit) * 1px); +} + +.text-title { + margin: 0; + height: calc(var(--input-height) * 0.8px); +} + +.text-title-content { + padding: 0 0 0 calc(var(--design-unit) * 1px); + margin: 0.5rem 0 0 0; + height: calc(var(--input-height) * 0.8px); +} + +.header-left { + flex-direction: column; + display: flex; + padding: 0.5rem 0.5rem 0.5rem 0; + flex: 0 0 33%; + box-sizing: border-box; +} + +.header { + flex-direction: column; + display: flex; + padding: 0.5rem; + flex: 0 0 33%; + box-sizing: border-box; +} + +.header-right { + flex-direction: column; + display: flex; + padding: 0.5rem 0 0.5rem 0.5rem; + flex: 0 0 33%; + box-sizing: border-box; +} + +.vsc-button-left { + float: left; + margin: 0 0.5rem 0.5rem 0; +} + +.vsc-button { + float: left; + margin: 0 0.5rem 0.5rem 0.5rem; +} + +.add-button { + margin: 0.5rem 0 0 0; +} + +.bottom-buttons { + padding: 0 0 0 calc(var(--design-unit) * 1px); + margin: 0.5rem 0 0 0; +} + +.preview { + padding: 0 0 0 calc(var(--design-unit) * 1px); + margin: 0 0 0.5rem 0; + width: 99%; +} + +.parameters-panel { + margin: 0; + width: calc(99% + 4px); +} + +.parameters-view { + padding: 0 0 0 calc(var(--design-unit) * 1px); + flex-direction: column; +} + +.parameter-cell { + padding-left: 0; + pointer-events: none; +} + +.parameter-cell-title { + padding-left: 0; +} + +.parameter-cell-title:focus { + border-color: var(--vscode-keybindingTable-headerBackground); + background-color: inherit; + color: inherit; +} + +.parameter-cell-header { + background-color: var(--vscode-keybindingTable-headerBackground); +} + +.parameter-cell-edit { + padding-left: 0; + background-color: var(--vscode-input-background); +} + +.parameter-cell-edit-button { + padding: 0; + border: 0; + display: flex; + justify-content: right; + background-color: var(--vscode-input-background); +} + +.parameter-cell-button { + padding: 0; + border: 0; + display: flex; + justify-content: right; +} + +.parameter-cell-button:focus { + background-color: inherit; +} + +.parameter-cell-button:active { + background-color: inherit; +} + +.table-buttons { + display: inline-flex; +} + +.table-buttons-edit { + display: inline-flex; + background-color: var(--vscode-editor-background); +} + +.table-buttons-edit-ok { + margin-left: 0.2rem; + margin-right: 0.2rem; +} + +.table-buttons-edit-cancel { + margin: 0; +} + +.delegate { + padding: 0 0 0 calc(var(--design-unit) * 1px); + margin: 0.5rem 0 0.5rem 0; +} diff --git a/src/webview/changeSignature/App.tsx b/src/webview/changeSignature/App.tsx new file mode 100644 index 0000000000..8535ab2de6 --- /dev/null +++ b/src/webview/changeSignature/App.tsx @@ -0,0 +1,594 @@ +/* eslint-disable @typescript-eslint/naming-convention */ +/* eslint-disable @typescript-eslint/prefer-for-of */ +import { VSCodeButton, VSCodeTextField, VSCodeDropdown, VSCodeOption, VSCodeCheckbox, VSCodePanels, VSCodePanelTab, VSCodePanelView, VSCodeDataGrid, VSCodeDataGridCell, VSCodeDataGridRow, VSCodeTextArea } from "@vscode/webview-ui-toolkit/react"; +import "./App.css"; +import React from "react"; +import { vscode } from "../vscodeApiWrapper"; +import _ from "lodash"; + +type State = UIState & Metadata; + +interface UIState { + focusRow: number; + editParameterRow: number; + editExceptionRow: number; +} + +interface Metadata { + methodIdentifier: string | undefined; + isDelegate: boolean; + methodName: string | undefined; + accessType: string | undefined; + returnType: string | undefined; + parameters: MethodParameter[]; + exceptions: MethodException[]; +} + +interface MethodParameter { + type: string; + name: string; + defaultValue: string; + originalIndex: number; +} + +interface MethodException { + type: string; + typeHandleIdentifier: string | undefined; +} + +export class App extends React.Component<{}, State> { + + private initialMetadata: Metadata; + private static TOOLTIP_RETURN_TYPE: string = "The method return type, can be either name or fully qualified name of the type."; + private static TOOLTIP_PARAMETER_TYPE: string = "The parameter type, can be either name or fully qualified name of the type."; + private static TOOLTIP_PARAMETER_DEFAULT: string = "The parameter default value, used when replacing the occurrences for an added parameter."; + private static TOOLTIP_EXCEPTION_TYPE: string = "The exception type, can be either name or fully qualified name of the type."; + + constructor(props: any) { + super(props); + this.state = { + focusRow: -1, + editParameterRow: -1, + editExceptionRow: -1, + methodIdentifier: undefined, + isDelegate: false, + methodName: undefined, + accessType: undefined, + returnType: undefined, + parameters: [], + exceptions: [] + }; + } + + doRefactor = (preview: boolean) => { + vscode.postMessage({ + preview: preview, + command: "doRefactor", + methodIdentifier: this.state.methodIdentifier, + isDelegate: this.state.isDelegate, + accessType: this.getModifierString(this.state.accessType), + methodName: this.state.methodName, + returnType: this.state.returnType, + parameters: this.state.parameters, + exceptions: this.state.exceptions, + }); + }; + + onChange = (event: any) => { + const id = event.target.id as string; + if (!id) { + return; + } + if (id === "access-modifier") { + this.setState({ + accessType: event.target.value + }); + } else if (id === "returnType") { + this.setState({ + returnType: event.target.value + }); + } else if (id === "methodName") { + this.setState({ + methodName: event.target.value + }); + } + return; + }; + + getPreview = () => { + let parameters = ""; + for (let i = 0; i < this.state.parameters.length; i++) { + parameters += `${this.state.parameters[i].type} ${this.state.parameters[i].name}, `; + } + parameters = parameters.substring(0, parameters.length - 2); + let exceptions = ""; + if (this.state.exceptions.length) { + exceptions = " throws "; + for (let i = 0; i < this.state.exceptions.length; i++) { + exceptions += `${this.state.exceptions[i].type}, `; + } + exceptions = exceptions.substring(0, exceptions.length - 2); + } + let accessTypeString = this.getModifierString(this.state.accessType); + if (accessTypeString?.length) { + accessTypeString += " "; + } + return `${accessTypeString}${this.state.returnType} ${this.state.methodName}(${parameters})${exceptions}`; + }; + + onClick = (event: any) => { + const id = event.target.id as string; + if (!id) { + return; + } + if (id === "refactor") { + this.doRefactor(false); + } else if (id === "preview") { + this.doRefactor(true); + } else if (id === "reset") { + this.setState({ + methodIdentifier: this.initialMetadata.methodIdentifier, + isDelegate: this.initialMetadata.isDelegate, + accessType: this.initialMetadata.accessType, + methodName: this.initialMetadata.methodName, + returnType: this.initialMetadata.returnType, + parameters: _.cloneDeep(this.initialMetadata.parameters), + exceptions: _.cloneDeep(this.initialMetadata.exceptions), + focusRow: -1, + editParameterRow: -1, + editExceptionRow: -1, + }); + } else if (id === "addParameter") { + const parameterNames = this.state.parameters.map(e => { + return e.name; + }); + let newParameterName: string = "newParam"; + let i = 1; + while (parameterNames.includes(newParameterName)) { + i++; + newParameterName = `newParam${i}`; + } + this.setState({ + parameters: [...this.state.parameters, { + type: "Object", + name: newParameterName, + defaultValue: "null", + originalIndex: -1 + }] + }); + } else if (id.startsWith("removeParameter")) { + const selectedRowNumber: number | undefined = this.getSelectedRowNumber(id); + if (selectedRowNumber === undefined) { + return; + } + this.setState({ + parameters: this.state.parameters.filter((e, i) => { + return i !== selectedRowNumber; + }) + }); + } else if (id.startsWith("editParameter")) { + const selectedRowNumber: number | undefined = this.getSelectedRowNumber(id); + if (selectedRowNumber === undefined) { + return; + } + this.setState({ + editParameterRow: selectedRowNumber, + editExceptionRow: -1, + focusRow: -1, + }); + const elementToSelect = document.getElementById(`parameterType-${selectedRowNumber}`); + if (elementToSelect) { + elementToSelect.focus(); + } + } else if (id.startsWith("editException")) { + const selectedRowNumber: number | undefined = this.getSelectedRowNumber(id); + if (selectedRowNumber === undefined) { + return; + } + this.setState({ + editParameterRow: -1, + editExceptionRow: selectedRowNumber, + focusRow: -1, + }); + const elementToSelect = document.getElementById(`exceptionType-${selectedRowNumber}`); + if (elementToSelect) { + elementToSelect.focus(); + } + } else if (id.startsWith("upParameter")) { + const selectedRowNumber: number | undefined = this.getSelectedRowNumber(id); + if (selectedRowNumber === undefined) { + return; + } + const currentParameters = this.state.parameters; + const temp = currentParameters[selectedRowNumber - 1]; + currentParameters[selectedRowNumber - 1] = currentParameters[selectedRowNumber]; + currentParameters[selectedRowNumber] = temp; + this.setState({ + parameters: currentParameters + }); + } else if (id.startsWith("downParameter")) { + const selectedRowNumber: number | undefined = this.getSelectedRowNumber(id); + if (selectedRowNumber === undefined) { + return; + } + const currentParameters = this.state.parameters; + const temp = currentParameters[selectedRowNumber + 1]; + currentParameters[selectedRowNumber + 1] = currentParameters[selectedRowNumber]; + currentParameters[selectedRowNumber] = temp; + this.setState({ + parameters: currentParameters + }); + } else if (id === "addException") { + const exceptionNames = this.state.exceptions.map(e => { + return e.type; + }); + let newExceptionName: string = "Exception"; + let i = 1; + while (exceptionNames.includes(newExceptionName)) { + i++; + newExceptionName = `Exception${i}`; + } + this.setState({ + exceptions: [...this.state.exceptions, { + type: newExceptionName, + typeHandleIdentifier: undefined, + }] + }); + } else if (id.startsWith("removeException")) { + const selectedRowNumber: number | undefined = this.getSelectedRowNumber(id); + if (selectedRowNumber === undefined) { + return; + } + this.setState({ + exceptions: this.state.exceptions.filter((e, i) => { + return i !== selectedRowNumber; + }) + }); + } else if (id === "delegate") { + this.setState({ + isDelegate: event.target.checked + }); + } else if (id.startsWith("confirmParameter")) { + const selectedRowNumber: number | undefined = this.getSelectedRowNumber(id); + if (selectedRowNumber === undefined) { + return; + } + const parameterType = document.getElementById(`parameterType-${selectedRowNumber}`); + const parameterName = document.getElementById(`parameterName-${selectedRowNumber}`); + const parameterDefault = this.isDefaultValueEditable(selectedRowNumber) ? document.getElementById(`parameterDefault-${selectedRowNumber}`) : undefined; + this.setState({ + parameters: this.state.parameters.map((e, i) => { + if (i === selectedRowNumber) { + if (parameterType?.outerText) { + e.type = parameterType.outerText; + } + if (parameterName?.outerText) { + e.name = parameterName.outerText; + } + if (parameterDefault?.outerText) { + e.defaultValue = parameterDefault.outerText; + } + } + return e; + }), + editParameterRow: -1, + editExceptionRow: -1, + focusRow: -1 + }); + } else if (id.startsWith("cancelParameter")) { + const selectedRowNumber: number | undefined = this.getSelectedRowNumber(id); + if (selectedRowNumber === undefined) { + return; + } + const parameterType = document.getElementById(`parameterType-${selectedRowNumber}`); + if (parameterType) { + parameterType.textContent = this.state.parameters[selectedRowNumber].type; + } + const parameterName = document.getElementById(`parameterName-${selectedRowNumber}`); + if (parameterName) { + parameterName.textContent = this.state.parameters[selectedRowNumber].name; + } + if (this.isDefaultValueEditable(selectedRowNumber)) { + const parameterDefault = document.getElementById(`parameterDefault-${selectedRowNumber}`); + if (parameterDefault) { + parameterDefault.textContent = this.state.parameters[selectedRowNumber].defaultValue; + } + } + this.setState({ + editParameterRow: -1, + editExceptionRow: -1, + focusRow: -1 + }); + } else if (id.startsWith("confirmException")) { + const selectedRowNumber: number | undefined = this.getSelectedRowNumber(id); + if (selectedRowNumber === undefined) { + return; + } + const exceptionType = document.getElementById(`exceptionType-${selectedRowNumber}`); + this.setState({ + exceptions: this.state.exceptions.map((e, i) => { + if (i === selectedRowNumber) { + if (exceptionType?.outerText) { + e.type = exceptionType.outerText; + } + } + return e; + }), + editParameterRow: -1, + editExceptionRow: -1, + focusRow: -1 + }); + } else if (id.startsWith("cancelException")) { + const selectedRowNumber: number | undefined = this.getSelectedRowNumber(id); + if (selectedRowNumber === undefined) { + return; + } + const exceptionType = document.getElementById(`exceptionType-${selectedRowNumber}`); + if (exceptionType) { + exceptionType.textContent = this.state.exceptions[selectedRowNumber].type; + } + this.setState({ + editParameterRow: -1, + editExceptionRow: -1, + focusRow: -1 + }); + } + }; + + handleMessage = (event: any) => { + const { data } = event; + const command = data.command as string; + if (command === "setInitialState") { + this.initialMetadata = { + methodIdentifier: data.methodIdentifier, + isDelegate: false, + accessType: this.getAccessTypeString(data.modifier), + methodName: data.methodName, + returnType: data.returnType, + parameters: data.parameters, + exceptions: data.exceptions, + }; + this.setState({ + methodIdentifier: this.initialMetadata.methodIdentifier, + isDelegate: this.initialMetadata.isDelegate, + accessType: this.initialMetadata.accessType, + methodName: this.initialMetadata.methodName, + returnType: this.initialMetadata.returnType, + parameters: _.cloneDeep(this.initialMetadata.parameters), + exceptions: _.cloneDeep(this.initialMetadata.exceptions), + }); + } + }; + + onMouseEnter = (event: any) => { + const id = event.target.id as string; + if (id.includes("Header")) { + this.setState({ + focusRow: -1 + }); + } else if (id) { + const selectedRowNumber: number | undefined = this.getSelectedRowNumber(id); + if (selectedRowNumber !== undefined) { + this.setState({ + focusRow: selectedRowNumber + }); + } + } + }; + + onMouseLeave = (event: any) => { + this.setState({ + focusRow: -1 + }); + }; + + componentDidMount(): void { + this.setTextAreaCursorStyle(); + window.addEventListener("message", this.handleMessage); + vscode.postMessage({ + command: "webviewReady" + }); + } + + isUnchanged = () => { + return this.state.isDelegate === this.initialMetadata?.isDelegate + && this.state.accessType === this.initialMetadata?.accessType + && this.state.methodName === this.initialMetadata?.methodName + && this.state.returnType === this.initialMetadata?.returnType + && this.isArrayEqual(this.state.parameters, this.initialMetadata?.parameters) + && this.isArrayEqual(this.state.exceptions, this.initialMetadata?.exceptions); + }; + + isArrayEqual = (a: any[], b: any[]) => { + return Array.isArray(a) && Array.isArray(b) && a.length === b.length && a.every((e, i) => this.objectsEqual(e, b[i])); + }; + + objectsEqual = (o1, o2) => { + return typeof o1 === 'object' && Object.keys(o1).length > 0 + ? Object.keys(o1).length === Object.keys(o2).length + && Object.keys(o1).every(p => this.objectsEqual(o1[p], o2[p])) + : o1 === o2; + }; + + /** + * Set the cursor style of the text area to text. Since the text area is + * inside a shadow DOM, we need to add a style element to the shadow DOM. + */ + setTextAreaCursorStyle(): void { + const host = document.getElementById("textArea"); + if (host?.shadowRoot) { + const style = document.createElement('style'); + style.innerHTML = '.control { cursor: text !important; }'; + host.shadowRoot.appendChild(style); + } + } + + isDefaultValueEditable = (row: number) => { + return this.state.parameters[row].originalIndex === -1; + }; + + getDefaultValue = (row: number) => { + return this.isDefaultValueEditable(row) ? this.state.parameters[row].defaultValue : "-"; + }; + + generateParameterDataGridRow = (row: number) => { + return + {this.state.parameters[row].type} + {this.state.parameters[row].name} + {this.getDefaultValue(row)} + + {row === this.state.editParameterRow ? +
+ OK + Cancel +
: row === this.state.focusRow ?
+ {row === 0 ? <> : + + } + {row === this.state.parameters.length - 1 ? <> : + + } + + + + + + +
:
+ } +
+
; + }; + + generateExceptionDataGridRow = (row: number) => { + return + {this.state.exceptions[row].type} + + {row === this.state.editExceptionRow ? +
+ OK + Cancel +
: row === this.state.focusRow ?
+ + + + + + +
:
+ } +
+
; + }; + + render = () => { + return ( +
+

Change Method Signature

+
+
+
+
Access modifier:
+ + public + protected + package-private + private + +
+
+
Return type:
+ +
+
+
Method name:
+ +
+
+
+ + Parameters + Exceptions + + + + Type + Name + Default value + + + { + (() => { + const options = []; + for (let row = 0; row < this.state.parameters.length; row++) { + options.push(this.generateParameterDataGridRow(row)); + } + return options; + })() + } + +
+ Add +
+
+ + + + Type + + + { + (() => { + const options = []; + for (let row = 0; row < this.state.exceptions.length; row++) { + options.push(this.generateExceptionDataGridRow(row)); + } + return options; + })() + } + +
+ Add +
+
+
+
Method signature:
+ + Keep original method as delegate to changed method +
+ Refactor + Preview + Reset +
+
+ ); + }; + + /** + * get the row number of the item id. The format is `${description}-${rowNumber}`. + * @param id the item id + * @returns the row number, or undefined if the id is not in the correct format + */ + private getSelectedRowNumber(id: string): number | undefined { + const idSplit: string[] = id.split("-"); + if (idSplit.length !== 2) { + return undefined; + } + return Number(idSplit[1]); + } + + private getModifierString(accessType: string): string { + if (accessType === "package-private") { + return ""; + } + return accessType; + } + + private getAccessTypeString(visibility: string): string { + if (visibility === "") { + return "package-private"; + } + return visibility; + } + +} diff --git a/src/webview/changeSignature/index.tsx b/src/webview/changeSignature/index.tsx new file mode 100644 index 0000000000..16e0258412 --- /dev/null +++ b/src/webview/changeSignature/index.tsx @@ -0,0 +1,10 @@ +import React from "react"; +import ReactDOM from "react-dom"; +import { App } from "./App"; + +ReactDOM.render( + + + , + document.getElementById("root") +); diff --git a/src/webview/vscodeApiWrapper.ts b/src/webview/vscodeApiWrapper.ts new file mode 100644 index 0000000000..0b0eaa0113 --- /dev/null +++ b/src/webview/vscodeApiWrapper.ts @@ -0,0 +1,41 @@ +import type { WebviewApi } from "vscode-webview"; + +/** + * A utility wrapper around the acquireVsCodeApi() function, which enables + * message passing and state management between the webview and extension + * contexts. + * + * This utility also enables webview code to be run in a web browser-based + * dev server by using native web browser features that mock the functionality + * enabled by acquireVsCodeApi. + */ +class VSCodeAPIWrapper { + private readonly vsCodeApi: WebviewApi | undefined; + + constructor() { + // Check if the acquireVsCodeApi function exists in the current development + // context (i.e. VS Code development window or web browser) + if (typeof acquireVsCodeApi === "function") { + this.vsCodeApi = acquireVsCodeApi(); + } + } + + /** + * Post a message (i.e. send arbitrary data) to the owner of the webview. + * + * @remarks When running webview code inside a web browser, postMessage will instead + * log the given message to the console. + * + * @param message Abitrary data (must be JSON serializable) to send to the extension context. + */ + public postMessage(message: unknown) { + if (this.vsCodeApi) { + this.vsCodeApi.postMessage(message); + } else { + console.log(`VS Code API not available, raw message: ${message}`); + } + } +} + +// Exports class singleton to prevent multiple invocations of acquireVsCodeApi. +export const vscode = new VSCodeAPIWrapper(); diff --git a/src/webviewUtils.ts b/src/webviewUtils.ts new file mode 100644 index 0000000000..394fd9410e --- /dev/null +++ b/src/webviewUtils.ts @@ -0,0 +1,14 @@ +import { Uri, Webview } from "vscode"; + +export function getUri(webview: Webview, extensionUri: Uri, pathList: string[]) { + return webview.asWebviewUri(Uri.joinPath(extensionUri, ...pathList)); +} + +export function getNonce() { + let text = ""; + const possible = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; + for (let i = 0; i < 32; i++) { + text += possible.charAt(Math.floor(Math.random() * possible.length)); + } + return text; +} diff --git a/test/lightweight-mode-suite/index.ts b/test/lightweight-mode-suite/index.ts index b04cc328fe..895e94daf5 100644 --- a/test/lightweight-mode-suite/index.ts +++ b/test/lightweight-mode-suite/index.ts @@ -11,8 +11,8 @@ // a possible error to the callback or null if none. import * as path from 'path'; -import * as Mocha from 'mocha'; -import * as glob from 'glob'; +import Mocha from 'mocha'; +import glob from 'glob'; export function run(testsRoot: string): Promise { const mocha = new Mocha({ diff --git a/test/standard-mode-suite/extension.test.ts b/test/standard-mode-suite/extension.test.ts index 0634c730ab..4dd115224e 100644 --- a/test/standard-mode-suite/extension.test.ts +++ b/test/standard-mode-suite/extension.test.ts @@ -1,4 +1,4 @@ -import * as assert from 'assert'; +import assert from 'assert'; import * as fs from 'fs'; import * as path from 'path'; import { env } from 'process'; diff --git a/test/standard-mode-suite/index.ts b/test/standard-mode-suite/index.ts index b04cc328fe..895e94daf5 100644 --- a/test/standard-mode-suite/index.ts +++ b/test/standard-mode-suite/index.ts @@ -11,8 +11,8 @@ // a possible error to the callback or null if none. import * as path from 'path'; -import * as Mocha from 'mocha'; -import * as glob from 'glob'; +import Mocha from 'mocha'; +import glob from 'glob'; export function run(testsRoot: string): Promise { const mocha = new Mocha({ diff --git a/tsconfig.json b/tsconfig.json index 0a41f04d89..fe775f7f67 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -2,13 +2,23 @@ "compilerOptions": { "target": "es6", "lib": [ - "es6" - ], + "es6", + "DOM" + ], "module": "commonjs", "moduleResolution": "node", "outDir": "out", "sourceMap": true, - "plugins": [{ "name": "eslint" }] + "plugins": [ + { + "name": "eslint" + } + ], + /* Enabled for React */ + "jsx": "react-jsx", + "esModuleInterop": true, + "allowSyntheticDefaultImports": true, + "skipLibCheck": true, }, "exclude": [ "node_modules", diff --git a/webpack.config.js b/webpack.config.js index eec53b45cb..a189186c02 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -3,6 +3,7 @@ //@ts-check const ESLintWebpackPlugin = require('eslint-webpack-plugin'); +const webpack = require('webpack'); const path = require('path'); /**@type {import('webpack').Configuration}*/ @@ -51,4 +52,44 @@ const config = { level: 'log', }, } -module.exports = config; + +const configAssets = { + name: 'assets', + mode: 'none', + entry: { + changeSignature: './src/webview/changeSignature/index.tsx', + }, + module: { + rules: [{ + test: /\.ts(x?)$/, + exclude: /node_modules/, + use: 'ts-loader' + }, { + test: /\.(css)$/, + use: [{ + loader: 'style-loader' + }, { + loader: 'css-loader' + }] + }, { + test: /\.(ttf)$/, + type: 'asset/inline', + }] + }, + output: { + filename: '[name].js', + path: path.resolve(__dirname, 'dist'), + publicPath: '/', + devtoolModuleFilenameTemplate: "../[resource-path]" + }, + plugins: [ + new webpack.ProvidePlugin({ + process: 'process/browser', + }), + ], + devtool: 'source-map', + resolve: { + extensions: ['.js', '.ts', '.tsx'] + } +} +module.exports = [config, configAssets];