diff --git a/README.MD b/README.MD index bb02d03e..90279f9d 100644 --- a/README.MD +++ b/README.MD @@ -88,6 +88,18 @@ Removes import statements from references when symbol has usages in the same fil You can quickly disable this plugin functionality by setting this setting to false. Useful for debugging a problem for example. +> Note: this setting doesn't disable Vue support. + +### Vue Support + +`.vue` SFC files support is disabled, but can be enabled with setting and when [Vue Language Features (Volar)](https://marketplace.visualstudio.com/items?itemName=Vue.volar) is installed. + +Enable now: `"tsEssentialPlugins.enableVueSupport": true` (if you're not using local `./volar.config.js`) + +For the first time, it will configure `volar.vueserver.configFilePath` setting. + +This also makes plugin work in Volar's takeover mode! + ### Web Support > Note: when you open TS/JS file in the web for the first time you currently need to switch editors to make everything work! diff --git a/buildTsPlugin.mjs b/buildTsPlugin.mjs index f16f02b3..4989d4b1 100644 --- a/buildTsPlugin.mjs +++ b/buildTsPlugin.mjs @@ -1,6 +1,20 @@ //@ts-check import buildTsPlugin from '@zardoy/vscode-utils/build/buildTypescriptPlugin.js' -import { analyzeMetafile } from 'esbuild' +import { build, analyzeMetafile } from 'esbuild' + +build({ + // bundle: true, + // minify: !watch, + entryPoints: ['./typescript/src/volarConfig.ts'], + outfile: './out/volarConfig.js', + format: 'cjs', + logLevel: 'info', + platform: 'node', + // banner: { + // js: 'let ts, tsFull;', + // }, + // treeShaking: true, +}) const result = await buildTsPlugin('typescript', undefined, undefined, { minify: !process.argv.includes('--watch'), diff --git a/package.json b/package.json index e9904ba5..2b7f3efd 100644 --- a/package.json +++ b/package.json @@ -77,7 +77,8 @@ "onLanguage:javascript", "onLanguage:javascriptreact", "onLanguage:typescript", - "onLanguage:typescriptreact" + "onLanguage:typescriptreact", + "onLanguage:vue" ], "scripts": { "start": "vscode-framework start --skip-launching", @@ -98,7 +99,7 @@ "@types/semver": "^7.3.13", "@types/vscode": "^1.63.1", "@zardoy/tsconfig": "^1.3.1", - "esbuild": "^0.15.15", + "esbuild": "^0.16.16", "fs-extra": "^10.1.0", "got": "^12.5.3", "type-fest": "^2.13.1", @@ -118,6 +119,8 @@ "@types/lodash": "^4.14.182", "@types/mocha": "^9.1.1", "@types/pluralize": "^0.0.29", + "@volar/language-server": "^1.0.24", + "@volar/language-service": "^1.0.24", "@vscode/emmet-helper": "^2.8.4", "@vscode/test-electron": "^2.1.5", "@zardoy/utils": "^0.0.9", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index b942dd19..f71054e5 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -17,6 +17,8 @@ importers: '@types/pluralize': ^0.0.29 '@types/semver': ^7.3.13 '@types/vscode': ^1.63.1 + '@volar/language-server': ^1.0.24 + '@volar/language-service': ^1.0.24 '@vscode/emmet-helper': ^2.8.4 '@vscode/test-electron': ^2.1.5 '@zardoy/tsconfig': ^1.3.1 @@ -60,10 +62,12 @@ importers: '@types/lodash': 4.14.182 '@types/mocha': 9.1.1 '@types/pluralize': 0.0.29 + '@volar/language-server': 1.0.24 + '@volar/language-service': 1.0.24 '@vscode/emmet-helper': 2.8.4 '@vscode/test-electron': 2.1.5 '@zardoy/utils': 0.0.9 - '@zardoy/vscode-utils': 0.0.47_wuodti7e77enk5whyk5rob4yre + '@zardoy/vscode-utils': 0.0.47_ai5wishe5ovkyp5mm2oyhrbtcu chai: 4.3.6 chokidar: 3.5.3 chokidar-cli: 3.0.0 @@ -95,7 +99,7 @@ importers: '@types/semver': 7.3.13 '@types/vscode': 1.63.1 '@zardoy/tsconfig': 1.3.1_typescript@4.9.3 - esbuild: 0.15.15 + esbuild: 0.15.18 fs-extra: 10.1.0 got: 12.5.3 type-fest: 2.13.1 @@ -373,16 +377,16 @@ packages: resolution: {integrity: sha512-8HqW8EVqjnCmWXVpqAOZf+EGESdkR27odcMMMGefgKXtar00SoYNSryGv//TELI4T3QFsECo78p+0lmalk/CFA==} dev: false - /@esbuild/android-arm/0.15.15: - resolution: {integrity: sha512-JJjZjJi2eBL01QJuWjfCdZxcIgot+VoK6Fq7eKF9w4YHm9hwl7nhBR1o2Wnt/WcANk5l9SkpvrldW1PLuXxcbw==} + /@esbuild/android-arm/0.15.18: + resolution: {integrity: sha512-5GT+kcs2WVGjVs7+boataCkO5Fg0y4kCjzkB5bAip7H4jfnOS3dA6KPiww9W1OEKTKeAcUVhdZGvgI65OXmUnw==} engines: {node: '>=12'} cpu: [arm] os: [android] requiresBuild: true optional: true - /@esbuild/linux-loong64/0.15.15: - resolution: {integrity: sha512-lhz6UNPMDXUhtXSulw8XlFAtSYO26WmHQnCi2Lg2p+/TMiJKNLtZCYUxV4wG6rZMzXmr8InGpNwk+DLT2Hm0PA==} + /@esbuild/linux-loong64/0.15.18: + resolution: {integrity: sha512-L4jVKS82XVhw2nvzLg/19ClLWg0y27ulRwuP7lcyL6AbUWB5aPglXY3M21mauDQMDfRLs8cQmeT03r/+X3cZYQ==} engines: {node: '>=12'} cpu: [loong64] os: [linux] @@ -818,6 +822,61 @@ packages: resolution: {integrity: sha512-sL/cEvJWAnClXw0wHk85/2L0G6Sj8UB0Ctc1TEMbKSsmpRosqhwj9gWgFRZSrBr2f9tiXISwNhCPmlfqUqyb9Q==} dev: false + /@volar/language-core/1.0.24: + resolution: {integrity: sha512-vTN+alJiWwK0Pax6POqrmevbtFW2dXhjwWiW/MW4f48eDYPLdyURWcr8TixO7EN/nHsUBj2udT7igFKPtjyAKg==} + dependencies: + '@volar/source-map': 1.0.24 + muggle-string: 0.1.0 + dev: false + + /@volar/language-server/1.0.24: + resolution: {integrity: sha512-F5T8ZYXGSSfzCtx8Yv02iRs0N4i1ddhFynTA6nS0axADDISklcwW5LTt9A4pO45/6yujyGj84+Kmxhtwa0+X6g==} + dependencies: + '@volar/language-core': 1.0.24 + '@volar/language-service': 1.0.24 + '@volar/shared': 1.0.24 + request-light: 0.6.0 + typesafe-path: 0.2.2 + vscode-html-languageservice: 5.0.3 + vscode-languageserver: 8.0.2 + vscode-languageserver-protocol: 3.17.2 + vscode-languageserver-textdocument: 1.0.8 + vscode-uri: 3.0.7 + dev: false + + /@volar/language-service/1.0.24: + resolution: {integrity: sha512-Y37lzNRVM2xDY1kFXn88n9nkXUD1H0ZffXXLbsFbkUhMM1pKBgcJGEuRXoATXu62R0NmrcGXrktjTPQ4VjKQuA==} + dependencies: + '@volar/language-core': 1.0.24 + '@volar/shared': 1.0.24 + '@volar/source-map': 1.0.24 + '@volar/typescript-faster': 1.0.24 + vscode-html-languageservice: 5.0.3 + vscode-json-languageservice: 5.1.3 + vscode-languageserver-protocol: 3.17.2 + vscode-languageserver-textdocument: 1.0.8 + vscode-uri: 3.0.7 + dev: false + + /@volar/shared/1.0.24: + resolution: {integrity: sha512-30mqmNsw49xlGhziL59z6kP6/TlBatkeOzMImUSWmn1QtqV7r2onDGgNNdCqSa1esTo4UtGup6yqqM2oUwrMSQ==} + dependencies: + typesafe-path: 0.2.2 + vscode-uri: 3.0.7 + dev: false + + /@volar/source-map/1.0.24: + resolution: {integrity: sha512-Qsv/tkplx18pgBr8lKAbM1vcDqgkGKQzbChg6NW+v0CZc3G7FLmK+WrqEPzKlN7Cwdc6XVL559Nod8WKAfKr4A==} + dependencies: + muggle-string: 0.1.0 + dev: false + + /@volar/typescript-faster/1.0.24: + resolution: {integrity: sha512-8JtPkR3p2EVpBrD5puuP4y4CA7LVIkMV/+O2a/biD5zHyfqwN+s5j1/perp0D/5RgyxLJWhWIIYo1HR5ac5jlA==} + dependencies: + semver: 7.3.8 + dev: false + /@vscode/emmet-helper/2.8.4: resolution: {integrity: sha512-lUki5QLS47bz/U8IlG9VQ+1lfxMtxMZENmU5nu4Z71eOD5j9FK0SmYGL5NiVJg9WBWeAU0VxRADMY2Qpq7BfVg==} dependencies: @@ -829,6 +888,10 @@ packages: vscode-uri: 2.1.2 dev: false + /@vscode/l10n/0.0.10: + resolution: {integrity: sha512-E1OCmDcDWa0Ya7vtSjp/XfHFGqYJfh+YPC1RkATU71fTac+j1JjCcB3qwSzmlKAighx2WxhLlfhS0RwAN++PFQ==} + dev: false + /@vscode/test-electron/2.1.5: resolution: {integrity: sha512-O/ioqFpV+RvKbRykX2ItYPnbcZ4Hk5V0rY4uhQjQTLhGL9WZUvS7exzuYQCCI+ilSqJpctvxq2llTfGXf9UnnA==} engines: {node: '>=8.9.3'} @@ -895,7 +958,7 @@ packages: type-fest: 2.19.0 dev: false - /@zardoy/vscode-utils/0.0.47_wuodti7e77enk5whyk5rob4yre: + /@zardoy/vscode-utils/0.0.47_ai5wishe5ovkyp5mm2oyhrbtcu: resolution: {integrity: sha512-0xsdTonXFxcBdsHcWgkSfJSpP9VOvOiT6oo/LuA5FOGJJI/5W6e6EDwN6Y9FahRWdrYmOfliekEMGZGk/eEWcQ==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} hasBin: true @@ -919,7 +982,7 @@ packages: '@zardoy/utils': 0.0.4 chokidar: 3.5.3 commander: 9.4.1 - esbuild: 0.15.15 + esbuild: 0.15.18 execa: 5.1.1 fs-extra: 10.1.0 lodash.throttle: 4.1.1 @@ -1876,194 +1939,194 @@ packages: is-symbol: 1.0.4 dev: false - /esbuild-android-64/0.15.15: - resolution: {integrity: sha512-F+WjjQxO+JQOva3tJWNdVjouFMLK6R6i5gjDvgUthLYJnIZJsp1HlF523k73hELY20WPyEO8xcz7aaYBVkeg5Q==} + /esbuild-android-64/0.15.18: + resolution: {integrity: sha512-wnpt3OXRhcjfIDSZu9bnzT4/TNTDsOUvip0foZOUBG7QbSt//w3QV4FInVJxNhKc/ErhUxc5z4QjHtMi7/TbgA==} engines: {node: '>=12'} cpu: [x64] os: [android] requiresBuild: true optional: true - /esbuild-android-arm64/0.15.15: - resolution: {integrity: sha512-attlyhD6Y22jNyQ0fIIQ7mnPvDWKw7k6FKnsXlBvQE6s3z6s6cuEHcSgoirquQc7TmZgVCK5fD/2uxmRN+ZpcQ==} + /esbuild-android-arm64/0.15.18: + resolution: {integrity: sha512-G4xu89B8FCzav9XU8EjsXacCKSG2FT7wW9J6hOc18soEHJdtWu03L3TQDGf0geNxfLTtxENKBzMSq9LlbjS8OQ==} engines: {node: '>=12'} cpu: [arm64] os: [android] requiresBuild: true optional: true - /esbuild-darwin-64/0.15.15: - resolution: {integrity: sha512-ohZtF8W1SHJ4JWldsPVdk8st0r9ExbAOSrBOh5L+Mq47i696GVwv1ab/KlmbUoikSTNoXEhDzVpxUR/WIO19FQ==} + /esbuild-darwin-64/0.15.18: + resolution: {integrity: sha512-2WAvs95uPnVJPuYKP0Eqx+Dl/jaYseZEUUT1sjg97TJa4oBtbAKnPnl3b5M9l51/nbx7+QAEtuummJZW0sBEmg==} engines: {node: '>=12'} cpu: [x64] os: [darwin] requiresBuild: true optional: true - /esbuild-darwin-arm64/0.15.15: - resolution: {integrity: sha512-P8jOZ5zshCNIuGn+9KehKs/cq5uIniC+BeCykvdVhx/rBXSxmtj3CUIKZz4sDCuESMbitK54drf/2QX9QHG5Ag==} + /esbuild-darwin-arm64/0.15.18: + resolution: {integrity: sha512-tKPSxcTJ5OmNb1btVikATJ8NftlyNlc8BVNtyT/UAr62JFOhwHlnoPrhYWz09akBLHI9nElFVfWSTSRsrZiDUA==} engines: {node: '>=12'} cpu: [arm64] os: [darwin] requiresBuild: true optional: true - /esbuild-freebsd-64/0.15.15: - resolution: {integrity: sha512-KkTg+AmDXz1IvA9S1gt8dE24C8Thx0X5oM0KGF322DuP+P3evwTL9YyusHAWNsh4qLsR80nvBr/EIYs29VSwuA==} + /esbuild-freebsd-64/0.15.18: + resolution: {integrity: sha512-TT3uBUxkteAjR1QbsmvSsjpKjOX6UkCstr8nMr+q7zi3NuZ1oIpa8U41Y8I8dJH2fJgdC3Dj3CXO5biLQpfdZA==} engines: {node: '>=12'} cpu: [x64] os: [freebsd] requiresBuild: true optional: true - /esbuild-freebsd-arm64/0.15.15: - resolution: {integrity: sha512-FUcML0DRsuyqCMfAC+HoeAqvWxMeq0qXvclZZ/lt2kLU6XBnDA5uKTLUd379WYEyVD4KKFctqWd9tTuk8C/96g==} + /esbuild-freebsd-arm64/0.15.18: + resolution: {integrity: sha512-R/oVr+X3Tkh+S0+tL41wRMbdWtpWB8hEAMsOXDumSSa6qJR89U0S/PpLXrGF7Wk/JykfpWNokERUpCeHDl47wA==} engines: {node: '>=12'} cpu: [arm64] os: [freebsd] requiresBuild: true optional: true - /esbuild-linux-32/0.15.15: - resolution: {integrity: sha512-q28Qn5pZgHNqug02aTkzw5sW9OklSo96b5nm17Mq0pDXrdTBcQ+M6Q9A1B+dalFeynunwh/pvfrNucjzwDXj+Q==} + /esbuild-linux-32/0.15.18: + resolution: {integrity: sha512-lphF3HiCSYtaa9p1DtXndiQEeQDKPl9eN/XNoBf2amEghugNuqXNZA/ZovthNE2aa4EN43WroO0B85xVSjYkbg==} engines: {node: '>=12'} cpu: [ia32] os: [linux] requiresBuild: true optional: true - /esbuild-linux-64/0.15.15: - resolution: {integrity: sha512-217KPmWMirkf8liO+fj2qrPwbIbhNTGNVtvqI1TnOWJgcMjUWvd677Gq3fTzXEjilkx2yWypVnTswM2KbXgoAg==} + /esbuild-linux-64/0.15.18: + resolution: {integrity: sha512-hNSeP97IviD7oxLKFuii5sDPJ+QHeiFTFLoLm7NZQligur8poNOWGIgpQ7Qf8Balb69hptMZzyOBIPtY09GZYw==} engines: {node: '>=12'} cpu: [x64] os: [linux] requiresBuild: true optional: true - /esbuild-linux-arm/0.15.15: - resolution: {integrity: sha512-RYVW9o2yN8yM7SB1yaWr378CwrjvGCyGybX3SdzPHpikUHkME2AP55Ma20uNwkNyY2eSYFX9D55kDrfQmQBR4w==} + /esbuild-linux-arm/0.15.18: + resolution: {integrity: sha512-UH779gstRblS4aoS2qpMl3wjg7U0j+ygu3GjIeTonCcN79ZvpPee12Qun3vcdxX+37O5LFxz39XeW2I9bybMVA==} engines: {node: '>=12'} cpu: [arm] os: [linux] requiresBuild: true optional: true - /esbuild-linux-arm64/0.15.15: - resolution: {integrity: sha512-/ltmNFs0FivZkYsTzAsXIfLQX38lFnwJTWCJts0IbCqWZQe+jjj0vYBNbI0kmXLb3y5NljiM5USVAO1NVkdh2g==} + /esbuild-linux-arm64/0.15.18: + resolution: {integrity: sha512-54qr8kg/6ilcxd+0V3h9rjT4qmjc0CccMVWrjOEM/pEcUzt8X62HfBSeZfT2ECpM7104mk4yfQXkosY8Quptug==} engines: {node: '>=12'} cpu: [arm64] os: [linux] requiresBuild: true optional: true - /esbuild-linux-mips64le/0.15.15: - resolution: {integrity: sha512-PksEPb321/28GFFxtvL33yVPfnMZihxkEv5zME2zapXGp7fA1X2jYeiTUK+9tJ/EGgcNWuwvtawPxJG7Mmn86A==} + /esbuild-linux-mips64le/0.15.18: + resolution: {integrity: sha512-Mk6Ppwzzz3YbMl/ZZL2P0q1tnYqh/trYZ1VfNP47C31yT0K8t9s7Z077QrDA/guU60tGNp2GOwCQnp+DYv7bxQ==} engines: {node: '>=12'} cpu: [mips64el] os: [linux] requiresBuild: true optional: true - /esbuild-linux-ppc64le/0.15.15: - resolution: {integrity: sha512-ek8gJBEIhcpGI327eAZigBOHl58QqrJrYYIZBWQCnH3UnXoeWMrMZLeeZL8BI2XMBhP+sQ6ERctD5X+ajL/AIA==} + /esbuild-linux-ppc64le/0.15.18: + resolution: {integrity: sha512-b0XkN4pL9WUulPTa/VKHx2wLCgvIAbgwABGnKMY19WhKZPT+8BxhZdqz6EgkqCLld7X5qiCY2F/bfpUUlnFZ9w==} engines: {node: '>=12'} cpu: [ppc64] os: [linux] requiresBuild: true optional: true - /esbuild-linux-riscv64/0.15.15: - resolution: {integrity: sha512-H5ilTZb33/GnUBrZMNJtBk7/OXzDHDXjIzoLXHSutwwsLxSNaLxzAaMoDGDd/keZoS+GDBqNVxdCkpuiRW4OSw==} + /esbuild-linux-riscv64/0.15.18: + resolution: {integrity: sha512-ba2COaoF5wL6VLZWn04k+ACZjZ6NYniMSQStodFKH/Pu6RxzQqzsmjR1t9QC89VYJxBeyVPTaHuBMCejl3O/xg==} engines: {node: '>=12'} cpu: [riscv64] os: [linux] requiresBuild: true optional: true - /esbuild-linux-s390x/0.15.15: - resolution: {integrity: sha512-jKaLUg78mua3rrtrkpv4Or2dNTJU7bgHN4bEjT4OX4GR7nLBSA9dfJezQouTxMmIW7opwEC5/iR9mpC18utnxQ==} + /esbuild-linux-s390x/0.15.18: + resolution: {integrity: sha512-VbpGuXEl5FCs1wDVp93O8UIzl3ZrglgnSQ+Hu79g7hZu6te6/YHgVJxCM2SqfIila0J3k0csfnf8VD2W7u2kzQ==} engines: {node: '>=12'} cpu: [s390x] os: [linux] requiresBuild: true optional: true - /esbuild-netbsd-64/0.15.15: - resolution: {integrity: sha512-aOvmF/UkjFuW6F36HbIlImJTTx45KUCHJndtKo+KdP8Dhq3mgLRKW9+6Ircpm8bX/RcS3zZMMmaBLkvGY06Gvw==} + /esbuild-netbsd-64/0.15.18: + resolution: {integrity: sha512-98ukeCdvdX7wr1vUYQzKo4kQ0N2p27H7I11maINv73fVEXt2kyh4K4m9f35U1K43Xc2QGXlzAw0K9yoU7JUjOg==} engines: {node: '>=12'} cpu: [x64] os: [netbsd] requiresBuild: true optional: true - /esbuild-openbsd-64/0.15.15: - resolution: {integrity: sha512-HFFX+WYedx1w2yJ1VyR1Dfo8zyYGQZf1cA69bLdrHzu9svj6KH6ZLK0k3A1/LFPhcEY9idSOhsB2UyU0tHPxgQ==} + /esbuild-openbsd-64/0.15.18: + resolution: {integrity: sha512-yK5NCcH31Uae076AyQAXeJzt/vxIo9+omZRKj1pauhk3ITuADzuOx5N2fdHrAKPxN+zH3w96uFKlY7yIn490xQ==} engines: {node: '>=12'} cpu: [x64] os: [openbsd] requiresBuild: true optional: true - /esbuild-sunos-64/0.15.15: - resolution: {integrity: sha512-jOPBudffG4HN8yJXcK9rib/ZTFoTA5pvIKbRrt3IKAGMq1EpBi4xoVoSRrq/0d4OgZLaQbmkHp8RO9eZIn5atA==} + /esbuild-sunos-64/0.15.18: + resolution: {integrity: sha512-On22LLFlBeLNj/YF3FT+cXcyKPEI263nflYlAhz5crxtp3yRG1Ugfr7ITyxmCmjm4vbN/dGrb/B7w7U8yJR9yw==} engines: {node: '>=12'} cpu: [x64] os: [sunos] requiresBuild: true optional: true - /esbuild-windows-32/0.15.15: - resolution: {integrity: sha512-MDkJ3QkjnCetKF0fKxCyYNBnOq6dmidcwstBVeMtXSgGYTy8XSwBeIE4+HuKiSsG6I/mXEb++px3IGSmTN0XiA==} + /esbuild-windows-32/0.15.18: + resolution: {integrity: sha512-o+eyLu2MjVny/nt+E0uPnBxYuJHBvho8vWsC2lV61A7wwTWC3jkN2w36jtA+yv1UgYkHRihPuQsL23hsCYGcOQ==} engines: {node: '>=12'} cpu: [ia32] os: [win32] requiresBuild: true optional: true - /esbuild-windows-64/0.15.15: - resolution: {integrity: sha512-xaAUIB2qllE888SsMU3j9nrqyLbkqqkpQyWVkfwSil6BBPgcPk3zOFitTTncEKCLTQy3XV9RuH7PDj3aJDljWA==} + /esbuild-windows-64/0.15.18: + resolution: {integrity: sha512-qinug1iTTaIIrCorAUjR0fcBk24fjzEedFYhhispP8Oc7SFvs+XeW3YpAKiKp8dRpizl4YYAhxMjlftAMJiaUw==} engines: {node: '>=12'} cpu: [x64] os: [win32] requiresBuild: true optional: true - /esbuild-windows-arm64/0.15.15: - resolution: {integrity: sha512-ttuoCYCIJAFx4UUKKWYnFdrVpoXa3+3WWkXVI6s09U+YjhnyM5h96ewTq/WgQj9LFSIlABQvadHSOQyAVjW5xQ==} + /esbuild-windows-arm64/0.15.18: + resolution: {integrity: sha512-q9bsYzegpZcLziq0zgUi5KqGVtfhjxGbnksaBFYmWLxeV/S1fK4OLdq2DFYnXcLMjlZw2L0jLsk1eGoB522WXQ==} engines: {node: '>=12'} cpu: [arm64] os: [win32] requiresBuild: true optional: true - /esbuild/0.15.15: - resolution: {integrity: sha512-TEw/lwK4Zzld9x3FedV6jy8onOUHqcEX3ADFk4k+gzPUwrxn8nWV62tH0udo8jOtjFodlEfc4ypsqX3e+WWO6w==} + /esbuild/0.15.18: + resolution: {integrity: sha512-x/R72SmW3sSFRm5zrrIjAhCeQSAWoni3CmHEqfQrZIQTM3lVCdehdwuIqaOtfC2slvpdlLa62GYoN8SxT23m6Q==} engines: {node: '>=12'} hasBin: true requiresBuild: true optionalDependencies: - '@esbuild/android-arm': 0.15.15 - '@esbuild/linux-loong64': 0.15.15 - esbuild-android-64: 0.15.15 - esbuild-android-arm64: 0.15.15 - esbuild-darwin-64: 0.15.15 - esbuild-darwin-arm64: 0.15.15 - esbuild-freebsd-64: 0.15.15 - esbuild-freebsd-arm64: 0.15.15 - esbuild-linux-32: 0.15.15 - esbuild-linux-64: 0.15.15 - esbuild-linux-arm: 0.15.15 - esbuild-linux-arm64: 0.15.15 - esbuild-linux-mips64le: 0.15.15 - esbuild-linux-ppc64le: 0.15.15 - esbuild-linux-riscv64: 0.15.15 - esbuild-linux-s390x: 0.15.15 - esbuild-netbsd-64: 0.15.15 - esbuild-openbsd-64: 0.15.15 - esbuild-sunos-64: 0.15.15 - esbuild-windows-32: 0.15.15 - esbuild-windows-64: 0.15.15 - esbuild-windows-arm64: 0.15.15 + '@esbuild/android-arm': 0.15.18 + '@esbuild/linux-loong64': 0.15.18 + esbuild-android-64: 0.15.18 + esbuild-android-arm64: 0.15.18 + esbuild-darwin-64: 0.15.18 + esbuild-darwin-arm64: 0.15.18 + esbuild-freebsd-64: 0.15.18 + esbuild-freebsd-arm64: 0.15.18 + esbuild-linux-32: 0.15.18 + esbuild-linux-64: 0.15.18 + esbuild-linux-arm: 0.15.18 + esbuild-linux-arm64: 0.15.18 + esbuild-linux-mips64le: 0.15.18 + esbuild-linux-ppc64le: 0.15.18 + esbuild-linux-riscv64: 0.15.18 + esbuild-linux-s390x: 0.15.18 + esbuild-netbsd-64: 0.15.18 + esbuild-openbsd-64: 0.15.18 + esbuild-sunos-64: 0.15.18 + esbuild-windows-32: 0.15.18 + esbuild-windows-64: 0.15.18 + esbuild-windows-arm64: 0.15.18 /escalade/3.1.1: resolution: {integrity: sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==} @@ -3365,6 +3428,10 @@ packages: /jsonc-parser/3.0.0: resolution: {integrity: sha512-fQzRfAbIBnR0IQvftw9FJveWiHp72Fg20giDrHz6TdfB12UH/uue0D3hm57UB5KgAVuniLMCaS8P1IMj9NR7cA==} + /jsonc-parser/3.2.0: + resolution: {integrity: sha512-gfFQZrcTc8CnKXp6Y4/CBT3fTc0OVuDofpre4aEeEpSBPV5X5v4+Vmx+8snU7RLPrNHPKSgLxGo9YuQzz20o+w==} + dev: false + /jsonfile/6.1.0: resolution: {integrity: sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==} dependencies: @@ -3785,6 +3852,10 @@ packages: resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} dev: false + /muggle-string/0.1.0: + resolution: {integrity: sha512-Tr1knR3d2mKvvWthlk7202rywKbiOm4rVFLsfAaSIhJ6dt9o47W4S+JMtWhd/PW9Wrdew2/S2fSvhz3E2gkfEg==} + dev: false + /multimap/1.1.0: resolution: {integrity: sha512-0ZIR9PasPxGXmRsEF8jsDzndzHDj7tIav+JUmvIFB/WHswliFnquxECT/De7GR4yg99ky/NlRKJT82G1y271bw==} dev: false @@ -4370,6 +4441,10 @@ packages: engines: {node: '>=8'} dev: false + /request-light/0.6.0: + resolution: {integrity: sha512-D3TyWnzX4Kej7ZomWbD+ZqnmzKw/otLHU4tsuhsnF3CoIBo9y0JsxkQXGAqBC4FH/y1hm/ry0/hrsVaKMCkljA==} + dev: false + /require-directory/2.1.1: resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} engines: {node: '>=0.10.0'} @@ -5031,6 +5106,10 @@ packages: resolution: {integrity: sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=} dev: false + /typesafe-path/0.2.2: + resolution: {integrity: sha512-OJabfkAg1WLZSqJAJ0Z6Sdt3utnbzr/jh+NAHoyWHJe8CMSy79Gm085094M9nvTPy22KzTVn5Zq5mbapCI/hPA==} + dev: false + /typescript-json-schema/0.51.0: resolution: {integrity: sha512-POhWbUNs2oaBti1W9k/JwS+uDsaZD9J/KQiZ/iXRQEOD0lTn9VmshIls9tn+A9X6O+smPjeEz5NEy6WTkCCzrQ==} hasBin: true @@ -5195,7 +5274,7 @@ packages: optional: true dependencies: '@types/node': 16.18.3 - esbuild: 0.15.15 + esbuild: 0.15.18 postcss: 8.4.19 resolve: 1.22.1 rollup: 2.79.1 @@ -5279,7 +5358,7 @@ packages: commander: 8.3.0 cosmiconfig: 7.0.1 del: 6.0.0 - esbuild: 0.15.15 + esbuild: 0.15.18 escape-string-regexp: 4.0.0 execa: 5.1.1 exit-hook: 2.2.1 @@ -5309,14 +5388,60 @@ packages: - utf-8-validate dev: false + /vscode-html-languageservice/5.0.3: + resolution: {integrity: sha512-6rfrtcHhXDMXmC5pR2WXrx02HiNCzQDynOBMn+53zLxr2hvZrDzoc0QgC0FaFGfcglf7GeOsfhkWvJBFC/a70g==} + dependencies: + '@vscode/l10n': 0.0.10 + vscode-languageserver-textdocument: 1.0.8 + vscode-languageserver-types: 3.17.2 + vscode-uri: 3.0.7 + dev: false + + /vscode-json-languageservice/5.1.3: + resolution: {integrity: sha512-p0O1Ql5+zyWFIBU4cSxnDcuq9OnbE0MmvNKDYYvz4EPsZ9EHBT3I6KJb5Gk3snkj+jQTFILEZ06cfY7WZxxqPw==} + dependencies: + '@vscode/l10n': 0.0.10 + jsonc-parser: 3.2.0 + vscode-languageserver-textdocument: 1.0.8 + vscode-languageserver-types: 3.17.2 + vscode-uri: 3.0.7 + dev: false + + /vscode-jsonrpc/8.0.2: + resolution: {integrity: sha512-RY7HwI/ydoC1Wwg4gJ3y6LpU9FJRZAUnTYMXthqhFXXu77ErDd/xkREpGuk4MyYkk4a+XDWAMqe0S3KkelYQEQ==} + engines: {node: '>=14.0.0'} + dev: false + + /vscode-languageserver-protocol/3.17.2: + resolution: {integrity: sha512-8kYisQ3z/SQ2kyjlNeQxbkkTNmVFoQCqkmGrzLH6A9ecPlgTbp3wDTnUNqaUxYr4vlAcloxx8zwy7G5WdguYNg==} + dependencies: + vscode-jsonrpc: 8.0.2 + vscode-languageserver-types: 3.17.2 + dev: false + /vscode-languageserver-textdocument/1.0.4: resolution: {integrity: sha512-/xhqXP/2A2RSs+J8JNXpiiNVvvNM0oTosNVmQnunlKvq9o4mupHOBAnnzH0lwIPKazXKvAKsVp1kr+H/K4lgoQ==} dev: false + /vscode-languageserver-textdocument/1.0.8: + resolution: {integrity: sha512-1bonkGqQs5/fxGT5UchTgjGVnfysL0O8v1AYMBjqTbWQTFn721zaPGDYFkOKtfDgFiSgXM3KwaG3FMGfW4Ed9Q==} + dev: false + /vscode-languageserver-types/3.16.0: resolution: {integrity: sha512-k8luDIWJWyenLc5ToFQQMaSrqCHiLwyKPHKPQZ5zz21vM+vIVUSvsRpcbiECH4WR88K2XZqc4ScRcZ7nk/jbeA==} dev: false + /vscode-languageserver-types/3.17.2: + resolution: {integrity: sha512-zHhCWatviizPIq9B7Vh9uvrH6x3sK8itC84HkamnBWoDFJtzBf7SWlpLCZUit72b3os45h6RWQNC9xHRDF8dRA==} + dev: false + + /vscode-languageserver/8.0.2: + resolution: {integrity: sha512-bpEt2ggPxKzsAOZlXmCJ50bV7VrxwCS5BI4+egUmure/oI/t4OlFzi/YNtVvY24A2UDOZAgwFGgnZPwqSJubkA==} + hasBin: true + dependencies: + vscode-languageserver-protocol: 3.17.2 + dev: false + /vscode-manifest/0.0.4: resolution: {integrity: sha512-DqeCLPd+rXIH3LQGICI6ZjW6aiNwQP7Kf1T+OUOa+G0288WizpEf9EYoQs0RSbq+b/LH0MlNmIrdM8Ea3bUsLA==} dependencies: @@ -5345,6 +5470,10 @@ packages: resolution: {integrity: sha512-fmL7V1eiDBFRRnu+gfRWTzyPpNIHJTc4mWnFkwBUmO9U3KPgJAmTx7oxi2bl/Rh6HLdU7+4C9wlj0k2E4AdKFQ==} dev: false + /vscode-uri/3.0.7: + resolution: {integrity: sha512-eOpPHogvorZRobNqJGhapa0JdwaxpjVvyBp0QIUMRMSf8ZAlqOdEquKuRmw9Qwu0qXtJIWqFtMkmvJjUZmMjVA==} + dev: false + /vue-eslint-parser/8.3.0_eslint@8.7.0: resolution: {integrity: sha512-dzHGG3+sYwSf6zFBa0Gi9ZDshD7+ad14DGOdTLjruRVgZXe2J+DcZ9iUhyR48z5g1PqRa20yt3Njna/veLJL/g==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} diff --git a/src/configurationType.ts b/src/configurationType.ts index 337333e1..a87d0557 100644 --- a/src/configurationType.ts +++ b/src/configurationType.ts @@ -1,24 +1,40 @@ -import { ScriptElementKind } from 'typescript/lib/tsserverlibrary' +import { ScriptElementKind, ScriptKind } from 'typescript/lib/tsserverlibrary' type ReplaceRule = { - /** e.g. `readFile`, `^readFile` (global) or `fs.readFile` */ + /** + * Name of completion + * e.g. `readFile`, `^readFile` (global) or `fs.readFile` + */ suggestion: string + /** + * Also its possible to specify any other completion properties. For example: + * - sourceDisplay + */ filter?: { - // package?: string - // TODO - kind?: keyof typeof ScriptElementKind + kind?: keyof Record + fileNamePattern?: string + languageMode?: keyof typeof ScriptKind } - // action + /** by default only first entry is proccessed */ + processMany?: boolean delete?: boolean + /** + * - true - original suggestion will be shown below current + */ duplicateOriginal?: boolean | 'above' patch?: Partial<{ name: string kind: keyof typeof ScriptElementKind /** Might be useless when `correntSorting.enable` is true */ sortText: string - /** Generally not recommended */ - // kindModifiers: string - insertText: string + insertText: string | true + /** Wether insertText differs from completion name */ + snippetLike: boolean + labelDetails: { + /** on the right */ + detail?: string + description?: string + } }> /** Works only with `correntSorting.enable` set to true (default) */ // movePos?: number @@ -36,9 +52,25 @@ type ReplaceRule = { export type Configuration = { /** * Controls wether TypeScript Essentials plugin is enabled or not. + * Does not affect Vue support enablement * @default true */ enablePlugin: boolean + /** + * Wether to enable support in Vue SFC files via Volar config file. + * Changing setting false->true->false requires volar server restart + * Experimental. + * @default false + */ + enableVueSupport: boolean + /** + * @default true + */ + vueSpecificImprovements: boolean + /** + * Temporary setting to enable loading config from other locations (also to expose plugin) + */ + // volarLoadConfigPaths: string[] /** * Removes `Symbol`, `caller`, `prototype` everywhere * @default true diff --git a/src/extension.ts b/src/extension.ts index 3311a1ce..afcff6f2 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -13,6 +13,7 @@ import figIntegration from './figIntegration' import apiCommands from './apiCommands' import onCompletionAccepted from './onCompletionAccepted' import specialCommands from './specialCommands' +import vueVolarSupport from './vueVolarSupport' export const activateTsPlugin = (tsApi: { configurePlugin; onCompletionAccepted }) => { let webWaitingForConfigSync = false @@ -70,6 +71,7 @@ export const activateTsPlugin = (tsApi: { configurePlugin; onCompletionAccepted specialCommands() figIntegration() + vueVolarSupport() } export const activate = async () => { diff --git a/src/vueVolarSupport.ts b/src/vueVolarSupport.ts new file mode 100644 index 00000000..294c0cbc --- /dev/null +++ b/src/vueVolarSupport.ts @@ -0,0 +1,26 @@ +import * as vscode from 'vscode' +import { watchExtensionSettings } from '@zardoy/vscode-utils/build/settings' +import { extensionCtx, getExtensionSetting } from 'vscode-framework' + +export default () => { + const handler = () => { + const config = vscode.workspace.getConfiguration('') + if ( + !getExtensionSetting('enableVueSupport') || + !vscode.extensions.getExtension('Vue.volar') || + isConfigValueChanged('volar.vueserver.configFilePath') + ) { + return + } + + void config.update('volar.vueserver.configFilePath', extensionCtx.asAbsolutePath('./volarConfig.js'), vscode.ConfigurationTarget.Global) + } + + handler() + watchExtensionSettings(['enableVueSupport'], handler) +} + +const isConfigValueChanged = (id: string) => { + const config = vscode.workspace.getConfiguration('') + return config.get(id) !== config.inspect(id)!.defaultValue +} diff --git a/typescript/src/completions/isGoodPositionMethodCompletion.ts b/typescript/src/completions/isGoodPositionMethodCompletion.ts index 7a3f8bb4..9f5a91bb 100644 --- a/typescript/src/completions/isGoodPositionMethodCompletion.ts +++ b/typescript/src/completions/isGoodPositionMethodCompletion.ts @@ -11,7 +11,9 @@ export const isGoodPositionBuiltinMethodCompletion = (ts: typeof tslib, sourceFi // const obj = { method() {}, arrow: () => {} } // type A = typeof obj["|"] if (ts.isStringLiteralLike(currentNode)) return false + if (ts.isNamedExports(currentNode)) return false if (ts.isIdentifier(currentNode)) currentNode = currentNode.parent + if (ts.isExportSpecifier(currentNode)) return false if (ts.isJsxSelfClosingElement(currentNode) || ts.isJsxOpeningElement(currentNode)) return false if (ts.isBindingElement(currentNode) || ts.isShorthandPropertyAssignment(currentNode)) currentNode = currentNode.parent if (ts.isObjectBindingPattern(currentNode) || ts.isObjectLiteralExpression(currentNode)) return false diff --git a/typescript/src/completions/objectLiteralCompletions.ts b/typescript/src/completions/objectLiteralCompletions.ts index 6b13d97f..ca52f7db 100644 --- a/typescript/src/completions/objectLiteralCompletions.ts +++ b/typescript/src/completions/objectLiteralCompletions.ts @@ -99,14 +99,15 @@ const isArrayCompletion = (type: ts.Type, checker: ts.TypeChecker) => { return false } -const isObjectCompletion = (type: ts.Type) => { +const isObjectCompletion = (type: ts.Type, checker: ts.TypeChecker) => { if (type.flags & ts.TypeFlags.Undefined) return true + if (checker['isArrayLikeType'](type)) return false if (type.flags & ts.TypeFlags.Object) { if ((type as ts.ObjectType).objectFlags & ts.ObjectFlags.Class) return false // complete with regexp? if (type.symbol?.escapedName === 'RegExp') return false return true } - if (type.isUnion()) return type.types.every(type => isObjectCompletion(type)) + if (type.isUnion()) return type.types.every(type => isObjectCompletion(type, checker)) return false } diff --git a/typescript/src/completionsAtPosition.ts b/typescript/src/completionsAtPosition.ts index 16935e74..192f85d4 100644 --- a/typescript/src/completionsAtPosition.ts +++ b/typescript/src/completionsAtPosition.ts @@ -22,6 +22,7 @@ import filterJsxElements from './completions/filterJsxComponents' import markOrRemoveGlobalCompletions from './completions/markOrRemoveGlobalLibCompletions' import { oneOf } from '@zardoy/utils' import filterWIthIgnoreAutoImports from './completions/ignoreAutoImports' +import escapeStringRegexp from 'escape-string-regexp' export type PrevCompletionMap = Record @@ -32,7 +33,8 @@ export const getCompletionsAtPosition = ( c: GetConfig, languageService: ts.LanguageService, scriptSnapshot: ts.IScriptSnapshot, - formatOptions?: ts.FormatCodeSettings, + formatOptions: ts.FormatCodeSettings | undefined, + additionalData: { scriptKind: ts.ScriptKind }, ): | { completions: ts.CompletionInfo @@ -199,24 +201,84 @@ export const getCompletionsAtPosition = ( prior.entries = arrayMethods(prior.entries, position, sourceFile, c) ?? prior.entries prior.entries = jsdocDefault(prior.entries, position, sourceFile, languageService) ?? prior.entries + if ((fileName.endsWith('.vue.ts') || fileName.endsWith('.vue.js')) && c('vueSpecificImprovements') && exactNode) { + let node = ts.isIdentifier(exactNode) ? exactNode.parent : exactNode + if (ts.isPropertyAssignment(node)) node = node.parent + if ( + ts.isObjectLiteralExpression(node) && + ts.isCallExpression(node.parent) && + ts.isIdentifier(node.parent.expression) && + node.parent.expression.text === 'defineComponent' + ) { + prior.entries = prior.entries.filter(({ name, kind }) => kind === ts.ScriptElementKind.warning || !name.startsWith('__')) + } + } + if (c('improveJsxCompletions') && leftNode) prior.entries = improveJsxCompletions(prior.entries, leftNode, position, sourceFile, c('jsxCompletionsMap')) + const processedEntries = new Set() for (const rule of c('replaceSuggestions')) { - let foundIndex!: number - const suggestion = prior.entries.find(({ name, kind }, index) => { - if (rule.suggestion !== name) return false - if (rule.filter?.kind && kind !== rule.filter.kind) return false - foundIndex = index - return true - }) - if (!suggestion) continue + if (rule.filter?.fileNamePattern) { + // todo replace with something better + const fileRegex = tsFull.getRegexFromPattern(tsFull.getPatternFromSpec(rule.filter.fileNamePattern, program.getCurrentDirectory(), 'files')!, false) + if (fileRegex && !fileRegex.test(fileName)) continue + } + if (rule.filter?.languageMode && ts.ScriptKind[rule.filter.languageMode] !== additionalData.scriptKind) continue + let nameComparator: (n: string) => boolean + if (rule.suggestion.includes('*')) { + const regex = new RegExp(`^${escapeStringRegexp(rule.suggestion).replaceAll('\\*', '.*')}$`) + nameComparator = n => regex.test(n) + } else { + nameComparator = n => n === rule.suggestion + } - if (rule.delete) prior.entries.splice(foundIndex, 1) + const entryIndexesToRemove: number[] = [] + const processEntryWithRule = (entryIndex: number) => { + if (rule.delete) { + entryIndexesToRemove.push(entryIndex) + return + } - if (rule.duplicateOriginal) prior.entries.splice(rule.duplicateOriginal === 'above' ? foundIndex : foundIndex + 1, 0, { ...suggestion }) + // todo-low (perf debt) clone probably should be used this + const entry = prior!.entries[entryIndex]! + if (rule.duplicateOriginal) { + const duplicateEntry = { ...entry } + prior!.entries.splice(rule.duplicateOriginal === 'above' ? entryIndex : entryIndex + 1, 0, duplicateEntry) + processedEntries.add(duplicateEntry) + } - Object.assign(suggestion, rule.patch ?? {}) - if (rule.patch?.insertText) suggestion.isSnippet = true + const { patch } = rule + if (patch) { + const { labelDetails, ...justPatch } = patch + if (labelDetails) { + entry.labelDetails ??= {} + Object.assign(entry.labelDetails, labelDetails) + } + Object.assign(entry, justPatch) + } + if (patch?.insertText === true) { + entry.insertText = entry.name + } + if (rule.patch?.insertText) entry.isSnippet = true + processedEntries.add(entry) + } + + entry: for (const [i, entry] of prior!.entries.entries()) { + if (processedEntries.has(entry)) continue + const { name } = entry + if (!nameComparator(name)) continue + const { fileNamePattern, languageMode, ...simpleEntryFilters } = rule.filter ?? {} + for (const [filterKey, filterValue] of Object.entries(simpleEntryFilters)) { + if (entry[filterKey] !== filterValue) continue entry + } + processEntryWithRule(i) + if (!rule.processMany) break + } + + let iStep = 0 + for (const i of entryIndexesToRemove) { + prior.entries.splice(i - iStep++, 1) + } } // prevent vscode-builtin wrong insertText with methods snippets enabled diff --git a/typescript/src/decorateProxy.ts b/typescript/src/decorateProxy.ts new file mode 100644 index 00000000..6a90e05e --- /dev/null +++ b/typescript/src/decorateProxy.ts @@ -0,0 +1,119 @@ +import { getCompletionsAtPosition, PrevCompletionMap } from './completionsAtPosition' +import { TriggerCharacterCommand } from './ipcTypes' +import { getNavTreeItems } from './getPatchedNavTree' +import decorateCodeActions from './codeActions/decorateProxy' +import decorateSemanticDiagnostics from './semanticDiagnostics' +import decorateCodeFixes from './codeFixes' +import decorateReferences from './references' +import handleSpecialCommand from './specialCommands/handle' +import decorateDefinitions from './definitions' +import decorateDocumentHighlights from './documentHighlights' +import completionEntryDetails from './completionEntryDetails' +import { GetConfig } from './types' +import lodashGet from 'lodash.get' + +/** @internal */ +export const thisPluginMarker = '__essentialPluginsMarker__' + +export const getInitialProxy = (languageService: ts.LanguageService, proxy = Object.create(null)): ts.LanguageService => { + for (const k of Object.keys(languageService)) { + const x = languageService[k]! + // @ts-expect-error - JS runtime trickery which is tricky to type tersely + proxy[k] = (...args: Array>) => x.apply(languageService, args) + } + return proxy +} + +export const decorateLanguageService = ( + info: ts.server.PluginCreateInfo, + existingProxy: ts.LanguageService | undefined, + config: { config: any }, + { pluginSpecificSyntaxServerConfigCheck = true }: { pluginSpecificSyntaxServerConfigCheck?: boolean } = {}, +) => { + const c: GetConfig = key => lodashGet(config.config, key) + const { languageService, languageServiceHost } = info + + // Set up decorator object + const proxy = getInitialProxy(languageService, existingProxy) + + let prevCompletionsMap: PrevCompletionMap + // eslint-disable-next-line complexity + proxy.getCompletionsAtPosition = (fileName, position, options, formatOptions) => { + const updateConfigCommand = 'updateConfig' + if (options?.triggerCharacter?.startsWith(updateConfigCommand)) { + config.config = JSON.parse(options.triggerCharacter.slice(updateConfigCommand.length)) + return { entries: [] } + } + const specialCommandResult = options?.triggerCharacter + ? handleSpecialCommand( + info, + fileName, + position, + options.triggerCharacter as TriggerCharacterCommand, + languageService, + config.config, + options, + formatOptions, + ) + : undefined + // handled specialCommand request + if (specialCommandResult !== undefined) return specialCommandResult as any + prevCompletionsMap = {} + const scriptSnapshot = languageServiceHost.getScriptSnapshot(fileName) + const scriptKind = languageServiceHost.getScriptKind!(fileName) + // have no idea in which cases its possible, but we can't work without it + if (!scriptSnapshot) return + const result = getCompletionsAtPosition(fileName, position, options, c, languageService, scriptSnapshot, formatOptions, { scriptKind }) + if (!result) return + prevCompletionsMap = result.prevCompletionsMap + return result.completions + } + + proxy.getCompletionEntryDetails = (fileName, position, entryName, formatOptions, source, preferences, data) => { + const program = languageService.getProgram() + const sourceFile = program?.getSourceFile(fileName) + if (!program || !sourceFile) return + const { documentationOverride } = prevCompletionsMap[entryName] ?? {} + if (documentationOverride) { + return { + name: entryName, + kind: ts.ScriptElementKind.alias, + kindModifiers: '', + displayParts: typeof documentationOverride === 'string' ? [{ kind: 'text', text: documentationOverride }] : documentationOverride, + } + } + const prior = languageService.getCompletionEntryDetails( + fileName, + position, + prevCompletionsMap[entryName]?.originalName || entryName, + formatOptions, + source, + preferences, + data, + ) + if (!prior) return + return completionEntryDetails(languageService, c, fileName, position, sourceFile, prior) + } + + decorateCodeActions(proxy, languageService, c) + decorateCodeFixes(proxy, languageService, c, languageServiceHost) + decorateSemanticDiagnostics(proxy, info, c) + decorateDefinitions(proxy, info, c) + decorateReferences(proxy, languageService, c) + decorateDocumentHighlights(proxy, languageService, c) + + if (pluginSpecificSyntaxServerConfigCheck) { + if (!__WEB__) { + // dedicated syntax server (which is enabled by default), which fires navtree doesn't seem to receive onConfigurationChanged + // so we forced to communicate via fs + const config = JSON.parse(ts.sys.readFile(require('path').join(__dirname, '../../plugin-config.json'), 'utf8') ?? '{}') + proxy.getNavigationTree = fileName => { + if (c('patchOutline') || config.patchOutline) return getNavTreeItems(info, fileName) + return languageService.getNavigationTree(fileName) + } + } + } + + languageService[thisPluginMarker] = true + return proxy +} diff --git a/typescript/src/index.ts b/typescript/src/index.ts index 6dd44ff5..add035dc 100644 --- a/typescript/src/index.ts +++ b/typescript/src/index.ts @@ -1,119 +1,10 @@ -import get from 'lodash.get' - // eslint-disable-next-line @typescript-eslint/ban-ts-comment //@ts-ignore import type { Configuration } from '../../src/configurationType' -import _ from 'lodash' -import { GetConfig } from './types' -import { getCompletionsAtPosition, PrevCompletionMap } from './completionsAtPosition' -import { TriggerCharacterCommand } from './ipcTypes' -import { getNavTreeItems } from './getPatchedNavTree' -import decorateCodeActions from './codeActions/decorateProxy' -import decorateSemanticDiagnostics from './semanticDiagnostics' -import decorateCodeFixes from './codeFixes' -import decorateReferences from './references' -import handleSpecialCommand from './specialCommands/handle' -import decorateDefinitions from './definitions' -import decorateDocumentHighlights from './documentHighlights' -import completionEntryDetails from './completionEntryDetails' - -const thisPluginMarker = '__essentialPluginsMarker__' - -let _configuration: Configuration -const c: GetConfig = key => get(_configuration, key) - -const getInitialProxy = (languageService: ts.LanguageService, proxy = Object.create(null)): ts.LanguageService => { - for (const k of Object.keys(languageService)) { - const x = languageService[k]! - // @ts-expect-error - JS runtime trickery which is tricky to type tersely - proxy[k] = (...args: Array>) => x.apply(languageService, args) - } - return proxy -} - -const decorateLanguageService = (info: ts.server.PluginCreateInfo, existingProxy?: ts.LanguageService) => { - // Set up decorator object - const proxy = getInitialProxy(info.languageService, existingProxy) - - const { languageService } = info - - let prevCompletionsMap: PrevCompletionMap - // eslint-disable-next-line complexity - proxy.getCompletionsAtPosition = (fileName, position, options, formatOptions) => { - const updateConfigCommand = 'updateConfig' - if (options?.triggerCharacter?.startsWith(updateConfigCommand)) { - _configuration = JSON.parse(options.triggerCharacter.slice(updateConfigCommand.length)) - return { entries: [] } - } - const specialCommandResult = options?.triggerCharacter - ? handleSpecialCommand( - info, - fileName, - position, - options.triggerCharacter as TriggerCharacterCommand, - languageService, - _configuration, - options, - formatOptions, - ) - : undefined - // handled specialCommand request - if (specialCommandResult !== undefined) return specialCommandResult as any - prevCompletionsMap = {} - const scriptSnapshot = info.project.getScriptSnapshot(fileName) - // have no idea in which cases its possible, but we can't work without it - if (!scriptSnapshot) return - const result = getCompletionsAtPosition(fileName, position, options, c, info.languageService, scriptSnapshot, formatOptions) - if (!result) return - prevCompletionsMap = result.prevCompletionsMap - return result.completions - } - - proxy.getCompletionEntryDetails = (fileName, position, entryName, formatOptions, source, preferences, data) => { - const program = languageService.getProgram() - const sourceFile = program?.getSourceFile(fileName) - if (!program || !sourceFile) return - const { documentationOverride } = prevCompletionsMap[entryName] ?? {} - if (documentationOverride) { - return { - name: entryName, - kind: ts.ScriptElementKind.alias, - kindModifiers: '', - displayParts: typeof documentationOverride === 'string' ? [{ kind: 'text', text: documentationOverride }] : documentationOverride, - } - } - const prior = languageService.getCompletionEntryDetails( - fileName, - position, - prevCompletionsMap[entryName]?.originalName || entryName, - formatOptions, - source, - preferences, - data, - ) - if (!prior) return - return completionEntryDetails(languageService, c, fileName, position, sourceFile, prior) - } - - decorateCodeActions(proxy, info.languageService, c) - decorateCodeFixes(proxy, info.languageService, c, info.languageServiceHost) - decorateSemanticDiagnostics(proxy, info, c) - decorateDefinitions(proxy, info, c) - decorateReferences(proxy, info.languageService, c) - decorateDocumentHighlights(proxy, info.languageService, c) - - if (!__WEB__) { - // dedicated syntax server (which is enabled by default), which fires navtree doesn't seem to receive onConfigurationChanged - // so we forced to communicate via fs - const config = JSON.parse(ts.sys.readFile(require('path').join(__dirname, '../../plugin-config.json'), 'utf8') ?? '{}') - proxy.getNavigationTree = fileName => { - if (c('patchOutline') || config.patchOutline) return getNavTreeItems(info, fileName) - return info.languageService.getNavigationTree(fileName) - } - } +import { decorateLanguageService, getInitialProxy, thisPluginMarker } from './decorateProxy' - info.languageService[thisPluginMarker] = true - return proxy +let _configObj = { + config: undefined! as Configuration, } const updateConfigListeners: Array<() => void> = [] @@ -123,25 +14,28 @@ const plugin: ts.server.PluginModuleFactory = ({ typescript }) => { return { create(info) { // receive fresh config - _configuration = info.config - console.log('receive config', JSON.stringify(_configuration)) + _configObj.config = info.config + console.log('receive config', JSON.stringify(_configObj.config)) if (info.languageService[thisPluginMarker]) return info.languageService - const proxy = _configuration.enablePlugin === false ? getInitialProxy(info.languageService) : decorateLanguageService(info, undefined) + const proxy = + _configObj.config.enablePlugin === false + ? getInitialProxy(info.languageService) + : decorateLanguageService(info, undefined, _configObj, _configObj.config?.['_additionalPluginOptions']) // #region watch enablePlugin setting - let prevPluginEnabledSetting = _configuration.enablePlugin + let prevPluginEnabledSetting = _configObj.config.enablePlugin updateConfigListeners.push(() => { - if ((prevPluginEnabledSetting === true || prevPluginEnabledSetting === undefined) && !_configuration.enablePlugin) { + if ((prevPluginEnabledSetting === true || prevPluginEnabledSetting === undefined) && !_configObj.config.enablePlugin) { // plugin got disabled, restore original languageService methods // todo resetting doesn't work after tsconfig changes getInitialProxy(info.languageService, proxy) - } else if (prevPluginEnabledSetting === false && _configuration.enablePlugin) { + } else if (prevPluginEnabledSetting === false && _configObj.config.enablePlugin) { // plugin got enabled - decorateLanguageService(info, proxy) + decorateLanguageService(info, proxy, _configObj, _configObj.config?.['_additionalPluginOptions']) } - prevPluginEnabledSetting = _configuration.enablePlugin + prevPluginEnabledSetting = _configObj.config.enablePlugin }) // #endregion @@ -149,7 +43,7 @@ const plugin: ts.server.PluginModuleFactory = ({ typescript }) => { }, onConfigurationChanged(config) { console.log('update config', JSON.stringify(config)) - _configuration = config + _configObj.config = config for (const updateConfigListener of updateConfigListeners) { updateConfigListener() } diff --git a/typescript/src/libMethods.ts b/typescript/src/libMethods.ts new file mode 100644 index 00000000..291dbcdd --- /dev/null +++ b/typescript/src/libMethods.ts @@ -0,0 +1,5 @@ +// exposed ts essentials plugins lib methods for external usage from npm + +export const initTypeScriptEssentialsPlugins = (typescript: typeof ts) => { + ts = tsFull = typescript as any +} diff --git a/typescript/src/semanticDiagnostics.ts b/typescript/src/semanticDiagnostics.ts index f2a9e9be..6917beab 100644 --- a/typescript/src/semanticDiagnostics.ts +++ b/typescript/src/semanticDiagnostics.ts @@ -5,7 +5,7 @@ export default (proxy: ts.LanguageService, info: ts.server.PluginCreateInfo, c: proxy.getSemanticDiagnostics = fileName => { let prior = info.languageService.getSemanticDiagnostics(fileName) if (c('supportTsDiagnosticDisableComment')) { - const scriptSnapshot = info.project.getScriptSnapshot(fileName)! + const scriptSnapshot = info.languageServiceHost.getScriptSnapshot(fileName)! const firstLine = scriptSnapshot.getText(0, scriptSnapshot.getLength()).split(/\r?\n/)[0]! if (firstLine.startsWith('//')) { const match = firstLine.match(/@ts-diagnostic-disable ((\d+, )*(\d+))/) diff --git a/typescript/src/specialCommands/handle.ts b/typescript/src/specialCommands/handle.ts index 6310dbd1..2899a5fe 100644 --- a/typescript/src/specialCommands/handle.ts +++ b/typescript/src/specialCommands/handle.ts @@ -66,7 +66,7 @@ export default ( } } if (specialCommand === 'getPostfixes') { - const scriptSnapshot = info.project.getScriptSnapshot(fileName) + const scriptSnapshot = info.languageServiceHost.getScriptSnapshot(fileName) if (!scriptSnapshot) return return { entries: [], diff --git a/typescript/src/volarConfig.ts b/typescript/src/volarConfig.ts new file mode 100644 index 00000000..7b979f76 --- /dev/null +++ b/typescript/src/volarConfig.ts @@ -0,0 +1,68 @@ +// will be required from ./node_modules/typescript-essential-plugins/index.js +const originalPluginFactory = require('typescript-essential-plugins') + +const plugin = (context => { + const typescript = context.typescript + const { configurationHost } = context.env + const patchConfig = config => { + return { + ...config, + _additionalPluginOptions: { + pluginSpecificSyntaxServerConfigCheck: false, + }, + enablePlugin: config.enableVueSupport, + } + } + + if (typescript && configurationHost) { + const plugin = originalPluginFactory({ + typescript: typescript.module, + }) + // todo support vue-specific settings + const originalLsMethods = { ...typescript.languageService } + + configurationHost.getConfiguration('tsEssentialPlugins').then(_configuration => { + // if (typescript.languageService[thisPluginMarker]) return + const config = patchConfig(_configuration) + if (!config.enablePlugin) return + const proxy = plugin.create({ + ...typescript, + config: config, + languageService: originalLsMethods as any, + } as any) + console.log('TS Essentials Plugins activated!') + // const methodToReassign = ['getCompletionsAtPosition', 'getCompletionEntryDetails'] + for (const method of Object.keys(proxy)) { + typescript.languageService[method] = proxy[method] as any + } + }) + + configurationHost.onDidChangeConfiguration(() => { + configurationHost.getConfiguration('tsEssentialPlugins').then(config => { + config = patchConfig(config) + plugin.onConfigurationChanged?.(config) + // temporary workaround + if (!config.enablePlugin) { + typescript.languageService = originalLsMethods + } + }) + }) + // typescript.languageService[thisPluginMarker] = true + } else { + console.warn('Failed to activate tsEssentialPlugins, because of no typescript or configurationHost context') + } + return {} +}) satisfies import('@volar/language-service').LanguageServicePlugin + +module.exports = { + plugins: [ + c => { + try { + return plugin(c) + } catch (err) { + console.log('TS Essentials error', err) + return {} + } + }, + ], +} satisfies import('@volar/language-server/out/common/utils/serverConfig').ServerConfig diff --git a/typescript/test/completions.spec.ts b/typescript/test/completions.spec.ts index 53526b80..b1c0330c 100644 --- a/typescript/test/completions.spec.ts +++ b/typescript/test/completions.spec.ts @@ -76,7 +76,16 @@ const defaultConfigFunc = await getDefaultConfigFunc(settingsOverride) const getCompletionsAtPosition = (pos: number, { fileName = entrypoint, shouldHave }: { fileName?: string; shouldHave?: boolean } = {}) => { if (pos === undefined) throw new Error('getCompletionsAtPosition: pos is undefined') - const result = getCompletionsAtPositionRaw(fileName, pos, {}, defaultConfigFunc, languageService, ts.ScriptSnapshot.fromString(files[entrypoint])) + const result = getCompletionsAtPositionRaw( + fileName, + pos, + {}, + defaultConfigFunc, + languageService, + ts.ScriptSnapshot.fromString(files[entrypoint]), + undefined, + { scriptKind: ts.ScriptKind.TSX }, + ) if (shouldHave) expect(result).not.toBeUndefined() if (!result) return return { @@ -108,6 +117,7 @@ test('Banned positions for all method snippets', () => { import {/*|*/} from 'test' const obj = { m$1e$2thod() {}, arrow: () => {} } type A = typeof obj["/*|*/"]; + export {/*|*/} from 'test' a(({ a/*|*/ }) => {}) const test = () => ({ method() {} }) const {/*|*/} = test()