diff --git a/.vscode/launch.json b/.vscode/launch.json index c7691e1..02afd39 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -9,7 +9,7 @@ "runtimeExecutable": "${execPath}", "args": [ "--extensionDevelopmentPath=${workspaceRoot}", - "${workspaceFolder}/sample" + "${workspaceFolder}/sample/project" ], "preLaunchTask": { "type": "npm", @@ -33,7 +33,9 @@ "--extensionTestsPath=${workspaceRoot}/dist/client/out/test/index", "${workspaceRoot}/test/fixtures" ], - "outFiles": ["${workspaceRoot}/dist/client/out/test/**/*.js"] + "outFiles": [ + "${workspaceRoot}/dist/client/out/test/**/*.js" + ] }, { "name": "Run Web Extension in VS Code", @@ -84,16 +86,21 @@ "request": "launch", "runtimeExecutable": "${execPath}", "args": [ - "--extensionDevelopmentPath=${workspaceFolder}", - "--extensionTestsPath=${workspaceFolder}/dist/client/out/test/index" + "--extensionDevelopmentPath=${workspaceFolder}", + "--extensionTestsPath=${workspaceFolder}/dist/client/out/test/index" ], - "outFiles": ["${workspaceFolder}/dist/client/out/test/**/*.test.js"] + "outFiles": [ + "${workspaceFolder}/dist/client/out/test/**/*.test.js" + ] } ], "compounds": [ { "name": "Client + Server", - "configurations": ["Launch Client", "Attach to Server"] + "configurations": [ + "Launch Client", + "Attach to Server" + ] } ] -} +} \ No newline at end of file diff --git a/README.md b/README.md index 06efa67..535b248 100644 --- a/README.md +++ b/README.md @@ -21,6 +21,23 @@ Provides Visual Basic for Applications (VBA) language support in Visual Studio C 1Currently full document `Shift+Alt+F` formatting only. +### *PREVIEW* Definition Provider + +This extension now supports definitions, i.e., following a name to a declaration. Pressing F12 will jump you to the relevant method or variable declaration. + +This is a preview feature and may not work 100% as expected. If bugs are encountered, please take the time to raise an issue on the repo. + +### *PREVIEW* Code Refactoring + +This extension now supports Rename requests, i.e., using F2 to rename an element. It will find the all instances of the name throughout the workspace and rename it. + +### Preview Limitations + +Known limitations: + - Method attributes (yet) aren't renamed when you rename a function or sub. + - Public methods still producing diagnostics when there's a similarly named method in another module. + - Can't yet rename classes with this functionality. + ### Web Support The VBA Pro Extension offers limited support for web environments (e.g. vscode-dev). @@ -75,6 +92,7 @@ End Property ## Coming Soon * Hovers +* Completion ## Installation diff --git a/client/package-lock.json b/client/package-lock.json index 6691ff1..9d8b06d 100644 --- a/client/package-lock.json +++ b/client/package-lock.json @@ -12,31 +12,31 @@ "vscode-languageclient": "^9.0.1" }, "devDependencies": { - "@types/vscode": "^1.99.0", - "@vscode/test-electron": "^2.4.1" + "@types/vscode": "^1.100.0", + "@vscode/test-electron": "^2.5.2" }, "engines": { "vscode": "^1.63.0" } }, "node_modules/@types/vscode": { - "version": "1.99.0", - "resolved": "https://registry.npmjs.org/@types/vscode/-/vscode-1.99.0.tgz", - "integrity": "sha512-30sjmas1hQ0gVbX68LAWlm/YYlEqUErunPJJKLpEl+xhK0mKn+jyzlCOpsdTwfkZfPy4U6CDkmygBLC3AB8W9Q==", + "version": "1.100.0", + "resolved": "https://registry.npmjs.org/@types/vscode/-/vscode-1.100.0.tgz", + "integrity": "sha512-4uNyvzHoraXEeCamR3+fzcBlh7Afs4Ifjs4epINyUX/jvdk0uzLnwiDY35UKDKnkCHP5Nu3dljl2H8lR6s+rQw==", "dev": true, "license": "MIT" }, "node_modules/@vscode/test-electron": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/@vscode/test-electron/-/test-electron-2.4.1.tgz", - "integrity": "sha512-Gc6EdaLANdktQ1t+zozoBVRynfIsMKMc94Svu1QreOBC8y76x4tvaK32TljrLi1LI2+PK58sDVbL7ALdqf3VRQ==", + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/@vscode/test-electron/-/test-electron-2.5.2.tgz", + "integrity": "sha512-8ukpxv4wYe0iWMRQU18jhzJOHkeGKbnw7xWRX3Zw1WJA4cEKbHcmmLPdPrPtL6rhDcrlCZN+xKRpv09n4gRHYg==", "dev": true, "license": "MIT", "dependencies": { "http-proxy-agent": "^7.0.2", "https-proxy-agent": "^7.0.5", "jszip": "^3.10.1", - "ora": "^7.0.1", + "ora": "^8.1.0", "semver": "^7.6.2" }, "engines": { @@ -72,54 +72,6 @@ "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", "license": "MIT" }, - "node_modules/base64-js": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", - "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT" - }, - "node_modules/bl": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/bl/-/bl-5.1.0.tgz", - "integrity": "sha512-tv1ZJHLfTDnXE6tMHv73YgSJaWR2AFuPwMntBe7XL/GBFHnT0CLnsHMogfk5+GzCDC5ZWarSCYaIGATZt9dNsQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "buffer": "^6.0.3", - "inherits": "^2.0.4", - "readable-stream": "^3.4.0" - } - }, - "node_modules/bl/node_modules/readable-stream": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", - "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", - "dev": true, - "license": "MIT", - "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - }, - "engines": { - "node": ">= 6" - } - }, "node_modules/brace-expansion": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", @@ -129,31 +81,6 @@ "balanced-match": "^1.0.0" } }, - "node_modules/buffer": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", - "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT", - "dependencies": { - "base64-js": "^1.3.1", - "ieee754": "^1.2.1" - } - }, "node_modules/chalk": { "version": "5.4.1", "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.4.1.tgz", @@ -168,16 +95,16 @@ } }, "node_modules/cli-cursor": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-4.0.0.tgz", - "integrity": "sha512-VGtlMu3x/4DOtIUwEkRezxUZ2lBacNJCHash0N0WeZDBS+7Ux1dm3XWAgWYxLJFMMdOeXMHXorshEFhbMSGelg==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-5.0.0.tgz", + "integrity": "sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw==", "dev": true, "license": "MIT", "dependencies": { - "restore-cursor": "^4.0.0" + "restore-cursor": "^5.0.0" }, "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + "node": ">=18" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" @@ -204,9 +131,9 @@ "license": "MIT" }, "node_modules/debug": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", - "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", + "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", "dev": true, "license": "MIT", "dependencies": { @@ -221,13 +148,6 @@ } } }, - "node_modules/eastasianwidth": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", - "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", - "dev": true, - "license": "MIT" - }, "node_modules/emoji-regex": { "version": "10.4.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.4.0.tgz", @@ -235,6 +155,19 @@ "dev": true, "license": "MIT" }, + "node_modules/get-east-asian-width": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.3.0.tgz", + "integrity": "sha512-vpeMIQKxczTD/0s2CdEWHcb0eeJe6TFjxb+J5xgX7hScxqrGuyjmv4c1D4A/gelKfyox0gJJwIHF+fLjeaM8kQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/http-proxy-agent": { "version": "7.0.2", "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", @@ -263,27 +196,6 @@ "node": ">= 14" } }, - "node_modules/ieee754": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", - "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "BSD-3-Clause" - }, "node_modules/immediate": { "version": "3.0.6", "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz", @@ -312,13 +224,13 @@ } }, "node_modules/is-unicode-supported": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-1.3.0.tgz", - "integrity": "sha512-43r2mRvz+8JRIKnWJ+3j8JtjRKZ6GmjzfaE/qiBJnikNnYv/6bagRJ1kUhNk8R5EX/GkobD+r+sfxCPJsiKBLQ==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-2.1.0.tgz", + "integrity": "sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ==", "dev": true, "license": "MIT", "engines": { - "node": ">=12" + "node": ">=18" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" @@ -355,15 +267,28 @@ } }, "node_modules/log-symbols": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-5.1.0.tgz", - "integrity": "sha512-l0x2DvrW294C9uDCoQe1VSU4gf529FkSZ6leBl4TiqZH/e+0R7hSfHQBNut2mNygDgHwvYHfFLn6Oxb3VWj2rA==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-6.0.0.tgz", + "integrity": "sha512-i24m8rpwhmPIS4zscNzK6MSEhk0DUWa/8iYQWxhffV8jkI4Phvs3F+quL5xvS0gdQR0FyTCMMH33Y78dDTzzIw==", "dev": true, "license": "MIT", "dependencies": { - "chalk": "^5.0.0", - "is-unicode-supported": "^1.1.0" + "chalk": "^5.3.0", + "is-unicode-supported": "^1.3.0" }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/log-symbols/node_modules/is-unicode-supported": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-1.3.0.tgz", + "integrity": "sha512-43r2mRvz+8JRIKnWJ+3j8JtjRKZ6GmjzfaE/qiBJnikNnYv/6bagRJ1kUhNk8R5EX/GkobD+r+sfxCPJsiKBLQ==", + "dev": true, + "license": "MIT", "engines": { "node": ">=12" }, @@ -371,14 +296,17 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/mimic-fn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", - "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "node_modules/mimic-function": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/mimic-function/-/mimic-function-5.0.1.tgz", + "integrity": "sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==", "dev": true, "license": "MIT", "engines": { - "node": ">=6" + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/minimatch": { @@ -401,40 +329,40 @@ "license": "MIT" }, "node_modules/onetime": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", - "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-7.0.0.tgz", + "integrity": "sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==", "dev": true, "license": "MIT", "dependencies": { - "mimic-fn": "^2.1.0" + "mimic-function": "^5.0.0" }, "engines": { - "node": ">=6" + "node": ">=18" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/ora": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/ora/-/ora-7.0.1.tgz", - "integrity": "sha512-0TUxTiFJWv+JnjWm4o9yvuskpEJLXTcng8MJuKd+SzAzp2o+OP3HWqNhB4OdJRt1Vsd9/mR0oyaEYlOnL7XIRw==", + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/ora/-/ora-8.2.0.tgz", + "integrity": "sha512-weP+BZ8MVNnlCm8c0Qdc1WSWq4Qn7I+9CJGm7Qali6g44e/PUzbjNqJX5NJ9ljlNMosfJvg1fKEGILklK9cwnw==", "dev": true, "license": "MIT", "dependencies": { "chalk": "^5.3.0", - "cli-cursor": "^4.0.0", - "cli-spinners": "^2.9.0", + "cli-cursor": "^5.0.0", + "cli-spinners": "^2.9.2", "is-interactive": "^2.0.0", - "is-unicode-supported": "^1.3.0", - "log-symbols": "^5.1.0", - "stdin-discarder": "^0.1.0", - "string-width": "^6.1.0", + "is-unicode-supported": "^2.0.0", + "log-symbols": "^6.0.0", + "stdin-discarder": "^0.2.2", + "string-width": "^7.2.0", "strip-ansi": "^7.1.0" }, "engines": { - "node": ">=16" + "node": ">=18" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" @@ -471,17 +399,17 @@ } }, "node_modules/restore-cursor": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-4.0.0.tgz", - "integrity": "sha512-I9fPXU9geO9bHOt9pHHOhOkYerIMsmVaWB0rA2AI9ERh/+x/i7MV5HKBNrg+ljO5eoPVgCcnFuRjJ9uH6I/3eg==", + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-5.1.0.tgz", + "integrity": "sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==", "dev": true, "license": "MIT", "dependencies": { - "onetime": "^5.1.0", - "signal-exit": "^3.0.2" + "onetime": "^7.0.0", + "signal-exit": "^4.1.0" }, "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + "node": ">=18" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" @@ -495,9 +423,9 @@ "license": "MIT" }, "node_modules/semver": { - "version": "7.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz", - "integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==", + "version": "7.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", + "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", "license": "ISC", "bin": { "semver": "bin/semver.js" @@ -514,23 +442,26 @@ "license": "MIT" }, "node_modules/signal-exit": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", - "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", "dev": true, - "license": "ISC" + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } }, "node_modules/stdin-discarder": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/stdin-discarder/-/stdin-discarder-0.1.0.tgz", - "integrity": "sha512-xhV7w8S+bUwlPTb4bAOUQhv8/cSS5offJuX8GQGq32ONF0ZtDWKfkdomM3HMRA+LhX6um/FZ0COqlwsjD53LeQ==", + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/stdin-discarder/-/stdin-discarder-0.2.2.tgz", + "integrity": "sha512-UhDfHmA92YAlNnCfhmq0VeNL5bDbiZGg7sZ2IvPsXubGkiNa9EC+tUTsjBRsYUAz87btI6/1wf4XoVvQ3uRnmQ==", "dev": true, "license": "MIT", - "dependencies": { - "bl": "^5.0.0" - }, "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + "node": ">=18" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" @@ -547,18 +478,18 @@ } }, "node_modules/string-width": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-6.1.0.tgz", - "integrity": "sha512-k01swCJAgQmuADB0YIc+7TuatfNvTBVOoaUWJjTB9R4VJzR5vNWzf5t42ESVZFPS8xTySF7CAdV4t/aaIm3UnQ==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", + "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", "dev": true, "license": "MIT", "dependencies": { - "eastasianwidth": "^0.2.0", - "emoji-regex": "^10.2.1", - "strip-ansi": "^7.0.1" + "emoji-regex": "^10.3.0", + "get-east-asian-width": "^1.0.0", + "strip-ansi": "^7.1.0" }, "engines": { - "node": ">=16" + "node": ">=18" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" diff --git a/client/package.json b/client/package.json index 6d4c7c4..58575bb 100644 --- a/client/package.json +++ b/client/package.json @@ -16,7 +16,7 @@ "vscode-languageclient": "^9.0.1" }, "devDependencies": { - "@types/vscode": "^1.99.0", - "@vscode/test-electron": "^2.4.1" + "@types/vscode": "^1.100.0", + "@vscode/test-electron": "^2.5.2" } } diff --git a/client/src/extension.ts b/client/src/extension.ts index 62ff40c..ee6357a 100644 --- a/client/src/extension.ts +++ b/client/src/extension.ts @@ -71,3 +71,12 @@ export function deactivate(): Thenable | undefined { } return client.stop(); } + +export function isExtensionInDebugMode(): boolean { + // process.execArgv contains arguments specific to the Node.js executable itself. + // When debugging, VS Code launches the extension host with flags like + // '--inspect-brk' or '--inspect'. + return process.execArgv.some(arg => + arg.startsWith('--inspect') || arg.startsWith('--debug') // '--debug' is older, but good to include + ); +} diff --git a/client/src/logger.ts b/client/src/logger.ts index 01a261c..bd2e1e1 100644 --- a/client/src/logger.ts +++ b/client/src/logger.ts @@ -1,4 +1,5 @@ import * as vscode from 'vscode'; +import { isExtensionInDebugMode } from './extension'; enum LogLevel { error = 1, @@ -15,23 +16,20 @@ export interface LogMessage { } export class VscodeLogger { - private static _outputChannel: vscode.OutputChannel; + private static _outputChannel: vscode.OutputChannel | undefined; private static get outputChannel(): vscode.OutputChannel { if (!VscodeLogger._outputChannel) { - VscodeLogger._outputChannel = vscode.window.createOutputChannel('VBAPro Output'); - VscodeLogger._outputChannel.show(); + VscodeLogger.initialiseOutputChannel(); } return VscodeLogger._outputChannel; } + private static get configuredLevel(): LogLevel { const config = vscode.workspace.getConfiguration('vbaLanguageServer'); const levelString = config.get('logLevel.outputChannel', 'warning'); return LogLevel[levelString as keyof typeof LogLevel]; } - static info = (msg: string, lvl?: number) => this.log(LogLevel.info, msg, lvl) - static debug = (msg: string, lvl?: number) => this.log(LogLevel.debug, msg, lvl) - static logMessage(params: LogMessage): void { this.log(params.type, params.message, params.level); } @@ -44,18 +42,26 @@ export class VscodeLogger { VscodeLogger.outputChannel.appendLine(`${t} [${LogLevel[type]}] ${i}${msg}`); } + private static initialiseOutputChannel(): void { + const channel = vscode.window.createOutputChannel('VBAPro Output'); + if (isExtensionInDebugMode) { + channel.show(); + } + VscodeLogger._outputChannel = channel; + } + private static getFormattedTimestamp(): string { const now = new Date(); - + const year = now.getFullYear(); const month = (now.getMonth() + 1).toString().padStart(2, "0"); const day = now.getDate().toString().padStart(2, "0"); - + const hours = now.getHours().toString().padStart(2, "0"); const minutes = now.getMinutes().toString().padStart(2, "0"); const seconds = now.getSeconds().toString().padStart(2, "0"); const milliseconds = now.getMilliseconds().toString().padStart(3, "0"); - + return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}.${milliseconds}`; } } diff --git a/client/src/test/codeActions.test.ts b/client/src/test/codeActions.test.ts index 07c78b5..6412459 100644 --- a/client/src/test/codeActions.test.ts +++ b/client/src/test/codeActions.test.ts @@ -8,7 +8,7 @@ import * as assert from 'assert'; import { getDocUri, activate, runOnActivate } from './helper'; import { toRange } from './util'; -suite('Should get code actions', () => { +suite('Should get module code actions', () => { test('actions.module.missingOptionExplicitCodeAction', async () => { const docUri = getDocUri('EmptyModule.bas'); const edits = new vscode.WorkspaceEdit(); diff --git a/client/src/test/diagnostics.test.ts b/client/src/test/diagnostics.test.ts index 18f1ebf..203cddb 100644 --- a/client/src/test/diagnostics.test.ts +++ b/client/src/test/diagnostics.test.ts @@ -8,18 +8,12 @@ import * as assert from 'assert'; import { getDocUri, activate, runOnActivate } from './helper'; import { toRange } from './util'; -suite('Should get diagnostics', () => { - test('diagnostics.class.missingNameAttributeError', async () => { - await testDiagnostics(getDocUri('DiagnosticsMissingAttributeClass.cls'), [ - { - message: 'Module missing attribute VB_NAME.', - range: toRange(4, 3, 4, 3), - severity: vscode.DiagnosticSeverity.Error, - source: 'ex' - } - ]); - }); +enum DiagnosticTag { + Unnecessary = 1, + Deprecated = 2 +} +suite('Should get module diagnostics', () => { test('diagnostics.module.missingNameAttributeError', async () => { await testDiagnostics(getDocUri('DiagnosticsMissingAttributeModule.bas'), [ { @@ -31,23 +25,12 @@ suite('Should get diagnostics', () => { ]); }); - test('diagnostics.class.noOptionExplicitWarning', async () => { - await testDiagnostics(getDocUri('EmptyClass.cls'), [ - { - message: 'Option Explicit is missing from module header.', - range: toRange(11, 1, 11, 1), - severity: vscode.DiagnosticSeverity.Warning, - source: 'ex' - } - ]); - }); - test('diagnostics.module.noOptionExplicitWarning', async () => { await testDiagnostics(getDocUri('EmptyModule.bas'), [ { message: 'Option Explicit is missing from module header.', range: toRange(2, 1, 2, 1), - severity: vscode.DiagnosticSeverity.Warning, + severity: vscode.DiagnosticSeverity.Hint, source: 'ex' } ]); @@ -63,15 +46,15 @@ suite('Should get diagnostics', () => { source: 'ex' }, { - message: 'Unknown attribute \'VB_Creatable\' will be ignored.', + message: 'Unknown attribute \'VB_Creatable\'.', range: toRange(3, 0, 3, 30), - severity: vscode.DiagnosticSeverity.Warning, + severity: vscode.DiagnosticSeverity.Error, source: 'ex' }, { - message: 'Unknown attribute \'VB_Foo\' will be ignored.', + message: 'Unknown attribute \'VB_Foo\'.', range: toRange(4, 0, 4, 24), - severity: vscode.DiagnosticSeverity.Warning, + severity: vscode.DiagnosticSeverity.Error, source: 'ex' }, { @@ -92,6 +75,27 @@ suite('Should get diagnostics', () => { severity: vscode.DiagnosticSeverity.Error, source: 'ex' }, + { + message: 'Enum1 is declared but its value is never read.', + range: toRange(29, 4, 29, 9), + severity: vscode.DiagnosticSeverity.Hint, + tags: [DiagnosticTag.Unnecessary], + source: 'ex' + }, + { + message: 'Enum2 is declared but its value is never read.', + range: toRange(30, 4, 30, 9), + severity: vscode.DiagnosticSeverity.Hint, + tags: [DiagnosticTag.Unnecessary], + source: 'ex' + }, + { + message: 'Enum3 is declared but its value is never read.', + range: toRange(31, 4, 31, 9), + severity: vscode.DiagnosticSeverity.Hint, + tags: [DiagnosticTag.Unnecessary], + source: 'ex' + }, { message: 'Invalid syntax: InvalidSubCall(arg)', range: toRange(43, 4, 43, 23), @@ -100,6 +104,30 @@ suite('Should get diagnostics', () => { } ]); }); +}); + +suite('Should get class diagnostics', () => { + test('diagnostics.class.missingNameAttributeError', async () => { + await testDiagnostics(getDocUri('DiagnosticsMissingAttributeClass.cls'), [ + { + message: 'Module missing attribute VB_NAME.', + range: toRange(4, 3, 4, 3), + severity: vscode.DiagnosticSeverity.Error, + source: 'ex' + } + ]); + }); + + test('diagnostics.class.noOptionExplicitWarning', async () => { + await testDiagnostics(getDocUri('EmptyClass.cls'), [ + { + message: 'Option Explicit is missing from module header.', + range: toRange(11, 1, 11, 1), + severity: vscode.DiagnosticSeverity.Hint, + source: 'ex' + } + ]); + }); test('diagnostics.class.generalDiagnostics', async () => { // Don't seem to be able to sort these. Good luck! @@ -117,15 +145,22 @@ suite('Should get diagnostics', () => { source: 'ex' }, { - message: 'Unknown attribute \'VB_Exxposed\' will be ignored.', + message: 'Unknown attribute \'VB_Exxposed\'.', range: toRange(12, 0, 12, 29), - severity: vscode.DiagnosticSeverity.Warning, + severity: vscode.DiagnosticSeverity.Error, source: 'ex' }, { - message: 'Unknown attribute \'VB_Exxposed\' will be ignored.', + message: 'Unknown attribute \'VB_Exxposed\'.', range: toRange(13, 0, 13, 29), - severity: vscode.DiagnosticSeverity.Warning, + severity: vscode.DiagnosticSeverity.Error, + source: 'ex' + }, + { + message: 'SmkfeiondFoo is declared but its value is never read.', + range: toRange(17, 11, 17, 23), + severity: vscode.DiagnosticSeverity.Hint, + tags: [DiagnosticTag.Unnecessary], source: 'ex' }, { @@ -140,11 +175,46 @@ suite('Should get diagnostics', () => { severity: vscode.DiagnosticSeverity.Error, source: 'ex' }, + { + message: 'GkiofseiFoo is declared but its value is never read.', + range: toRange(25, 4, 25, 15), + severity: vscode.DiagnosticSeverity.Hint, + tags: [DiagnosticTag.Unnecessary], + source: 'ex' + }, { message: 'Enum declarations cannot appear below a Sub, Function, or Property declaration.', range: toRange(37, 7, 41, 8), severity: vscode.DiagnosticSeverity.Error, source: 'ex' + }, + { + message: 'PewmfoiawFoo is declared but its value is never read.', + range: toRange(37, 12, 37, 24), + severity: vscode.DiagnosticSeverity.Hint, + tags: [DiagnosticTag.Unnecessary], + source: 'ex' + }, + { + message: 'Enum1 is declared but its value is never read.', + range: toRange(38, 4, 38, 9), + severity: vscode.DiagnosticSeverity.Hint, + tags: [DiagnosticTag.Unnecessary], + source: 'ex' + }, + { + message: 'Enum2 is declared but its value is never read.', + range: toRange(39, 4, 39, 9), + severity: vscode.DiagnosticSeverity.Hint, + tags: [DiagnosticTag.Unnecessary], + source: 'ex' + }, + { + message: 'Enum3 is declared but its value is never read.', + range: toRange(40, 4, 40, 9), + severity: vscode.DiagnosticSeverity.Hint, + tags: [DiagnosticTag.Unnecessary], + source: 'ex' } ]); }); @@ -165,5 +235,6 @@ async function testDiagnostics(docUri: vscode.Uri, expectedDiagnostics: vscode.D assert.equal(actualDiagnostic.message, expectedDiagnostic.message, `Message: expected '${expectedDiagnostic.message}' got '${actualDiagnostic.message}'.`); assert.deepEqual(actualDiagnostic.range, expectedDiagnostic.range, `Range: expected '${JSON.stringify(expectedDiagnostic.range)}' got '${JSON.stringify(actualDiagnostic.range)}'.`); assert.equal(actualDiagnostic.severity, expectedDiagnostic.severity, `Severity: expected '${expectedDiagnostic.severity}' got '${actualDiagnostic.severity}'.`); + assert.deepEqual(actualDiagnostic.tags, expectedDiagnostic.tags, `Tags: expected '${JSON.stringify(expectedDiagnostic.tags)}' got '${JSON.stringify(actualDiagnostic.tags)}'.`); }); } \ No newline at end of file diff --git a/client/src/test/foldingRanges.test.ts b/client/src/test/foldingRanges.test.ts index 6b80841..835841e 100644 --- a/client/src/test/foldingRanges.test.ts +++ b/client/src/test/foldingRanges.test.ts @@ -2,7 +2,7 @@ import * as vscode from 'vscode'; import * as assert from 'assert'; import { getDocUri, activate, runOnActivate } from './helper'; -suite('Should get folding ranges', () => { +suite('Should get class folding ranges', () => { test('formatting.class.template', async () => { const subFoo = {start: 23, end: 42}; const subBar = {start: 44, end: 58}; diff --git a/client/src/test/formatting.test.ts b/client/src/test/formatting.test.ts index ce539d1..40443d4 100644 --- a/client/src/test/formatting.test.ts +++ b/client/src/test/formatting.test.ts @@ -3,7 +3,7 @@ import * as assert from 'assert'; import { getDocUri, activate, runOnActivate } from './helper'; import { toRange } from './util'; -suite('Should get text edits', () => { +suite('Should get class text edits', () => { test('formatting.class.template', async () => { await testTextEdits(getDocUri('FormatTemplateClass.cls'), [ {range: toRange(3, 0, 3, 0), newText: ' '}, @@ -12,7 +12,9 @@ suite('Should get text edits', () => { {range: toRange(8, 0, 8, 8), newText: ''} ]); }); +}); +suite('Should get module text edits', () => { test('formatting.module.directives', async () => { await testTextEdits(getDocUri('FormatPrecompilerDirectives.bas'), [ // Staggered (half) indentation. diff --git a/client/src/test/helper.ts b/client/src/test/helper.ts index 8168979..7fca652 100644 --- a/client/src/test/helper.ts +++ b/client/src/test/helper.ts @@ -10,7 +10,7 @@ export let doc: vscode.TextDocument; export let editor: vscode.TextEditor; export let documentEol: string; export let platformEol: string; -const TIMEOUTMS = 5000; +const TIMEOUTMS = 10000; /** * Activates the vscode.lsp-sample extension @@ -35,10 +35,10 @@ async function sleep(ms: number) { return new Promise(resolve => setTimeout(resolve, ms)); } -export async function runOnActivate(action: () => T|Thenable, test: (result: T) => boolean): Promise { +export async function runOnActivate(action: () => T | Thenable, test: (result: T) => boolean): Promise { const timeout = getTimeout(); while (Date.now() < timeout) { - const result = await action(); + const result = await action(); if (test(result)) { return result; } diff --git a/icons/theme-seti/.vscodeignore b/icons/theme-seti/.vscodeignore new file mode 100644 index 0000000..25699ef --- /dev/null +++ b/icons/theme-seti/.vscodeignore @@ -0,0 +1,4 @@ +build/** +cgmanifest.json +icons/preview.html +CONTRIBUTING.md diff --git a/icons/theme-seti/README.md b/icons/theme-seti/README.md new file mode 100644 index 0000000..2c4d721 --- /dev/null +++ b/icons/theme-seti/README.md @@ -0,0 +1,32 @@ +# theme-seti + +This is an icon theme that uses the icons from a fork of [`seti-ui`](https://github.com/jesseweed/seti-ui). + +## Updating icons + +There is script that can be used to update icons, [./build/update-icon-theme.js](build/update-icon-theme.js). + +To run this script, run `npm run update` from the `theme-seti` directory. + +This can be run in one of two ways: looking at a local copy of `seti-ui` for icons, or getting them straight from GitHub. + +If you want to run it from a local copy of `seti-ui`, first clone [`seti-ui`](https://github.com/jesseweed/seti-ui) to the folder next to your `vscode` repo (from the `theme-seti` directory, `../../`). +Then, inside the `set-ui` directory, run `npm install` followed by `npm run prepublishOnly`. This will generate updated icons. + +If you want to download the icons straight from GitHub, change the `FROM_DISK` variable to `false` inside of `update-icon-theme.js`. + +### Languages not shipped with `vscode` + +Languages that are not shipped with `vscode` must be added to the `nonBuiltInLanguages` object inside of `update-icon-theme.js`. + +These should match [the file mapping in `seti-ui`](https://github.com/jesseweed/seti-ui/blob/master/styles/components/icons/mapping.less). + +Please try and keep this list in alphabetical order! Thank you. + +## Previewing icons + +There is a [`./icons/preview.html`](./icons/preview.html) file that can be opened to see all of the icons included in the theme. +Note that to view this, it needs to be hosted by a web server. + +When updating icons, it is always a good idea to make sure that they work properly by looking at this page. +When submitting a PR that updates these icons, a screenshot of the preview page should accompany it. diff --git a/icons/theme-seti/ThirdPartyNotices.txt b/icons/theme-seti/ThirdPartyNotices.txt new file mode 100644 index 0000000..6a495c8 --- /dev/null +++ b/icons/theme-seti/ThirdPartyNotices.txt @@ -0,0 +1,32 @@ + +THIRD-PARTY SOFTWARE NOTICES AND INFORMATION +For VBA-LanguageServer vscode-theme-seti + +This theme is based on or incorporates material from the projects listed below ("Third Party OSS"). The original copyright +notice and the license under which VBA-LanguageServer received such Third Party OSS, are set forth below. Such licenses and notice +are provided for informational purposes only. VBA-LanguageServer licenses the Third Party OSS to you under the licensing terms for +the VBA-LanguageServer product or service. VBA-LanguageServer reserves all other rights not expressly granted under this agreement, whether +by implication, estoppel or otherwise.† + +1. Seti UI - A subtle dark colored UI theme for Atom. (https://github.com/jesseweed/seti-ui) + +Copyright (c) 2014 Jesse Weed + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. \ No newline at end of file diff --git a/icons/theme-seti/build/update-icon-theme.js b/icons/theme-seti/build/update-icon-theme.js new file mode 100644 index 0000000..bd7f518 --- /dev/null +++ b/icons/theme-seti/build/update-icon-theme.js @@ -0,0 +1,475 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +//@ts-nocheck + +'use strict'; + +const path = require('path'); +const fs = require('fs'); +const https = require('https'); +const url = require('url'); +const minimatch = require('minimatch'); + +// list of languagesId not shipped with VSCode. The information is used to associate an icon with a language association +// Please try and keep this list in alphabetical order! Thank you. +const nonBuiltInLanguages = { // { fileNames, extensions } + "argdown": { extensions: ['ad', 'adown', 'argdown', 'argdn'] }, + "bicep": { extensions: ['bicep'] }, + "elixir": { extensions: ['ex'] }, + "elm": { extensions: ['elm'] }, + "erb": { extensions: ['erb', 'rhtml', 'html.erb'] }, + "github-issues": { extensions: ['github-issues'] }, + "gradle": { extensions: ['gradle'] }, + "godot": { extensions: ['gd', 'godot', 'tres', 'tscn'] }, + "haml": { extensions: ['haml'] }, + "haskell": { extensions: ['hs'] }, + "haxe": { extensions: ['hx'] }, + "jinja": { extensions: ['jinja'] }, + "kotlin": { extensions: ['kt'] }, + "mustache": { extensions: ['mustache', 'mst', 'mu', 'stache'] }, + "nunjucks": { extensions: ['nunjucks', 'nunjs', 'nunj', 'nj', 'njk', 'tmpl', 'tpl'] }, + "ocaml": { extensions: ['ml', 'mli', 'mll', 'mly', 'eliom', 'eliomi'] }, + "puppet": { extensions: ['puppet'] }, + "r": { extensions: ['r', 'rhistory', 'rprofile', 'rt'] }, + "rescript": { extensions: ['res', 'resi'] }, + "sass": { extensions: ['sass'] }, + "stylus": { extensions: ['styl'] }, + "terraform": { extensions: ['tf', 'tfvars', 'hcl'] }, + "todo": { fileNames: ['todo'] }, + "vala": { extensions: ['vala'] }, + "vue": { extensions: ['vue'] } +}; + +// list of languagesId that inherit the icon from another language +const inheritIconFromLanguage = { + "jsonc": 'json', + "jsonl": 'json', + "postcss": 'css', + "django-html": 'html', + "blade": 'php' +}; + +const ignoreExtAssociation = { + "properties": true +}; + +const FROM_DISK = false; // set to true to take content from a repo checked out next to the vscode repo + +let font, fontMappingsFile, fileAssociationFile, colorsFile; +if (!FROM_DISK) { + font = 'https://raw.githubusercontent.com/DecimalTurn/seti-ui/vba/styles/_fonts/seti/seti.woff'; + fontMappingsFile = 'https://raw.githubusercontent.com/DecimalTurn/seti-ui/vba/styles/_fonts/seti.less'; + fileAssociationFile = 'https://raw.githubusercontent.com/DecimalTurn/seti-ui/vba/styles/components/icons/mapping.less'; + colorsFile = 'https://raw.githubusercontent.com/DecimalTurn/seti-ui/vba/styles/ui-variables.less'; +} else { + font = '../../../seti-ui/styles/_fonts/seti/seti.woff'; + fontMappingsFile = '../../../seti-ui/styles/_fonts/seti.less'; + fileAssociationFile = '../../../seti-ui/styles/components/icons/mapping.less'; + colorsFile = '../../../seti-ui/styles/ui-variables.less'; +} + +function getCommitSha(repoId) { + const commitInfo = 'https://api.github.com/repos/' + repoId + '/commits/vba'; + return download(commitInfo).then(function (content) { + try { + const lastCommit = JSON.parse(content); + return Promise.resolve({ + commitSha: lastCommit.sha, + commitDate: lastCommit.commit.author.date + }); + } catch (e) { + console.error('Failed parsing ' + content); + return Promise.resolve(null); + } + }, function () { + console.error('Failed loading ' + commitInfo); + return Promise.resolve(null); + }); +} + +function download(source) { + if (source.startsWith('.')) { + return readFile(source); + } + return new Promise((c, e) => { + const _url = url.parse(source); + const options = { host: _url.host, port: _url.port, path: _url.path, headers: { 'User-Agent': 'NodeJS' } }; + let content = ''; + https.get(options, function (response) { + response.on('data', function (data) { + content += data.toString(); + }).on('end', function () { + c(content); + }); + }).on('error', function (err) { + e(err.message); + }); + }); +} + +function readFile(fileName) { + return new Promise((c, e) => { + fs.readFile(fileName, function (err, data) { + if (err) { + e(err); + } else { + c(data.toString()); + } + }); + }); +} + +function downloadBinary(source, dest) { + if (source.startsWith('.')) { + return copyFile(source, dest); + } + + return new Promise((c, e) => { + https.get(source, function (response) { + switch (response.statusCode) { + case 200: { + const file = fs.createWriteStream(dest); + response.on('data', function (chunk) { + file.write(chunk); + }).on('end', function () { + file.end(); + c(null); + }).on('error', function (err) { + fs.unlink(dest); + e(err.message); + }); + break; + } + case 301: + case 302: + case 303: + case 307: + console.log('redirect to ' + response.headers.location); + downloadBinary(response.headers.location, dest).then(c, e); + break; + default: + e(new Error('Server responded with status code ' + response.statusCode)); + } + }); + }); +} + +function copyFile(fileName, dest) { + return new Promise((c, e) => { + let cbCalled = false; + function handleError(err) { + if (!cbCalled) { + e(err); + cbCalled = true; + } + } + const rd = fs.createReadStream(fileName); + rd.on("error", handleError); + const wr = fs.createWriteStream(dest); + wr.on("error", handleError); + wr.on("close", function () { + if (!cbCalled) { + c(); + cbCalled = true; + } + }); + rd.pipe(wr); + }); +} + +function darkenColor(color) { + let res = '#'; + for (let i = 1; i < 7; i += 2) { + const newVal = Math.round(parseInt('0x' + color.substr(i, 2), 16) * 0.9); + const hex = newVal.toString(16); + if (hex.length === 1) { + res += '0'; + } + res += hex; + } + return res; +} + +function mergeMapping(to, from, property) { + if (from[property]) { + if (to[property]) { + to[property].push(...from[property]); + } else { + to[property] = from[property]; + } + } +} + +function getLanguageMappings() { + const langMappings = {}; + const allExtensions = fs.readdirSync('..'); + for (let i = 0; i < allExtensions.length; i++) { + const dirPath = path.join('..', allExtensions[i], 'package.json'); + if (fs.existsSync(dirPath)) { + const content = fs.readFileSync(dirPath).toString(); + const jsonContent = JSON.parse(content); + const languages = jsonContent.contributes && jsonContent.contributes.languages; + if (Array.isArray(languages)) { + for (let k = 0; k < languages.length; k++) { + const languageId = languages[k].id; + if (languageId) { + const extensions = languages[k].extensions; + const mapping = {}; + if (Array.isArray(extensions)) { + mapping.extensions = extensions.map(function (e) { return e.substr(1).toLowerCase(); }); + } + const filenames = languages[k].filenames; + if (Array.isArray(filenames)) { + mapping.fileNames = filenames.map(function (f) { return f.toLowerCase(); }); + } + const filenamePatterns = languages[k].filenamePatterns; + if (Array.isArray(filenamePatterns)) { + mapping.filenamePatterns = filenamePatterns.map(function (f) { return f.toLowerCase(); }); + } + const existing = langMappings[languageId]; + + if (existing) { + // multiple contributions to the same language + // give preference to the contribution wth the configuration + if (languages[k].configuration) { + mergeMapping(mapping, existing, 'extensions'); + mergeMapping(mapping, existing, 'fileNames'); + mergeMapping(mapping, existing, 'filenamePatterns'); + langMappings[languageId] = mapping; + } else { + mergeMapping(existing, mapping, 'extensions'); + mergeMapping(existing, mapping, 'fileNames'); + mergeMapping(existing, mapping, 'filenamePatterns'); + } + } else { + langMappings[languageId] = mapping; + } + } + } + } + } + } + for (const languageId in nonBuiltInLanguages) { + langMappings[languageId] = nonBuiltInLanguages[languageId]; + } + return langMappings; +} + +exports.copyFont = function () { + return downloadBinary(font, './icons/seti.woff'); +}; + +exports.update = function () { + + console.log('Reading from ' + fontMappingsFile); + const def2Content = {}; + const ext2Def = {}; + const fileName2Def = {}; + const def2ColorId = {}; + const colorId2Value = {}; + const lang2Def = {}; + + function writeFileIconContent(info) { + const iconDefinitions = {}; + const allDefs = Object.keys(def2Content).sort(); + + for (let i = 0; i < allDefs.length; i++) { + const def = allDefs[i]; + const entry = { fontCharacter: def2Content[def] }; + const colorId = def2ColorId[def]; + if (colorId) { + const colorValue = colorId2Value[colorId]; + if (colorValue) { + entry.fontColor = colorValue; + + const entryInverse = { fontCharacter: entry.fontCharacter, fontColor: darkenColor(colorValue) }; + iconDefinitions[def + '_light'] = entryInverse; + } + } + iconDefinitions[def] = entry; + } + + function getInvertSet(input) { + const result = {}; + for (const assoc in input) { + const invertDef = input[assoc] + '_light'; + if (iconDefinitions[invertDef]) { + result[assoc] = invertDef; + } + } + return result; + } + + const res = { + information_for_contributors: [ + 'This file has been generated from data in https://github.com/DecimalTurn/seti-ui', + '- icon definitions: https://github.com/DecimalTurn/seti-ui/blob/vba/styles/_fonts/seti.less', + '- icon colors: https://github.com/DecimalTurn/seti-ui/blob/vba/styles/ui-variables.less', + '- file associations: https://github.com/DecimalTurn/seti-ui/blob/vba/styles/components/icons/mapping.less', + 'If you want to provide a fix or improvement, please create a pull request against the DecimalTurn/seti-ui repository.', + 'Once accepted there, we are happy to receive an update request.', + ], + fonts: [{ + id: "seti", + src: [{ "path": "./seti.woff", "format": "woff" }], + weight: "normal", + style: "normal", + size: "150%" + }], + iconDefinitions: iconDefinitions, + // folder: "_folder", + file: "_default", + fileExtensions: ext2Def, + fileNames: fileName2Def, + languageIds: lang2Def, + light: { + file: "_default_light", + fileExtensions: getInvertSet(ext2Def), + languageIds: getInvertSet(lang2Def), + fileNames: getInvertSet(fileName2Def) + }, + version: 'https://github.com/DecimalTurn/seti-ui/commit/' + info.commitSha, + }; + + const path = './icons/vs-seti-icon-theme.json'; + fs.writeFileSync(path, JSON.stringify(res, null, '\t')); + console.log('written ' + path); + } + + + let match; + + return download(fontMappingsFile).then(function (content) { + const regex = /@([\w-]+):\s*'(\\E[0-9A-F]+)';/g; + const contents = {}; + while ((match = regex.exec(content)) !== null) { + contents[match[1]] = match[2]; + } + + return download(fileAssociationFile).then(function (content) { + const regex2 = /\.icon-(?:set|partial)\(['"]([\w-\.+]+)['"],\s*['"]([\w-]+)['"],\s*(@[\w-]+)\)/g; + while ((match = regex2.exec(content)) !== null) { + const pattern = match[1]; + let def = '_' + match[2]; + const colorId = match[3]; + let storedColorId = def2ColorId[def]; + let i = 1; + while (storedColorId && colorId !== storedColorId) { // different colors for the same def? + def = `_${match[2]}_${i}`; + storedColorId = def2ColorId[def]; + i++; + } + if (!def2ColorId[def]) { + def2ColorId[def] = colorId; + def2Content[def] = contents[match[2]]; + } + + if (def === '_default') { + continue; // no need to assign default color. + } + if (pattern[0] === '.') { + ext2Def[pattern.substr(1).toLowerCase()] = def; + } else { + fileName2Def[pattern.toLowerCase()] = def; + } + } + // replace extensions for languageId + const langMappings = getLanguageMappings(); + for (let lang in langMappings) { + const mappings = langMappings[lang]; + const exts = mappings.extensions || []; + const fileNames = mappings.fileNames || []; + const filenamePatterns = mappings.filenamePatterns || []; + let preferredDef = null; + // use the first file extension association for the preferred definition + for (let i1 = 0; i1 < exts.length && !preferredDef; i1++) { + preferredDef = ext2Def[exts[i1]]; + } + // use the first file name association for the preferred definition, if not availbale + for (let i1 = 0; i1 < fileNames.length && !preferredDef; i1++) { + preferredDef = fileName2Def[fileNames[i1]]; + } + for (let i1 = 0; i1 < filenamePatterns.length && !preferredDef; i1++) { + let pattern = filenamePatterns[i1]; + for (const name in fileName2Def) { + if (minimatch(name, pattern)) { + preferredDef = fileName2Def[name]; + break; + } + } + } + if (preferredDef) { + lang2Def[lang] = preferredDef; + if (!nonBuiltInLanguages[lang] && !inheritIconFromLanguage[lang]) { + for (let i2 = 0; i2 < exts.length; i2++) { + // remove the extension association, unless it is different from the preferred + if (ext2Def[exts[i2]] === preferredDef || ignoreExtAssociation[exts[i2]]) { + delete ext2Def[exts[i2]]; + } + } + for (let i2 = 0; i2 < fileNames.length; i2++) { + // remove the fileName association, unless it is different from the preferred + if (fileName2Def[fileNames[i2]] === preferredDef) { + delete fileName2Def[fileNames[i2]]; + } + } + for (let i2 = 0; i2 < filenamePatterns.length; i2++) { + let pattern = filenamePatterns[i2]; + // remove the filenamePatterns association, unless it is different from the preferred + for (const name in fileName2Def) { + if (minimatch(name, pattern) && fileName2Def[name] === preferredDef) { + delete fileName2Def[name]; + } + } + } + } + } + } + for (const lang in inheritIconFromLanguage) { + const superLang = inheritIconFromLanguage[lang]; + const def = lang2Def[superLang]; + if (def) { + lang2Def[lang] = def; + } else { + console.log('skipping icon def for ' + lang + ': no icon for ' + superLang + ' defined'); + } + + } + + + return download(colorsFile).then(function (content) { + const regex3 = /(@[\w-]+):\s*(#[0-9a-z]+)/g; + while ((match = regex3.exec(content)) !== null) { + colorId2Value[match[1]] = match[2]; + } + return getCommitSha('DecimalTurn/seti-ui').then(function (info) { + try { + writeFileIconContent(info); + + const cgmanifestPath = './cgmanifest.json'; + const cgmanifest = fs.readFileSync(cgmanifestPath).toString(); + const cgmanifestContent = JSON.parse(cgmanifest); + cgmanifestContent['registrations'][0]['component']['git']['commitHash'] = info.commitSha; + fs.writeFileSync(cgmanifestPath, JSON.stringify(cgmanifestContent, null, '\t')); + console.log('updated ' + cgmanifestPath); + + console.log('Updated to DecimalTurn/seti-ui@' + info.commitSha.substr(0, 7) + ' (' + info.commitDate.substr(0, 10) + ')'); + + } catch (e) { + console.error(e); + } + }); + }); + }); + }, console.error); +}; + +if (path.basename(process.argv[1]) === 'update-icon-theme.js') { + exports.copyFont().then(() => exports.update()); +} + + + diff --git a/icons/theme-seti/cgmanifest.json b/icons/theme-seti/cgmanifest.json new file mode 100644 index 0000000..cd70a9f --- /dev/null +++ b/icons/theme-seti/cgmanifest.json @@ -0,0 +1,16 @@ +{ + "registrations": [ + { + "component": { + "type": "git", + "git": { + "name": "seti-ui", + "repositoryUrl": "https://github.com/DecimalTurn/seti-ui", + "commitHash": "7a8d51ccb32737be812549fe1c31fae8276b284f" + } + }, + "version": "0.1.0" + } + ], + "version": 1 +} \ No newline at end of file diff --git a/icons/theme-seti/icons/preview.html b/icons/theme-seti/icons/preview.html new file mode 100644 index 0000000..84b5166 --- /dev/null +++ b/icons/theme-seti/icons/preview.html @@ -0,0 +1,104 @@ + + + + + + seti font preview + + + + + + + + diff --git a/icons/theme-seti/icons/seti-circular-128x128.png b/icons/theme-seti/icons/seti-circular-128x128.png new file mode 100644 index 0000000..fbe533b Binary files /dev/null and b/icons/theme-seti/icons/seti-circular-128x128.png differ diff --git a/icons/theme-seti/icons/seti.woff b/icons/theme-seti/icons/seti.woff new file mode 100644 index 0000000..b20123e Binary files /dev/null and b/icons/theme-seti/icons/seti.woff differ diff --git a/icons/theme-seti/icons/vs-seti-icon-theme.json b/icons/theme-seti/icons/vs-seti-icon-theme.json new file mode 100644 index 0000000..7a69ca3 --- /dev/null +++ b/icons/theme-seti/icons/vs-seti-icon-theme.json @@ -0,0 +1,2496 @@ +{ + "information_for_contributors": [ + "This file has been generated from data in https://github.com/DecimalTurn/seti-ui", + "- icon definitions: https://github.com/DecimalTurn/seti-ui/blob/vba/styles/_fonts/seti.less", + "- icon colors: https://github.com/DecimalTurn/seti-ui/blob/vba/styles/ui-variables.less", + "- file associations: https://github.com/DecimalTurn/seti-ui/blob/vba/styles/components/icons/mapping.less", + "If you want to provide a fix or improvement, please create a pull request against the DecimalTurn/seti-ui repository.", + "Once accepted there, we are happy to receive an update request." + ], + "fonts": [ + { + "id": "seti", + "src": [ + { + "path": "./seti.woff", + "format": "woff" + } + ], + "weight": "normal", + "style": "normal", + "size": "150%" + } + ], + "iconDefinitions": { + "_R_light": { + "fontCharacter": "\\E07B", + "fontColor": "#498ba7" + }, + "_R": { + "fontCharacter": "\\E07B", + "fontColor": "#519aba" + }, + "_argdown_light": { + "fontCharacter": "\\E002", + "fontColor": "#498ba7" + }, + "_argdown": { + "fontCharacter": "\\E002", + "fontColor": "#519aba" + }, + "_asm_light": { + "fontCharacter": "\\E003", + "fontColor": "#b8383d" + }, + "_asm": { + "fontCharacter": "\\E003", + "fontColor": "#cc3e44" + }, + "_audio_light": { + "fontCharacter": "\\E004", + "fontColor": "#9068b0" + }, + "_audio": { + "fontCharacter": "\\E004", + "fontColor": "#a074c4" + }, + "_babel_light": { + "fontCharacter": "\\E005", + "fontColor": "#b7b73b" + }, + "_babel": { + "fontCharacter": "\\E005", + "fontColor": "#cbcb41" + }, + "_bazel_light": { + "fontCharacter": "\\E006", + "fontColor": "#7fae42" + }, + "_bazel": { + "fontCharacter": "\\E006", + "fontColor": "#8dc149" + }, + "_bazel_1_light": { + "fontCharacter": "\\E006", + "fontColor": "#455155" + }, + "_bazel_1": { + "fontCharacter": "\\E006", + "fontColor": "#4d5a5e" + }, + "_bicep_light": { + "fontCharacter": "\\E007", + "fontColor": "#498ba7" + }, + "_bicep": { + "fontCharacter": "\\E007", + "fontColor": "#519aba" + }, + "_bower_light": { + "fontCharacter": "\\E008", + "fontColor": "#cc6d2e" + }, + "_bower": { + "fontCharacter": "\\E008", + "fontColor": "#e37933" + }, + "_bsl_light": { + "fontCharacter": "\\E009", + "fontColor": "#b8383d" + }, + "_bsl": { + "fontCharacter": "\\E009", + "fontColor": "#cc3e44" + }, + "_c_light": { + "fontCharacter": "\\E00B", + "fontColor": "#498ba7" + }, + "_c": { + "fontCharacter": "\\E00B", + "fontColor": "#519aba" + }, + "_c-sharp_light": { + "fontCharacter": "\\E00A", + "fontColor": "#498ba7" + }, + "_c-sharp": { + "fontCharacter": "\\E00A", + "fontColor": "#519aba" + }, + "_c_1_light": { + "fontCharacter": "\\E00B", + "fontColor": "#9068b0" + }, + "_c_1": { + "fontCharacter": "\\E00B", + "fontColor": "#a074c4" + }, + "_c_2_light": { + "fontCharacter": "\\E00B", + "fontColor": "#b7b73b" + }, + "_c_2": { + "fontCharacter": "\\E00B", + "fontColor": "#cbcb41" + }, + "_cake_light": { + "fontCharacter": "\\E00C", + "fontColor": "#b8383d" + }, + "_cake": { + "fontCharacter": "\\E00C", + "fontColor": "#cc3e44" + }, + "_cake_php_light": { + "fontCharacter": "\\E00D", + "fontColor": "#b8383d" + }, + "_cake_php": { + "fontCharacter": "\\E00D", + "fontColor": "#cc3e44" + }, + "_clock_light": { + "fontCharacter": "\\E011", + "fontColor": "#498ba7" + }, + "_clock": { + "fontCharacter": "\\E011", + "fontColor": "#519aba" + }, + "_clock_1_light": { + "fontCharacter": "\\E011", + "fontColor": "#627379" + }, + "_clock_1": { + "fontCharacter": "\\E011", + "fontColor": "#6d8086" + }, + "_clojure_light": { + "fontCharacter": "\\E012", + "fontColor": "#7fae42" + }, + "_clojure": { + "fontCharacter": "\\E012", + "fontColor": "#8dc149" + }, + "_clojure_1_light": { + "fontCharacter": "\\E012", + "fontColor": "#498ba7" + }, + "_clojure_1": { + "fontCharacter": "\\E012", + "fontColor": "#519aba" + }, + "_code-climate_light": { + "fontCharacter": "\\E013", + "fontColor": "#7fae42" + }, + "_code-climate": { + "fontCharacter": "\\E013", + "fontColor": "#8dc149" + }, + "_code-search_light": { + "fontCharacter": "\\E014", + "fontColor": "#9068b0" + }, + "_code-search": { + "fontCharacter": "\\E014", + "fontColor": "#a074c4" + }, + "_coffee_light": { + "fontCharacter": "\\E015", + "fontColor": "#b7b73b" + }, + "_coffee": { + "fontCharacter": "\\E015", + "fontColor": "#cbcb41" + }, + "_coldfusion_light": { + "fontCharacter": "\\E017", + "fontColor": "#498ba7" + }, + "_coldfusion": { + "fontCharacter": "\\E017", + "fontColor": "#519aba" + }, + "_config_light": { + "fontCharacter": "\\E018", + "fontColor": "#627379" + }, + "_config": { + "fontCharacter": "\\E018", + "fontColor": "#6d8086" + }, + "_cpp_light": { + "fontCharacter": "\\E019", + "fontColor": "#498ba7" + }, + "_cpp": { + "fontCharacter": "\\E019", + "fontColor": "#519aba" + }, + "_cpp_1_light": { + "fontCharacter": "\\E019", + "fontColor": "#9068b0" + }, + "_cpp_1": { + "fontCharacter": "\\E019", + "fontColor": "#a074c4" + }, + "_cpp_2_light": { + "fontCharacter": "\\E019", + "fontColor": "#b7b73b" + }, + "_cpp_2": { + "fontCharacter": "\\E019", + "fontColor": "#cbcb41" + }, + "_crystal_light": { + "fontCharacter": "\\E01A", + "fontColor": "#bfc2c1" + }, + "_crystal": { + "fontCharacter": "\\E01A", + "fontColor": "#d4d7d6" + }, + "_crystal_embedded_light": { + "fontCharacter": "\\E01B", + "fontColor": "#bfc2c1" + }, + "_crystal_embedded": { + "fontCharacter": "\\E01B", + "fontColor": "#d4d7d6" + }, + "_css_light": { + "fontCharacter": "\\E01C", + "fontColor": "#498ba7" + }, + "_css": { + "fontCharacter": "\\E01C", + "fontColor": "#519aba" + }, + "_csv_light": { + "fontCharacter": "\\E01D", + "fontColor": "#7fae42" + }, + "_csv": { + "fontCharacter": "\\E01D", + "fontColor": "#8dc149" + }, + "_cu_light": { + "fontCharacter": "\\E01E", + "fontColor": "#7fae42" + }, + "_cu": { + "fontCharacter": "\\E01E", + "fontColor": "#8dc149" + }, + "_cu_1_light": { + "fontCharacter": "\\E01E", + "fontColor": "#9068b0" + }, + "_cu_1": { + "fontCharacter": "\\E01E", + "fontColor": "#a074c4" + }, + "_d_light": { + "fontCharacter": "\\E01F", + "fontColor": "#b8383d" + }, + "_d": { + "fontCharacter": "\\E01F", + "fontColor": "#cc3e44" + }, + "_dart_light": { + "fontCharacter": "\\E020", + "fontColor": "#498ba7" + }, + "_dart": { + "fontCharacter": "\\E020", + "fontColor": "#519aba" + }, + "_db_light": { + "fontCharacter": "\\E021", + "fontColor": "#dd4b78" + }, + "_db": { + "fontCharacter": "\\E021", + "fontColor": "#f55385" + }, + "_db_1_light": { + "fontCharacter": "\\E021", + "fontColor": "#498ba7" + }, + "_db_1": { + "fontCharacter": "\\E021", + "fontColor": "#519aba" + }, + "_default_light": { + "fontCharacter": "\\E022", + "fontColor": "#bfc2c1" + }, + "_default": { + "fontCharacter": "\\E022", + "fontColor": "#d4d7d6" + }, + "_docker_light": { + "fontCharacter": "\\E024", + "fontColor": "#498ba7" + }, + "_docker": { + "fontCharacter": "\\E024", + "fontColor": "#519aba" + }, + "_docker_1_light": { + "fontCharacter": "\\E024", + "fontColor": "#455155" + }, + "_docker_1": { + "fontCharacter": "\\E024", + "fontColor": "#4d5a5e" + }, + "_docker_2_light": { + "fontCharacter": "\\E024", + "fontColor": "#7fae42" + }, + "_docker_2": { + "fontCharacter": "\\E024", + "fontColor": "#8dc149" + }, + "_docker_3_light": { + "fontCharacter": "\\E024", + "fontColor": "#dd4b78" + }, + "_docker_3": { + "fontCharacter": "\\E024", + "fontColor": "#f55385" + }, + "_ejs_light": { + "fontCharacter": "\\E026", + "fontColor": "#b7b73b" + }, + "_ejs": { + "fontCharacter": "\\E026", + "fontColor": "#cbcb41" + }, + "_elixir_light": { + "fontCharacter": "\\E027", + "fontColor": "#9068b0" + }, + "_elixir": { + "fontCharacter": "\\E027", + "fontColor": "#a074c4" + }, + "_elixir_script_light": { + "fontCharacter": "\\E028", + "fontColor": "#9068b0" + }, + "_elixir_script": { + "fontCharacter": "\\E028", + "fontColor": "#a074c4" + }, + "_elm_light": { + "fontCharacter": "\\E029", + "fontColor": "#498ba7" + }, + "_elm": { + "fontCharacter": "\\E029", + "fontColor": "#519aba" + }, + "_eslint_light": { + "fontCharacter": "\\E02B", + "fontColor": "#9068b0" + }, + "_eslint": { + "fontCharacter": "\\E02B", + "fontColor": "#a074c4" + }, + "_eslint_1_light": { + "fontCharacter": "\\E02B", + "fontColor": "#455155" + }, + "_eslint_1": { + "fontCharacter": "\\E02B", + "fontColor": "#4d5a5e" + }, + "_ethereum_light": { + "fontCharacter": "\\E02C", + "fontColor": "#498ba7" + }, + "_ethereum": { + "fontCharacter": "\\E02C", + "fontColor": "#519aba" + }, + "_f-sharp_light": { + "fontCharacter": "\\E02D", + "fontColor": "#498ba7" + }, + "_f-sharp": { + "fontCharacter": "\\E02D", + "fontColor": "#519aba" + }, + "_favicon_light": { + "fontCharacter": "\\E02E", + "fontColor": "#b7b73b" + }, + "_favicon": { + "fontCharacter": "\\E02E", + "fontColor": "#cbcb41" + }, + "_firebase_light": { + "fontCharacter": "\\E02F", + "fontColor": "#cc6d2e" + }, + "_firebase": { + "fontCharacter": "\\E02F", + "fontColor": "#e37933" + }, + "_firefox_light": { + "fontCharacter": "\\E030", + "fontColor": "#cc6d2e" + }, + "_firefox": { + "fontCharacter": "\\E030", + "fontColor": "#e37933" + }, + "_font_light": { + "fontCharacter": "\\E032", + "fontColor": "#b8383d" + }, + "_font": { + "fontCharacter": "\\E032", + "fontColor": "#cc3e44" + }, + "_git_light": { + "fontCharacter": "\\E033", + "fontColor": "#3b4b52" + }, + "_git": { + "fontCharacter": "\\E033", + "fontColor": "#41535b" + }, + "_github_light": { + "fontCharacter": "\\E034", + "fontColor": "#bfc2c1" + }, + "_github": { + "fontCharacter": "\\E034", + "fontColor": "#d4d7d6" + }, + "_gitlab_light": { + "fontCharacter": "\\E035", + "fontColor": "#cc6d2e" + }, + "_gitlab": { + "fontCharacter": "\\E035", + "fontColor": "#e37933" + }, + "_go_light": { + "fontCharacter": "\\E038", + "fontColor": "#498ba7" + }, + "_go": { + "fontCharacter": "\\E038", + "fontColor": "#519aba" + }, + "_go2_light": { + "fontCharacter": "\\E039", + "fontColor": "#498ba7" + }, + "_go2": { + "fontCharacter": "\\E039", + "fontColor": "#519aba" + }, + "_godot_light": { + "fontCharacter": "\\E03A", + "fontColor": "#498ba7" + }, + "_godot": { + "fontCharacter": "\\E03A", + "fontColor": "#519aba" + }, + "_godot_1_light": { + "fontCharacter": "\\E03A", + "fontColor": "#b8383d" + }, + "_godot_1": { + "fontCharacter": "\\E03A", + "fontColor": "#cc3e44" + }, + "_godot_2_light": { + "fontCharacter": "\\E03A", + "fontColor": "#b7b73b" + }, + "_godot_2": { + "fontCharacter": "\\E03A", + "fontColor": "#cbcb41" + }, + "_godot_3_light": { + "fontCharacter": "\\E03A", + "fontColor": "#9068b0" + }, + "_godot_3": { + "fontCharacter": "\\E03A", + "fontColor": "#a074c4" + }, + "_gradle_light": { + "fontCharacter": "\\E03B", + "fontColor": "#498ba7" + }, + "_gradle": { + "fontCharacter": "\\E03B", + "fontColor": "#519aba" + }, + "_grails_light": { + "fontCharacter": "\\E03C", + "fontColor": "#7fae42" + }, + "_grails": { + "fontCharacter": "\\E03C", + "fontColor": "#8dc149" + }, + "_graphql_light": { + "fontCharacter": "\\E03D", + "fontColor": "#dd4b78" + }, + "_graphql": { + "fontCharacter": "\\E03D", + "fontColor": "#f55385" + }, + "_grunt_light": { + "fontCharacter": "\\E03E", + "fontColor": "#cc6d2e" + }, + "_grunt": { + "fontCharacter": "\\E03E", + "fontColor": "#e37933" + }, + "_gulp_light": { + "fontCharacter": "\\E03F", + "fontColor": "#b8383d" + }, + "_gulp": { + "fontCharacter": "\\E03F", + "fontColor": "#cc3e44" + }, + "_hacklang_light": { + "fontCharacter": "\\E040", + "fontColor": "#cc6d2e" + }, + "_hacklang": { + "fontCharacter": "\\E040", + "fontColor": "#e37933" + }, + "_haml_light": { + "fontCharacter": "\\E041", + "fontColor": "#b8383d" + }, + "_haml": { + "fontCharacter": "\\E041", + "fontColor": "#cc3e44" + }, + "_happenings_light": { + "fontCharacter": "\\E042", + "fontColor": "#498ba7" + }, + "_happenings": { + "fontCharacter": "\\E042", + "fontColor": "#519aba" + }, + "_haskell_light": { + "fontCharacter": "\\E043", + "fontColor": "#9068b0" + }, + "_haskell": { + "fontCharacter": "\\E043", + "fontColor": "#a074c4" + }, + "_haxe_light": { + "fontCharacter": "\\E044", + "fontColor": "#cc6d2e" + }, + "_haxe": { + "fontCharacter": "\\E044", + "fontColor": "#e37933" + }, + "_haxe_1_light": { + "fontCharacter": "\\E044", + "fontColor": "#b7b73b" + }, + "_haxe_1": { + "fontCharacter": "\\E044", + "fontColor": "#cbcb41" + }, + "_haxe_2_light": { + "fontCharacter": "\\E044", + "fontColor": "#498ba7" + }, + "_haxe_2": { + "fontCharacter": "\\E044", + "fontColor": "#519aba" + }, + "_haxe_3_light": { + "fontCharacter": "\\E044", + "fontColor": "#9068b0" + }, + "_haxe_3": { + "fontCharacter": "\\E044", + "fontColor": "#a074c4" + }, + "_heroku_light": { + "fontCharacter": "\\E045", + "fontColor": "#9068b0" + }, + "_heroku": { + "fontCharacter": "\\E045", + "fontColor": "#a074c4" + }, + "_hex_light": { + "fontCharacter": "\\E046", + "fontColor": "#b8383d" + }, + "_hex": { + "fontCharacter": "\\E046", + "fontColor": "#cc3e44" + }, + "_html_light": { + "fontCharacter": "\\E047", + "fontColor": "#498ba7" + }, + "_html": { + "fontCharacter": "\\E047", + "fontColor": "#519aba" + }, + "_html_1_light": { + "fontCharacter": "\\E047", + "fontColor": "#7fae42" + }, + "_html_1": { + "fontCharacter": "\\E047", + "fontColor": "#8dc149" + }, + "_html_2_light": { + "fontCharacter": "\\E047", + "fontColor": "#b7b73b" + }, + "_html_2": { + "fontCharacter": "\\E047", + "fontColor": "#cbcb41" + }, + "_html_3_light": { + "fontCharacter": "\\E047", + "fontColor": "#cc6d2e" + }, + "_html_3": { + "fontCharacter": "\\E047", + "fontColor": "#e37933" + }, + "_html_erb_light": { + "fontCharacter": "\\E048", + "fontColor": "#b8383d" + }, + "_html_erb": { + "fontCharacter": "\\E048", + "fontColor": "#cc3e44" + }, + "_ignored_light": { + "fontCharacter": "\\E049", + "fontColor": "#3b4b52" + }, + "_ignored": { + "fontCharacter": "\\E049", + "fontColor": "#41535b" + }, + "_illustrator_light": { + "fontCharacter": "\\E04A", + "fontColor": "#b7b73b" + }, + "_illustrator": { + "fontCharacter": "\\E04A", + "fontColor": "#cbcb41" + }, + "_image_light": { + "fontCharacter": "\\E04B", + "fontColor": "#9068b0" + }, + "_image": { + "fontCharacter": "\\E04B", + "fontColor": "#a074c4" + }, + "_info_light": { + "fontCharacter": "\\E04C", + "fontColor": "#498ba7" + }, + "_info": { + "fontCharacter": "\\E04C", + "fontColor": "#519aba" + }, + "_ionic_light": { + "fontCharacter": "\\E04D", + "fontColor": "#498ba7" + }, + "_ionic": { + "fontCharacter": "\\E04D", + "fontColor": "#519aba" + }, + "_jade_light": { + "fontCharacter": "\\E04E", + "fontColor": "#b8383d" + }, + "_jade": { + "fontCharacter": "\\E04E", + "fontColor": "#cc3e44" + }, + "_java_light": { + "fontCharacter": "\\E04F", + "fontColor": "#b8383d" + }, + "_java": { + "fontCharacter": "\\E04F", + "fontColor": "#cc3e44" + }, + "_java_1_light": { + "fontCharacter": "\\E04F", + "fontColor": "#498ba7" + }, + "_java_1": { + "fontCharacter": "\\E04F", + "fontColor": "#519aba" + }, + "_javascript_light": { + "fontCharacter": "\\E050", + "fontColor": "#b7b73b" + }, + "_javascript": { + "fontCharacter": "\\E050", + "fontColor": "#cbcb41" + }, + "_javascript_1_light": { + "fontCharacter": "\\E050", + "fontColor": "#cc6d2e" + }, + "_javascript_1": { + "fontCharacter": "\\E050", + "fontColor": "#e37933" + }, + "_javascript_2_light": { + "fontCharacter": "\\E050", + "fontColor": "#498ba7" + }, + "_javascript_2": { + "fontCharacter": "\\E050", + "fontColor": "#519aba" + }, + "_jenkins_light": { + "fontCharacter": "\\E051", + "fontColor": "#b8383d" + }, + "_jenkins": { + "fontCharacter": "\\E051", + "fontColor": "#cc3e44" + }, + "_jinja_light": { + "fontCharacter": "\\E052", + "fontColor": "#b8383d" + }, + "_jinja": { + "fontCharacter": "\\E052", + "fontColor": "#cc3e44" + }, + "_json_light": { + "fontCharacter": "\\E053", + "fontColor": "#b7b73b" + }, + "_json": { + "fontCharacter": "\\E053", + "fontColor": "#cbcb41" + }, + "_json_1_light": { + "fontCharacter": "\\E053", + "fontColor": "#7fae42" + }, + "_json_1": { + "fontCharacter": "\\E053", + "fontColor": "#8dc149" + }, + "_julia_light": { + "fontCharacter": "\\E055", + "fontColor": "#9068b0" + }, + "_julia": { + "fontCharacter": "\\E055", + "fontColor": "#a074c4" + }, + "_karma_light": { + "fontCharacter": "\\E056", + "fontColor": "#7fae42" + }, + "_karma": { + "fontCharacter": "\\E056", + "fontColor": "#8dc149" + }, + "_kotlin_light": { + "fontCharacter": "\\E057", + "fontColor": "#cc6d2e" + }, + "_kotlin": { + "fontCharacter": "\\E057", + "fontColor": "#e37933" + }, + "_less_light": { + "fontCharacter": "\\E058", + "fontColor": "#498ba7" + }, + "_less": { + "fontCharacter": "\\E058", + "fontColor": "#519aba" + }, + "_license_light": { + "fontCharacter": "\\E059", + "fontColor": "#b7b73b" + }, + "_license": { + "fontCharacter": "\\E059", + "fontColor": "#cbcb41" + }, + "_license_1_light": { + "fontCharacter": "\\E059", + "fontColor": "#cc6d2e" + }, + "_license_1": { + "fontCharacter": "\\E059", + "fontColor": "#e37933" + }, + "_license_2_light": { + "fontCharacter": "\\E059", + "fontColor": "#b8383d" + }, + "_license_2": { + "fontCharacter": "\\E059", + "fontColor": "#cc3e44" + }, + "_liquid_light": { + "fontCharacter": "\\E05A", + "fontColor": "#7fae42" + }, + "_liquid": { + "fontCharacter": "\\E05A", + "fontColor": "#8dc149" + }, + "_livescript_light": { + "fontCharacter": "\\E05B", + "fontColor": "#498ba7" + }, + "_livescript": { + "fontCharacter": "\\E05B", + "fontColor": "#519aba" + }, + "_lock_light": { + "fontCharacter": "\\E05C", + "fontColor": "#7fae42" + }, + "_lock": { + "fontCharacter": "\\E05C", + "fontColor": "#8dc149" + }, + "_lua_light": { + "fontCharacter": "\\E05D", + "fontColor": "#498ba7" + }, + "_lua": { + "fontCharacter": "\\E05D", + "fontColor": "#519aba" + }, + "_makefile_light": { + "fontCharacter": "\\E05E", + "fontColor": "#cc6d2e" + }, + "_makefile": { + "fontCharacter": "\\E05E", + "fontColor": "#e37933" + }, + "_makefile_1_light": { + "fontCharacter": "\\E05E", + "fontColor": "#9068b0" + }, + "_makefile_1": { + "fontCharacter": "\\E05E", + "fontColor": "#a074c4" + }, + "_makefile_2_light": { + "fontCharacter": "\\E05E", + "fontColor": "#627379" + }, + "_makefile_2": { + "fontCharacter": "\\E05E", + "fontColor": "#6d8086" + }, + "_makefile_3_light": { + "fontCharacter": "\\E05E", + "fontColor": "#498ba7" + }, + "_makefile_3": { + "fontCharacter": "\\E05E", + "fontColor": "#519aba" + }, + "_markdown_light": { + "fontCharacter": "\\E05F", + "fontColor": "#498ba7" + }, + "_markdown": { + "fontCharacter": "\\E05F", + "fontColor": "#519aba" + }, + "_maven_light": { + "fontCharacter": "\\E060", + "fontColor": "#b8383d" + }, + "_maven": { + "fontCharacter": "\\E060", + "fontColor": "#cc3e44" + }, + "_mdo_light": { + "fontCharacter": "\\E061", + "fontColor": "#b8383d" + }, + "_mdo": { + "fontCharacter": "\\E061", + "fontColor": "#cc3e44" + }, + "_mustache_light": { + "fontCharacter": "\\E062", + "fontColor": "#cc6d2e" + }, + "_mustache": { + "fontCharacter": "\\E062", + "fontColor": "#e37933" + }, + "_nim_light": { + "fontCharacter": "\\E064", + "fontColor": "#b7b73b" + }, + "_nim": { + "fontCharacter": "\\E064", + "fontColor": "#cbcb41" + }, + "_notebook_light": { + "fontCharacter": "\\E065", + "fontColor": "#498ba7" + }, + "_notebook": { + "fontCharacter": "\\E065", + "fontColor": "#519aba" + }, + "_npm_light": { + "fontCharacter": "\\E066", + "fontColor": "#3b4b52" + }, + "_npm": { + "fontCharacter": "\\E066", + "fontColor": "#41535b" + }, + "_npm_1_light": { + "fontCharacter": "\\E066", + "fontColor": "#b8383d" + }, + "_npm_1": { + "fontCharacter": "\\E066", + "fontColor": "#cc3e44" + }, + "_npm_ignored_light": { + "fontCharacter": "\\E067", + "fontColor": "#3b4b52" + }, + "_npm_ignored": { + "fontCharacter": "\\E067", + "fontColor": "#41535b" + }, + "_nunjucks_light": { + "fontCharacter": "\\E068", + "fontColor": "#7fae42" + }, + "_nunjucks": { + "fontCharacter": "\\E068", + "fontColor": "#8dc149" + }, + "_ocaml_light": { + "fontCharacter": "\\E069", + "fontColor": "#cc6d2e" + }, + "_ocaml": { + "fontCharacter": "\\E069", + "fontColor": "#e37933" + }, + "_odata_light": { + "fontCharacter": "\\E06A", + "fontColor": "#cc6d2e" + }, + "_odata": { + "fontCharacter": "\\E06A", + "fontColor": "#e37933" + }, + "_pddl_light": { + "fontCharacter": "\\E06B", + "fontColor": "#9068b0" + }, + "_pddl": { + "fontCharacter": "\\E06B", + "fontColor": "#a074c4" + }, + "_pdf_light": { + "fontCharacter": "\\E06C", + "fontColor": "#b8383d" + }, + "_pdf": { + "fontCharacter": "\\E06C", + "fontColor": "#cc3e44" + }, + "_perl_light": { + "fontCharacter": "\\E06D", + "fontColor": "#498ba7" + }, + "_perl": { + "fontCharacter": "\\E06D", + "fontColor": "#519aba" + }, + "_photoshop_light": { + "fontCharacter": "\\E06E", + "fontColor": "#498ba7" + }, + "_photoshop": { + "fontCharacter": "\\E06E", + "fontColor": "#519aba" + }, + "_php_light": { + "fontCharacter": "\\E06F", + "fontColor": "#9068b0" + }, + "_php": { + "fontCharacter": "\\E06F", + "fontColor": "#a074c4" + }, + "_pipeline_light": { + "fontCharacter": "\\E070", + "fontColor": "#cc6d2e" + }, + "_pipeline": { + "fontCharacter": "\\E070", + "fontColor": "#e37933" + }, + "_plan_light": { + "fontCharacter": "\\E071", + "fontColor": "#7fae42" + }, + "_plan": { + "fontCharacter": "\\E071", + "fontColor": "#8dc149" + }, + "_platformio_light": { + "fontCharacter": "\\E072", + "fontColor": "#cc6d2e" + }, + "_platformio": { + "fontCharacter": "\\E072", + "fontColor": "#e37933" + }, + "_powershell_light": { + "fontCharacter": "\\E073", + "fontColor": "#498ba7" + }, + "_powershell": { + "fontCharacter": "\\E073", + "fontColor": "#519aba" + }, + "_prisma_light": { + "fontCharacter": "\\E074", + "fontColor": "#498ba7" + }, + "_prisma": { + "fontCharacter": "\\E074", + "fontColor": "#519aba" + }, + "_prolog_light": { + "fontCharacter": "\\E076", + "fontColor": "#cc6d2e" + }, + "_prolog": { + "fontCharacter": "\\E076", + "fontColor": "#e37933" + }, + "_pug_light": { + "fontCharacter": "\\E077", + "fontColor": "#b8383d" + }, + "_pug": { + "fontCharacter": "\\E077", + "fontColor": "#cc3e44" + }, + "_puppet_light": { + "fontCharacter": "\\E078", + "fontColor": "#b7b73b" + }, + "_puppet": { + "fontCharacter": "\\E078", + "fontColor": "#cbcb41" + }, + "_purescript_light": { + "fontCharacter": "\\E079", + "fontColor": "#bfc2c1" + }, + "_purescript": { + "fontCharacter": "\\E079", + "fontColor": "#d4d7d6" + }, + "_python_light": { + "fontCharacter": "\\E07A", + "fontColor": "#498ba7" + }, + "_python": { + "fontCharacter": "\\E07A", + "fontColor": "#519aba" + }, + "_react_light": { + "fontCharacter": "\\E07D", + "fontColor": "#498ba7" + }, + "_react": { + "fontCharacter": "\\E07D", + "fontColor": "#519aba" + }, + "_react_1_light": { + "fontCharacter": "\\E07D", + "fontColor": "#cc6d2e" + }, + "_react_1": { + "fontCharacter": "\\E07D", + "fontColor": "#e37933" + }, + "_reasonml_light": { + "fontCharacter": "\\E07E", + "fontColor": "#b8383d" + }, + "_reasonml": { + "fontCharacter": "\\E07E", + "fontColor": "#cc3e44" + }, + "_rescript_light": { + "fontCharacter": "\\E07F", + "fontColor": "#b8383d" + }, + "_rescript": { + "fontCharacter": "\\E07F", + "fontColor": "#cc3e44" + }, + "_rescript_1_light": { + "fontCharacter": "\\E07F", + "fontColor": "#dd4b78" + }, + "_rescript_1": { + "fontCharacter": "\\E07F", + "fontColor": "#f55385" + }, + "_rollup_light": { + "fontCharacter": "\\E080", + "fontColor": "#b8383d" + }, + "_rollup": { + "fontCharacter": "\\E080", + "fontColor": "#cc3e44" + }, + "_ruby_light": { + "fontCharacter": "\\E081", + "fontColor": "#b8383d" + }, + "_ruby": { + "fontCharacter": "\\E081", + "fontColor": "#cc3e44" + }, + "_rust_light": { + "fontCharacter": "\\E082", + "fontColor": "#627379" + }, + "_rust": { + "fontCharacter": "\\E082", + "fontColor": "#6d8086" + }, + "_salesforce_light": { + "fontCharacter": "\\E083", + "fontColor": "#498ba7" + }, + "_salesforce": { + "fontCharacter": "\\E083", + "fontColor": "#519aba" + }, + "_sass_light": { + "fontCharacter": "\\E084", + "fontColor": "#dd4b78" + }, + "_sass": { + "fontCharacter": "\\E084", + "fontColor": "#f55385" + }, + "_sbt_light": { + "fontCharacter": "\\E085", + "fontColor": "#498ba7" + }, + "_sbt": { + "fontCharacter": "\\E085", + "fontColor": "#519aba" + }, + "_scala_light": { + "fontCharacter": "\\E086", + "fontColor": "#b8383d" + }, + "_scala": { + "fontCharacter": "\\E086", + "fontColor": "#cc3e44" + }, + "_shell_light": { + "fontCharacter": "\\E089", + "fontColor": "#7fae42" + }, + "_shell": { + "fontCharacter": "\\E089", + "fontColor": "#8dc149" + }, + "_slim_light": { + "fontCharacter": "\\E08A", + "fontColor": "#cc6d2e" + }, + "_slim": { + "fontCharacter": "\\E08A", + "fontColor": "#e37933" + }, + "_smarty_light": { + "fontCharacter": "\\E08B", + "fontColor": "#b7b73b" + }, + "_smarty": { + "fontCharacter": "\\E08B", + "fontColor": "#cbcb41" + }, + "_spring_light": { + "fontCharacter": "\\E08C", + "fontColor": "#7fae42" + }, + "_spring": { + "fontCharacter": "\\E08C", + "fontColor": "#8dc149" + }, + "_stylelint_light": { + "fontCharacter": "\\E08D", + "fontColor": "#bfc2c1" + }, + "_stylelint": { + "fontCharacter": "\\E08D", + "fontColor": "#d4d7d6" + }, + "_stylelint_1_light": { + "fontCharacter": "\\E08D", + "fontColor": "#455155" + }, + "_stylelint_1": { + "fontCharacter": "\\E08D", + "fontColor": "#4d5a5e" + }, + "_stylus_light": { + "fontCharacter": "\\E08E", + "fontColor": "#7fae42" + }, + "_stylus": { + "fontCharacter": "\\E08E", + "fontColor": "#8dc149" + }, + "_sublime_light": { + "fontCharacter": "\\E08F", + "fontColor": "#cc6d2e" + }, + "_sublime": { + "fontCharacter": "\\E08F", + "fontColor": "#e37933" + }, + "_svelte_light": { + "fontCharacter": "\\E090", + "fontColor": "#b8383d" + }, + "_svelte": { + "fontCharacter": "\\E090", + "fontColor": "#cc3e44" + }, + "_svg_light": { + "fontCharacter": "\\E091", + "fontColor": "#9068b0" + }, + "_svg": { + "fontCharacter": "\\E091", + "fontColor": "#a074c4" + }, + "_svg_1_light": { + "fontCharacter": "\\E091", + "fontColor": "#498ba7" + }, + "_svg_1": { + "fontCharacter": "\\E091", + "fontColor": "#519aba" + }, + "_swift_light": { + "fontCharacter": "\\E092", + "fontColor": "#cc6d2e" + }, + "_swift": { + "fontCharacter": "\\E092", + "fontColor": "#e37933" + }, + "_terraform_light": { + "fontCharacter": "\\E093", + "fontColor": "#9068b0" + }, + "_terraform": { + "fontCharacter": "\\E093", + "fontColor": "#a074c4" + }, + "_tex_light": { + "fontCharacter": "\\E094", + "fontColor": "#498ba7" + }, + "_tex": { + "fontCharacter": "\\E094", + "fontColor": "#519aba" + }, + "_tex_1_light": { + "fontCharacter": "\\E094", + "fontColor": "#b7b73b" + }, + "_tex_1": { + "fontCharacter": "\\E094", + "fontColor": "#cbcb41" + }, + "_tex_2_light": { + "fontCharacter": "\\E094", + "fontColor": "#cc6d2e" + }, + "_tex_2": { + "fontCharacter": "\\E094", + "fontColor": "#e37933" + }, + "_tex_3_light": { + "fontCharacter": "\\E094", + "fontColor": "#bfc2c1" + }, + "_tex_3": { + "fontCharacter": "\\E094", + "fontColor": "#d4d7d6" + }, + "_todo": { + "fontCharacter": "\\E096" + }, + "_tsconfig_light": { + "fontCharacter": "\\E097", + "fontColor": "#498ba7" + }, + "_tsconfig": { + "fontCharacter": "\\E097", + "fontColor": "#519aba" + }, + "_twig_light": { + "fontCharacter": "\\E098", + "fontColor": "#7fae42" + }, + "_twig": { + "fontCharacter": "\\E098", + "fontColor": "#8dc149" + }, + "_typescript_light": { + "fontCharacter": "\\E099", + "fontColor": "#498ba7" + }, + "_typescript": { + "fontCharacter": "\\E099", + "fontColor": "#519aba" + }, + "_typescript_1_light": { + "fontCharacter": "\\E099", + "fontColor": "#cc6d2e" + }, + "_typescript_1": { + "fontCharacter": "\\E099", + "fontColor": "#e37933" + }, + "_vala_light": { + "fontCharacter": "\\E09A", + "fontColor": "#627379" + }, + "_vala": { + "fontCharacter": "\\E09A", + "fontColor": "#6d8086" + }, + "_vba_light": { + "fontCharacter": "\\E09B", + "fontColor": "#9068b0" + }, + "_vba": { + "fontCharacter": "\\E09B", + "fontColor": "#a074c4" + }, + "_vba_1_light": { + "fontCharacter": "\\E09B", + "fontColor": "#7fae42" + }, + "_vba_1": { + "fontCharacter": "\\E09B", + "fontColor": "#8dc149" + }, + "_vba_2_light": { + "fontCharacter": "\\E09B", + "fontColor": "#b7b73b" + }, + "_vba_2": { + "fontCharacter": "\\E09B", + "fontColor": "#cbcb41" + }, + "_video_light": { + "fontCharacter": "\\E09C", + "fontColor": "#dd4b78" + }, + "_video": { + "fontCharacter": "\\E09C", + "fontColor": "#f55385" + }, + "_vite_light": { + "fontCharacter": "\\E09D", + "fontColor": "#b7b73b" + }, + "_vite": { + "fontCharacter": "\\E09D", + "fontColor": "#cbcb41" + }, + "_vue_light": { + "fontCharacter": "\\E09E", + "fontColor": "#7fae42" + }, + "_vue": { + "fontCharacter": "\\E09E", + "fontColor": "#8dc149" + }, + "_wasm_light": { + "fontCharacter": "\\E09F", + "fontColor": "#9068b0" + }, + "_wasm": { + "fontCharacter": "\\E09F", + "fontColor": "#a074c4" + }, + "_wat_light": { + "fontCharacter": "\\E0A0", + "fontColor": "#9068b0" + }, + "_wat": { + "fontCharacter": "\\E0A0", + "fontColor": "#a074c4" + }, + "_webpack_light": { + "fontCharacter": "\\E0A1", + "fontColor": "#498ba7" + }, + "_webpack": { + "fontCharacter": "\\E0A1", + "fontColor": "#519aba" + }, + "_wgt_light": { + "fontCharacter": "\\E0A2", + "fontColor": "#498ba7" + }, + "_wgt": { + "fontCharacter": "\\E0A2", + "fontColor": "#519aba" + }, + "_windows_light": { + "fontCharacter": "\\E0A3", + "fontColor": "#498ba7" + }, + "_windows": { + "fontCharacter": "\\E0A3", + "fontColor": "#519aba" + }, + "_word_light": { + "fontCharacter": "\\E0A4", + "fontColor": "#498ba7" + }, + "_word": { + "fontCharacter": "\\E0A4", + "fontColor": "#519aba" + }, + "_xls_light": { + "fontCharacter": "\\E0A5", + "fontColor": "#7fae42" + }, + "_xls": { + "fontCharacter": "\\E0A5", + "fontColor": "#8dc149" + }, + "_xml_light": { + "fontCharacter": "\\E0A6", + "fontColor": "#cc6d2e" + }, + "_xml": { + "fontCharacter": "\\E0A6", + "fontColor": "#e37933" + }, + "_yarn_light": { + "fontCharacter": "\\E0A7", + "fontColor": "#498ba7" + }, + "_yarn": { + "fontCharacter": "\\E0A7", + "fontColor": "#519aba" + }, + "_yml_light": { + "fontCharacter": "\\E0A8", + "fontColor": "#9068b0" + }, + "_yml": { + "fontCharacter": "\\E0A8", + "fontColor": "#a074c4" + }, + "_zig_light": { + "fontCharacter": "\\E0A9", + "fontColor": "#cc6d2e" + }, + "_zig": { + "fontCharacter": "\\E0A9", + "fontColor": "#e37933" + }, + "_zip_light": { + "fontCharacter": "\\E0AA", + "fontColor": "#b8383d" + }, + "_zip": { + "fontCharacter": "\\E0AA", + "fontColor": "#cc3e44" + }, + "_zip_1_light": { + "fontCharacter": "\\E0AA", + "fontColor": "#627379" + }, + "_zip_1": { + "fontCharacter": "\\E0AA", + "fontColor": "#6d8086" + } + }, + "file": "_default", + "fileExtensions": { + "bsl": "_bsl", + "mdo": "_mdo", + "apex": "_salesforce", + "asm": "_asm", + "s": "_asm", + "bicep": "_bicep", + "bzl": "_bazel", + "bazel": "_bazel", + "build": "_bazel", + "workspace": "_bazel", + "bazelignore": "_bazel", + "bazelversion": "_bazel", + "c": "_c", + "h": "_c_1", + "m": "_c_2", + "cs": "_c-sharp", + "cshtml": "_html", + "aspx": "_html", + "ascx": "_html_1", + "asax": "_html_2", + "master": "_html_2", + "cc": "_cpp", + "cpp": "_cpp", + "cxx": "_cpp", + "c++": "_cpp", + "hh": "_cpp_1", + "hpp": "_cpp_1", + "hxx": "_cpp_1", + "h++": "_cpp_1", + "mm": "_cpp_2", + "clj": "_clojure", + "cljs": "_clojure", + "cljc": "_clojure", + "edn": "_clojure_1", + "cfc": "_coldfusion", + "cfm": "_coldfusion", + "coffee": "_coffee", + "litcoffee": "_coffee", + "config": "_config", + "cfg": "_config", + "conf": "_config", + "cr": "_crystal", + "ecr": "_crystal_embedded", + "slang": "_crystal_embedded", + "cson": "_json", + "css": "_css", + "css.map": "_css", + "sss": "_css", + "csv": "_csv", + "xla": "_xls", + "xlam": "_xls", + "xls": "_xls", + "xlsb": "_xls", + "xlsm": "_xls", + "xlsx": "_xls", + "xlt": "_xls", + "xltm": "_xls", + "cu": "_cu", + "cuh": "_cu_1", + "hu": "_cu_1", + "cake": "_cake", + "ctp": "_cake_php", + "d": "_d", + "doc": "_word", + "docx": "_word", + "dot": "_word", + "dotm": "_word", + "ejs": "_ejs", + "ex": "_elixir", + "exs": "_elixir_script", + "elm": "_elm", + "ico": "_favicon", + "fs": "_f-sharp", + "fsx": "_f-sharp", + "gitignore": "_git", + "gitconfig": "_git", + "gitkeep": "_git", + "gitattributes": "_git", + "gitmodules": "_git", + "go": "_go2", + "slide": "_go", + "article": "_go", + "gd": "_godot", + "godot": "_godot_1", + "tres": "_godot_2", + "tscn": "_godot_3", + "gradle": "_gradle", + "groovy": "_grails", + "gsp": "_grails", + "gql": "_graphql", + "graphql": "_graphql", + "graphqls": "_graphql", + "hack": "_hacklang", + "haml": "_haml", + "handlebars": "_mustache", + "hbs": "_mustache", + "hjs": "_mustache", + "hs": "_haskell", + "lhs": "_haskell", + "hx": "_haxe", + "hxs": "_haxe_1", + "hxp": "_haxe_2", + "hxml": "_haxe_3", + "html": "_html_3", + "jade": "_jade", + "java": "_java", + "class": "_java_1", + "classpath": "_java", + "properties": "_java", + "js": "_javascript", + "js.map": "_javascript", + "cjs": "_javascript", + "cjs.map": "_javascript", + "mjs": "_javascript", + "mjs.map": "_javascript", + "spec.js": "_javascript_1", + "spec.cjs": "_javascript_1", + "spec.mjs": "_javascript_1", + "test.js": "_javascript_1", + "test.cjs": "_javascript_1", + "test.mjs": "_javascript_1", + "es": "_javascript", + "es5": "_javascript", + "es6": "_javascript", + "es7": "_javascript", + "jinja": "_jinja", + "jinja2": "_jinja", + "json": "_json", + "jl": "_julia", + "kt": "_kotlin", + "kts": "_kotlin", + "dart": "_dart", + "less": "_less", + "liquid": "_liquid", + "ls": "_livescript", + "lua": "_lua", + "markdown": "_markdown", + "md": "_markdown", + "argdown": "_argdown", + "ad": "_argdown", + "mustache": "_mustache", + "stache": "_mustache", + "nim": "_nim", + "nims": "_nim", + "github-issues": "_github", + "ipynb": "_notebook", + "njk": "_nunjucks", + "nunjucks": "_nunjucks", + "nunjs": "_nunjucks", + "nunj": "_nunjucks", + "njs": "_nunjucks", + "nj": "_nunjucks", + "npm-debug.log": "_npm", + "npmignore": "_npm_1", + "npmrc": "_npm_1", + "ml": "_ocaml", + "mli": "_ocaml", + "cmx": "_ocaml", + "cmxa": "_ocaml", + "odata": "_odata", + "pl": "_perl", + "php": "_php", + "php.inc": "_php", + "pipeline": "_pipeline", + "pddl": "_pddl", + "plan": "_plan", + "happenings": "_happenings", + "ps1": "_powershell", + "psd1": "_powershell", + "psm1": "_powershell", + "prisma": "_prisma", + "pug": "_pug", + "pp": "_puppet", + "epp": "_puppet", + "purs": "_purescript", + "py": "_python", + "jsx": "_react", + "spec.jsx": "_react_1", + "test.jsx": "_react_1", + "cjsx": "_react", + "tsx": "_react", + "spec.tsx": "_react_1", + "test.tsx": "_react_1", + "re": "_reasonml", + "res": "_rescript", + "resi": "_rescript_1", + "r": "_R", + "rmd": "_R", + "rb": "_ruby", + "erb": "_html_erb", + "erb.html": "_html_erb", + "html.erb": "_html_erb", + "rs": "_rust", + "sass": "_sass", + "scss": "_sass", + "springbeans": "_spring", + "slim": "_slim", + "smarty.tpl": "_smarty", + "tpl": "_smarty", + "sbt": "_sbt", + "scala": "_scala", + "sol": "_ethereum", + "styl": "_stylus", + "svelte": "_svelte", + "swift": "_swift", + "sql": "_db", + "soql": "_db_1", + "tf": "_terraform", + "tf.json": "_terraform", + "tfvars": "_terraform", + "tfvars.json": "_terraform", + "tex": "_tex", + "sty": "_tex_1", + "dtx": "_tex_2", + "ins": "_tex_3", + "toml": "_config", + "twig": "_twig", + "ts": "_typescript", + "spec.ts": "_typescript_1", + "test.ts": "_typescript_1", + "vala": "_vala", + "vapi": "_vala", + "bas": "_vba", + "cls": "_vba_1", + "frm": "_vba_2", + "component": "_html_3", + "vue": "_vue", + "wasm": "_wasm", + "wat": "_wat", + "xml": "_xml", + "yml": "_yml", + "yaml": "_yml", + "pro": "_prolog", + "zig": "_zig", + "jar": "_zip", + "zip": "_zip_1", + "wgt": "_wgt", + "ai": "_illustrator", + "psd": "_photoshop", + "pdf": "_pdf", + "eot": "_font", + "ttf": "_font", + "woff": "_font", + "woff2": "_font", + "otf": "_font", + "avif": "_image", + "gif": "_image", + "jpg": "_image", + "jpeg": "_image", + "png": "_image", + "pxm": "_image", + "svg": "_svg", + "svgx": "_image", + "tiff": "_image", + "webp": "_image", + "sublime-project": "_sublime", + "sublime-workspace": "_sublime", + "code-search": "_code-search", + "sh": "_shell", + "zsh": "_shell", + "fish": "_shell", + "zshrc": "_shell", + "bashrc": "_shell", + "mov": "_video", + "ogv": "_video", + "webm": "_video", + "avi": "_video", + "mpg": "_video", + "mp4": "_video", + "mp3": "_audio", + "ogg": "_audio", + "wav": "_audio", + "flac": "_audio", + "3ds": "_svg_1", + "3dm": "_svg_1", + "stl": "_svg_1", + "obj": "_svg_1", + "dae": "_svg_1", + "bat": "_windows", + "cmd": "_windows", + "babelrc": "_babel", + "babelrc.js": "_babel", + "babelrc.cjs": "_babel", + "bazelrc": "_bazel_1", + "bowerrc": "_bower", + "dockerignore": "_docker_1", + "codeclimate.yml": "_code-climate", + "eslintrc": "_eslint", + "eslintrc.js": "_eslint", + "eslintrc.cjs": "_eslint", + "eslintrc.yaml": "_eslint", + "eslintrc.yml": "_eslint", + "eslintrc.json": "_eslint", + "eslintignore": "_eslint_1", + "firebaserc": "_firebase", + "gitlab-ci.yml": "_gitlab", + "jshintrc": "_javascript_2", + "jscsrc": "_javascript_2", + "stylelintrc": "_stylelint", + "stylelintrc.json": "_stylelint", + "stylelintrc.yaml": "_stylelint", + "stylelintrc.yml": "_stylelint", + "stylelintrc.js": "_stylelint", + "stylelintignore": "_stylelint_1", + "direnv": "_config", + "env": "_config", + "static": "_config", + "editorconfig": "_config", + "slugignore": "_config", + "tmp": "_clock_1", + "htaccess": "_config", + "key": "_lock", + "cert": "_lock", + "cer": "_lock", + "crt": "_lock", + "pem": "_lock", + "ds_store": "_ignored" + }, + "fileNames": { + "mix": "_hex", + "commit_editmsg": "_git", + "merge_msg": "_git", + "karma.conf.js": "_karma", + "karma.conf.cjs": "_karma", + "karma.conf.mjs": "_karma", + "karma.conf.coffee": "_karma", + "readme.md": "_info", + "readme.txt": "_info", + "readme": "_info", + "changelog.md": "_clock", + "changelog.txt": "_clock", + "changelog": "_clock", + "changes.md": "_clock", + "changes.txt": "_clock", + "changes": "_clock", + "version.md": "_clock", + "version.txt": "_clock", + "version": "_clock", + "mvnw": "_maven", + "pom.xml": "_maven", + "gemfile": "_ruby", + "tsconfig.json": "_tsconfig", + "vite.config.js": "_vite", + "vite.config.ts": "_vite", + "vite.config.mjs": "_vite", + "vite.config.mts": "_vite", + "vite.config.cjs": "_vite", + "vite.config.cts": "_vite", + "swagger.json": "_json_1", + "swagger.yml": "_json_1", + "swagger.yaml": "_json_1", + "mime.types": "_config", + "jenkinsfile": "_jenkins", + "babel.config.js": "_babel", + "babel.config.json": "_babel", + "babel.config.cjs": "_babel", + "build": "_bazel", + "build.bazel": "_bazel", + "workspace": "_bazel", + "workspace.bazel": "_bazel", + "bower.json": "_bower", + "dockerfile": "_docker", + "docker-healthcheck": "_docker_2", + "docker-compose.yml": "_docker_3", + "docker-compose.yaml": "_docker_3", + "docker-compose.override.yml": "_docker_3", + "docker-compose.override.yaml": "_docker_3", + "eslint.config.js": "_eslint", + "firebase.json": "_firebase", + "geckodriver": "_firefox", + "gruntfile.js": "_grunt", + "gruntfile.babel.js": "_grunt", + "gruntfile.coffee": "_grunt", + "gulpfile": "_gulp", + "gulpfile.js": "_gulp", + "ionic.config.json": "_ionic", + "ionic.project": "_ionic", + "platformio.ini": "_platformio", + "rollup.config.js": "_rollup", + "sass-lint.yml": "_sass", + "stylelint.config.js": "_stylelint", + "stylelint.config.cjs": "_stylelint", + "stylelint.config.mjs": "_stylelint", + "yarn.clean": "_yarn", + "yarn.lock": "_yarn", + "webpack.config.js": "_webpack", + "webpack.config.cjs": "_webpack", + "webpack.config.mjs": "_webpack", + "webpack.config.ts": "_webpack", + "webpack.config.build.js": "_webpack", + "webpack.config.build.cjs": "_webpack", + "webpack.config.build.mjs": "_webpack", + "webpack.config.build.ts": "_webpack", + "webpack.common.js": "_webpack", + "webpack.common.cjs": "_webpack", + "webpack.common.mjs": "_webpack", + "webpack.common.ts": "_webpack", + "webpack.dev.js": "_webpack", + "webpack.dev.cjs": "_webpack", + "webpack.dev.mjs": "_webpack", + "webpack.dev.ts": "_webpack", + "webpack.prod.js": "_webpack", + "webpack.prod.cjs": "_webpack", + "webpack.prod.mjs": "_webpack", + "webpack.prod.ts": "_webpack", + "license": "_license", + "licence": "_license", + "license.txt": "_license", + "licence.txt": "_license", + "license.md": "_license", + "licence.md": "_license", + "copying": "_license", + "copying.txt": "_license", + "copying.md": "_license", + "compiling": "_license_1", + "compiling.txt": "_license_1", + "compiling.md": "_license_1", + "contributing": "_license_2", + "contributing.txt": "_license_2", + "contributing.md": "_license_2", + "makefile": "_makefile", + "qmakefile": "_makefile_1", + "omakefile": "_makefile_2", + "cmakelists.txt": "_makefile_3", + "procfile": "_heroku", + "todo": "_todo", + "todo.txt": "_todo", + "todo.md": "_todo", + "npm-debug.log": "_npm_ignored" + }, + "languageIds": { + "argdown": "_argdown", + "bicep": "_bicep", + "elixir": "_elixir", + "elm": "_elm", + "erb": "_html_erb", + "github-issues": "_github", + "gradle": "_gradle", + "godot": "_godot", + "haml": "_haml", + "haskell": "_haskell", + "haxe": "_haxe", + "jinja": "_jinja", + "kotlin": "_kotlin", + "mustache": "_mustache", + "nunjucks": "_nunjucks", + "ocaml": "_ocaml", + "r": "_R", + "rescript": "_rescript", + "sass": "_sass", + "stylus": "_stylus", + "terraform": "_terraform", + "todo": "_todo", + "vala": "_vala", + "vue": "_vue" + }, + "light": { + "file": "_default_light", + "fileExtensions": { + "bsl": "_bsl_light", + "mdo": "_mdo_light", + "apex": "_salesforce_light", + "asm": "_asm_light", + "s": "_asm_light", + "bicep": "_bicep_light", + "bzl": "_bazel_light", + "bazel": "_bazel_light", + "build": "_bazel_light", + "workspace": "_bazel_light", + "bazelignore": "_bazel_light", + "bazelversion": "_bazel_light", + "c": "_c_light", + "h": "_c_1_light", + "m": "_c_2_light", + "cs": "_c-sharp_light", + "cshtml": "_html_light", + "aspx": "_html_light", + "ascx": "_html_1_light", + "asax": "_html_2_light", + "master": "_html_2_light", + "cc": "_cpp_light", + "cpp": "_cpp_light", + "cxx": "_cpp_light", + "c++": "_cpp_light", + "hh": "_cpp_1_light", + "hpp": "_cpp_1_light", + "hxx": "_cpp_1_light", + "h++": "_cpp_1_light", + "mm": "_cpp_2_light", + "clj": "_clojure_light", + "cljs": "_clojure_light", + "cljc": "_clojure_light", + "edn": "_clojure_1_light", + "cfc": "_coldfusion_light", + "cfm": "_coldfusion_light", + "coffee": "_coffee_light", + "litcoffee": "_coffee_light", + "config": "_config_light", + "cfg": "_config_light", + "conf": "_config_light", + "cr": "_crystal_light", + "ecr": "_crystal_embedded_light", + "slang": "_crystal_embedded_light", + "cson": "_json_light", + "css": "_css_light", + "css.map": "_css_light", + "sss": "_css_light", + "csv": "_csv_light", + "xla": "_xls_light", + "xlam": "_xls_light", + "xls": "_xls_light", + "xlsb": "_xls_light", + "xlsm": "_xls_light", + "xlsx": "_xls_light", + "xlt": "_xls_light", + "xltm": "_xls_light", + "cu": "_cu_light", + "cuh": "_cu_1_light", + "hu": "_cu_1_light", + "cake": "_cake_light", + "ctp": "_cake_php_light", + "d": "_d_light", + "doc": "_word_light", + "docx": "_word_light", + "dot": "_word_light", + "dotm": "_word_light", + "ejs": "_ejs_light", + "ex": "_elixir_light", + "exs": "_elixir_script_light", + "elm": "_elm_light", + "ico": "_favicon_light", + "fs": "_f-sharp_light", + "fsx": "_f-sharp_light", + "gitignore": "_git_light", + "gitconfig": "_git_light", + "gitkeep": "_git_light", + "gitattributes": "_git_light", + "gitmodules": "_git_light", + "go": "_go2_light", + "slide": "_go_light", + "article": "_go_light", + "gd": "_godot_light", + "godot": "_godot_1_light", + "tres": "_godot_2_light", + "tscn": "_godot_3_light", + "gradle": "_gradle_light", + "groovy": "_grails_light", + "gsp": "_grails_light", + "gql": "_graphql_light", + "graphql": "_graphql_light", + "graphqls": "_graphql_light", + "hack": "_hacklang_light", + "haml": "_haml_light", + "handlebars": "_mustache_light", + "hbs": "_mustache_light", + "hjs": "_mustache_light", + "hs": "_haskell_light", + "lhs": "_haskell_light", + "hx": "_haxe_light", + "hxs": "_haxe_1_light", + "hxp": "_haxe_2_light", + "hxml": "_haxe_3_light", + "html": "_html_3_light", + "jade": "_jade_light", + "java": "_java_light", + "class": "_java_1_light", + "classpath": "_java_light", + "properties": "_java_light", + "js": "_javascript_light", + "js.map": "_javascript_light", + "cjs": "_javascript_light", + "cjs.map": "_javascript_light", + "mjs": "_javascript_light", + "mjs.map": "_javascript_light", + "spec.js": "_javascript_1_light", + "spec.cjs": "_javascript_1_light", + "spec.mjs": "_javascript_1_light", + "test.js": "_javascript_1_light", + "test.cjs": "_javascript_1_light", + "test.mjs": "_javascript_1_light", + "es": "_javascript_light", + "es5": "_javascript_light", + "es6": "_javascript_light", + "es7": "_javascript_light", + "jinja": "_jinja_light", + "jinja2": "_jinja_light", + "json": "_json_light", + "jl": "_julia_light", + "kt": "_kotlin_light", + "kts": "_kotlin_light", + "dart": "_dart_light", + "less": "_less_light", + "liquid": "_liquid_light", + "ls": "_livescript_light", + "lua": "_lua_light", + "markdown": "_markdown_light", + "md": "_markdown_light", + "argdown": "_argdown_light", + "ad": "_argdown_light", + "mustache": "_mustache_light", + "stache": "_mustache_light", + "nim": "_nim_light", + "nims": "_nim_light", + "github-issues": "_github_light", + "ipynb": "_notebook_light", + "njk": "_nunjucks_light", + "nunjucks": "_nunjucks_light", + "nunjs": "_nunjucks_light", + "nunj": "_nunjucks_light", + "njs": "_nunjucks_light", + "nj": "_nunjucks_light", + "npm-debug.log": "_npm_light", + "npmignore": "_npm_1_light", + "npmrc": "_npm_1_light", + "ml": "_ocaml_light", + "mli": "_ocaml_light", + "cmx": "_ocaml_light", + "cmxa": "_ocaml_light", + "odata": "_odata_light", + "pl": "_perl_light", + "php": "_php_light", + "php.inc": "_php_light", + "pipeline": "_pipeline_light", + "pddl": "_pddl_light", + "plan": "_plan_light", + "happenings": "_happenings_light", + "ps1": "_powershell_light", + "psd1": "_powershell_light", + "psm1": "_powershell_light", + "prisma": "_prisma_light", + "pug": "_pug_light", + "pp": "_puppet_light", + "epp": "_puppet_light", + "purs": "_purescript_light", + "py": "_python_light", + "jsx": "_react_light", + "spec.jsx": "_react_1_light", + "test.jsx": "_react_1_light", + "cjsx": "_react_light", + "tsx": "_react_light", + "spec.tsx": "_react_1_light", + "test.tsx": "_react_1_light", + "re": "_reasonml_light", + "res": "_rescript_light", + "resi": "_rescript_1_light", + "r": "_R_light", + "rmd": "_R_light", + "rb": "_ruby_light", + "erb": "_html_erb_light", + "erb.html": "_html_erb_light", + "html.erb": "_html_erb_light", + "rs": "_rust_light", + "sass": "_sass_light", + "scss": "_sass_light", + "springbeans": "_spring_light", + "slim": "_slim_light", + "smarty.tpl": "_smarty_light", + "tpl": "_smarty_light", + "sbt": "_sbt_light", + "scala": "_scala_light", + "sol": "_ethereum_light", + "styl": "_stylus_light", + "svelte": "_svelte_light", + "swift": "_swift_light", + "sql": "_db_light", + "soql": "_db_1_light", + "tf": "_terraform_light", + "tf.json": "_terraform_light", + "tfvars": "_terraform_light", + "tfvars.json": "_terraform_light", + "tex": "_tex_light", + "sty": "_tex_1_light", + "dtx": "_tex_2_light", + "ins": "_tex_3_light", + "toml": "_config_light", + "twig": "_twig_light", + "ts": "_typescript_light", + "spec.ts": "_typescript_1_light", + "test.ts": "_typescript_1_light", + "vala": "_vala_light", + "vapi": "_vala_light", + "bas": "_vba_light", + "cls": "_vba_1_light", + "frm": "_vba_2_light", + "component": "_html_3_light", + "vue": "_vue_light", + "wasm": "_wasm_light", + "wat": "_wat_light", + "xml": "_xml_light", + "yml": "_yml_light", + "yaml": "_yml_light", + "pro": "_prolog_light", + "zig": "_zig_light", + "jar": "_zip_light", + "zip": "_zip_1_light", + "wgt": "_wgt_light", + "ai": "_illustrator_light", + "psd": "_photoshop_light", + "pdf": "_pdf_light", + "eot": "_font_light", + "ttf": "_font_light", + "woff": "_font_light", + "woff2": "_font_light", + "otf": "_font_light", + "avif": "_image_light", + "gif": "_image_light", + "jpg": "_image_light", + "jpeg": "_image_light", + "png": "_image_light", + "pxm": "_image_light", + "svg": "_svg_light", + "svgx": "_image_light", + "tiff": "_image_light", + "webp": "_image_light", + "sublime-project": "_sublime_light", + "sublime-workspace": "_sublime_light", + "code-search": "_code-search_light", + "sh": "_shell_light", + "zsh": "_shell_light", + "fish": "_shell_light", + "zshrc": "_shell_light", + "bashrc": "_shell_light", + "mov": "_video_light", + "ogv": "_video_light", + "webm": "_video_light", + "avi": "_video_light", + "mpg": "_video_light", + "mp4": "_video_light", + "mp3": "_audio_light", + "ogg": "_audio_light", + "wav": "_audio_light", + "flac": "_audio_light", + "3ds": "_svg_1_light", + "3dm": "_svg_1_light", + "stl": "_svg_1_light", + "obj": "_svg_1_light", + "dae": "_svg_1_light", + "bat": "_windows_light", + "cmd": "_windows_light", + "babelrc": "_babel_light", + "babelrc.js": "_babel_light", + "babelrc.cjs": "_babel_light", + "bazelrc": "_bazel_1_light", + "bowerrc": "_bower_light", + "dockerignore": "_docker_1_light", + "codeclimate.yml": "_code-climate_light", + "eslintrc": "_eslint_light", + "eslintrc.js": "_eslint_light", + "eslintrc.cjs": "_eslint_light", + "eslintrc.yaml": "_eslint_light", + "eslintrc.yml": "_eslint_light", + "eslintrc.json": "_eslint_light", + "eslintignore": "_eslint_1_light", + "firebaserc": "_firebase_light", + "gitlab-ci.yml": "_gitlab_light", + "jshintrc": "_javascript_2_light", + "jscsrc": "_javascript_2_light", + "stylelintrc": "_stylelint_light", + "stylelintrc.json": "_stylelint_light", + "stylelintrc.yaml": "_stylelint_light", + "stylelintrc.yml": "_stylelint_light", + "stylelintrc.js": "_stylelint_light", + "stylelintignore": "_stylelint_1_light", + "direnv": "_config_light", + "env": "_config_light", + "static": "_config_light", + "editorconfig": "_config_light", + "slugignore": "_config_light", + "tmp": "_clock_1_light", + "htaccess": "_config_light", + "key": "_lock_light", + "cert": "_lock_light", + "cer": "_lock_light", + "crt": "_lock_light", + "pem": "_lock_light", + "ds_store": "_ignored_light" + }, + "languageIds": { + "argdown": "_argdown_light", + "bicep": "_bicep_light", + "elixir": "_elixir_light", + "elm": "_elm_light", + "erb": "_html_erb_light", + "github-issues": "_github_light", + "gradle": "_gradle_light", + "godot": "_godot_light", + "haml": "_haml_light", + "haskell": "_haskell_light", + "haxe": "_haxe_light", + "jinja": "_jinja_light", + "kotlin": "_kotlin_light", + "mustache": "_mustache_light", + "nunjucks": "_nunjucks_light", + "ocaml": "_ocaml_light", + "r": "_R_light", + "rescript": "_rescript_light", + "sass": "_sass_light", + "stylus": "_stylus_light", + "terraform": "_terraform_light", + "vala": "_vala_light", + "vue": "_vue_light" + }, + "fileNames": { + "mix": "_hex_light", + "commit_editmsg": "_git_light", + "merge_msg": "_git_light", + "karma.conf.js": "_karma_light", + "karma.conf.cjs": "_karma_light", + "karma.conf.mjs": "_karma_light", + "karma.conf.coffee": "_karma_light", + "readme.md": "_info_light", + "readme.txt": "_info_light", + "readme": "_info_light", + "changelog.md": "_clock_light", + "changelog.txt": "_clock_light", + "changelog": "_clock_light", + "changes.md": "_clock_light", + "changes.txt": "_clock_light", + "changes": "_clock_light", + "version.md": "_clock_light", + "version.txt": "_clock_light", + "version": "_clock_light", + "mvnw": "_maven_light", + "pom.xml": "_maven_light", + "gemfile": "_ruby_light", + "tsconfig.json": "_tsconfig_light", + "vite.config.js": "_vite_light", + "vite.config.ts": "_vite_light", + "vite.config.mjs": "_vite_light", + "vite.config.mts": "_vite_light", + "vite.config.cjs": "_vite_light", + "vite.config.cts": "_vite_light", + "swagger.json": "_json_1_light", + "swagger.yml": "_json_1_light", + "swagger.yaml": "_json_1_light", + "mime.types": "_config_light", + "jenkinsfile": "_jenkins_light", + "babel.config.js": "_babel_light", + "babel.config.json": "_babel_light", + "babel.config.cjs": "_babel_light", + "build": "_bazel_light", + "build.bazel": "_bazel_light", + "workspace": "_bazel_light", + "workspace.bazel": "_bazel_light", + "bower.json": "_bower_light", + "dockerfile": "_docker_light", + "docker-healthcheck": "_docker_2_light", + "docker-compose.yml": "_docker_3_light", + "docker-compose.yaml": "_docker_3_light", + "docker-compose.override.yml": "_docker_3_light", + "docker-compose.override.yaml": "_docker_3_light", + "eslint.config.js": "_eslint_light", + "firebase.json": "_firebase_light", + "geckodriver": "_firefox_light", + "gruntfile.js": "_grunt_light", + "gruntfile.babel.js": "_grunt_light", + "gruntfile.coffee": "_grunt_light", + "gulpfile": "_gulp_light", + "gulpfile.js": "_gulp_light", + "ionic.config.json": "_ionic_light", + "ionic.project": "_ionic_light", + "platformio.ini": "_platformio_light", + "rollup.config.js": "_rollup_light", + "sass-lint.yml": "_sass_light", + "stylelint.config.js": "_stylelint_light", + "stylelint.config.cjs": "_stylelint_light", + "stylelint.config.mjs": "_stylelint_light", + "yarn.clean": "_yarn_light", + "yarn.lock": "_yarn_light", + "webpack.config.js": "_webpack_light", + "webpack.config.cjs": "_webpack_light", + "webpack.config.mjs": "_webpack_light", + "webpack.config.ts": "_webpack_light", + "webpack.config.build.js": "_webpack_light", + "webpack.config.build.cjs": "_webpack_light", + "webpack.config.build.mjs": "_webpack_light", + "webpack.config.build.ts": "_webpack_light", + "webpack.common.js": "_webpack_light", + "webpack.common.cjs": "_webpack_light", + "webpack.common.mjs": "_webpack_light", + "webpack.common.ts": "_webpack_light", + "webpack.dev.js": "_webpack_light", + "webpack.dev.cjs": "_webpack_light", + "webpack.dev.mjs": "_webpack_light", + "webpack.dev.ts": "_webpack_light", + "webpack.prod.js": "_webpack_light", + "webpack.prod.cjs": "_webpack_light", + "webpack.prod.mjs": "_webpack_light", + "webpack.prod.ts": "_webpack_light", + "license": "_license_light", + "licence": "_license_light", + "license.txt": "_license_light", + "licence.txt": "_license_light", + "license.md": "_license_light", + "licence.md": "_license_light", + "copying": "_license_light", + "copying.txt": "_license_light", + "copying.md": "_license_light", + "compiling": "_license_1_light", + "compiling.txt": "_license_1_light", + "compiling.md": "_license_1_light", + "contributing": "_license_2_light", + "contributing.txt": "_license_2_light", + "contributing.md": "_license_2_light", + "makefile": "_makefile_light", + "qmakefile": "_makefile_1_light", + "omakefile": "_makefile_2_light", + "cmakelists.txt": "_makefile_3_light", + "procfile": "_heroku_light", + "npm-debug.log": "_npm_ignored_light" + } + }, + "version": "https://github.com/DecimalTurn/seti-ui/commit/7a8d51ccb32737be812549fe1c31fae8276b284f" +} \ No newline at end of file diff --git a/icons/theme-seti/package.json b/icons/theme-seti/package.json new file mode 100644 index 0000000..64b61ce --- /dev/null +++ b/icons/theme-seti/package.json @@ -0,0 +1,37 @@ +{ + "name": "vscode-theme-seti-vba", + "private": true, + "version": "1.1.0-vba", + "displayName": "%displayName%", + "description": "%description%", + "publisher": "vscode", + "license": "MIT", + "icon": "icons/seti-circular-128x128.png", + "scripts": { + "update": "node ./build/update-icon-theme.js", + "build": "vsce package" + }, + "engines": { + "vscode": "*" + }, + "categories": [ + "Themes" + ], + "contributes": { + "iconThemes": [ + { + "id": "vs-seti-vba", + "label": "%themeLabel%", + "path": "./icons/vs-seti-icon-theme.json" + } + ] + }, + "repository": { + "type": "git", + "url": "https://github.com/microsoft/vscode.git" + }, + "devDependencies": { + "minimatch": "^3.0.4", + "vsce": "^2.15.0" + } +} diff --git a/icons/theme-seti/package.nls.json b/icons/theme-seti/package.nls.json new file mode 100644 index 0000000..3b95693 --- /dev/null +++ b/icons/theme-seti/package.nls.json @@ -0,0 +1,5 @@ +{ + "displayName": "Seti File Icon Theme + VBA", + "description": "A file icon theme made out of the Seti UI file icons + VBA icons.", + "themeLabel": "Seti + VBA" +} diff --git a/package-lock.json b/package-lock.json index 0feb940..4cc0006 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,32 +1,32 @@ { "name": "vba-lsp", - "version": "1.5.13", + "version": "1.7.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "vba-lsp", - "version": "1.5.13", + "version": "1.7.1", "hasInstallScript": true, "license": "MIT", "dependencies": { "antlr4ng": "^3.0.16", "reflect-metadata": "^0.2.2", - "tsyringe": "^4.9.1", - "typescript-eslint": "^8.29.1" + "tsyringe": "^4.10.0", + "typescript-eslint": "^8.32.1" }, "devDependencies": { "@types/mocha": "^10.0.10", - "@types/node": "^22.14.0", + "@types/node": "^22.15.21", "@typescript-eslint/eslint-plugin": "^8.29.0", "@typescript-eslint/parser": "^8.29.0", "@vscode/test-cli": "^0.0.10", - "@vscode/test-electron": "^2.4.1", + "@vscode/test-electron": "^2.5.2", "antlr4ng-cli": "^2.0.0", - "esbuild": "^0.25.2", - "eslint": "^9.24.0", + "esbuild": "^0.25.4", + "eslint": "^9.27.0", "js-yaml": "^4.1.0", - "mocha": "^11.1.0", + "mocha": "^11.5.0", "npm-run-all": "^4.1.5", "typescript": "^5.8.3", "vscode-tmgrammar-test": "^0.1.3" @@ -43,9 +43,9 @@ "license": "MIT" }, "node_modules/@esbuild/aix-ppc64": { - "version": "0.25.2", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.2.tgz", - "integrity": "sha512-wCIboOL2yXZym2cgm6mlA742s9QeJ8DjGVaL39dLN4rRwrOgOyYSnOaFPhKZGLb2ngj4EyfAFjsNJwPXZvseag==", + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.4.tgz", + "integrity": "sha512-1VCICWypeQKhVbE9oW/sJaAmjLxhVqacdkvPLEjwlttjfwENRSClS8EjBz0KzRyFSCPDIkuXW34Je/vk7zdB7Q==", "cpu": [ "ppc64" ], @@ -60,9 +60,9 @@ } }, "node_modules/@esbuild/android-arm": { - "version": "0.25.2", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.2.tgz", - "integrity": "sha512-NQhH7jFstVY5x8CKbcfa166GoV0EFkaPkCKBQkdPJFvo5u+nGXLEH/ooniLb3QI8Fk58YAx7nsPLozUWfCBOJA==", + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.4.tgz", + "integrity": "sha512-QNdQEps7DfFwE3hXiU4BZeOV68HHzYwGd0Nthhd3uCkkEKK7/R6MTgM0P7H7FAs5pU/DIWsviMmEGxEoxIZ+ZQ==", "cpu": [ "arm" ], @@ -77,9 +77,9 @@ } }, "node_modules/@esbuild/android-arm64": { - "version": "0.25.2", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.2.tgz", - "integrity": "sha512-5ZAX5xOmTligeBaeNEPnPaeEuah53Id2tX4c2CVP3JaROTH+j4fnfHCkr1PjXMd78hMst+TlkfKcW/DlTq0i4w==", + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.4.tgz", + "integrity": "sha512-bBy69pgfhMGtCnwpC/x5QhfxAz/cBgQ9enbtwjf6V9lnPI/hMyT9iWpR1arm0l3kttTr4L0KSLpKmLp/ilKS9A==", "cpu": [ "arm64" ], @@ -94,9 +94,9 @@ } }, "node_modules/@esbuild/android-x64": { - "version": "0.25.2", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.2.tgz", - "integrity": "sha512-Ffcx+nnma8Sge4jzddPHCZVRvIfQ0kMsUsCMcJRHkGJ1cDmhe4SsrYIjLUKn1xpHZybmOqCWwB0zQvsjdEHtkg==", + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.4.tgz", + "integrity": "sha512-TVhdVtQIFuVpIIR282btcGC2oGQoSfZfmBdTip2anCaVYcqWlZXGcdcKIUklfX2wj0JklNYgz39OBqh2cqXvcQ==", "cpu": [ "x64" ], @@ -111,9 +111,9 @@ } }, "node_modules/@esbuild/darwin-arm64": { - "version": "0.25.2", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.2.tgz", - "integrity": "sha512-MpM6LUVTXAzOvN4KbjzU/q5smzryuoNjlriAIx+06RpecwCkL9JpenNzpKd2YMzLJFOdPqBpuub6eVRP5IgiSA==", + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.4.tgz", + "integrity": "sha512-Y1giCfM4nlHDWEfSckMzeWNdQS31BQGs9/rouw6Ub91tkK79aIMTH3q9xHvzH8d0wDru5Ci0kWB8b3up/nl16g==", "cpu": [ "arm64" ], @@ -128,9 +128,9 @@ } }, "node_modules/@esbuild/darwin-x64": { - "version": "0.25.2", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.2.tgz", - "integrity": "sha512-5eRPrTX7wFyuWe8FqEFPG2cU0+butQQVNcT4sVipqjLYQjjh8a8+vUTfgBKM88ObB85ahsnTwF7PSIt6PG+QkA==", + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.4.tgz", + "integrity": "sha512-CJsry8ZGM5VFVeyUYB3cdKpd/H69PYez4eJh1W/t38vzutdjEjtP7hB6eLKBoOdxcAlCtEYHzQ/PJ/oU9I4u0A==", "cpu": [ "x64" ], @@ -145,9 +145,9 @@ } }, "node_modules/@esbuild/freebsd-arm64": { - "version": "0.25.2", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.2.tgz", - "integrity": "sha512-mLwm4vXKiQ2UTSX4+ImyiPdiHjiZhIaE9QvC7sw0tZ6HoNMjYAqQpGyui5VRIi5sGd+uWq940gdCbY3VLvsO1w==", + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.4.tgz", + "integrity": "sha512-yYq+39NlTRzU2XmoPW4l5Ifpl9fqSk0nAJYM/V/WUGPEFfek1epLHJIkTQM6bBs1swApjO5nWgvr843g6TjxuQ==", "cpu": [ "arm64" ], @@ -162,9 +162,9 @@ } }, "node_modules/@esbuild/freebsd-x64": { - "version": "0.25.2", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.2.tgz", - "integrity": "sha512-6qyyn6TjayJSwGpm8J9QYYGQcRgc90nmfdUb0O7pp1s4lTY+9D0H9O02v5JqGApUyiHOtkz6+1hZNvNtEhbwRQ==", + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.4.tgz", + "integrity": "sha512-0FgvOJ6UUMflsHSPLzdfDnnBBVoCDtBTVyn/MrWloUNvq/5SFmh13l3dvgRPkDihRxb77Y17MbqbCAa2strMQQ==", "cpu": [ "x64" ], @@ -179,9 +179,9 @@ } }, "node_modules/@esbuild/linux-arm": { - "version": "0.25.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.2.tgz", - "integrity": "sha512-UHBRgJcmjJv5oeQF8EpTRZs/1knq6loLxTsjc3nxO9eXAPDLcWW55flrMVc97qFPbmZP31ta1AZVUKQzKTzb0g==", + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.4.tgz", + "integrity": "sha512-kro4c0P85GMfFYqW4TWOpvmF8rFShbWGnrLqlzp4X1TNWjRY3JMYUfDCtOxPKOIY8B0WC8HN51hGP4I4hz4AaQ==", "cpu": [ "arm" ], @@ -196,9 +196,9 @@ } }, "node_modules/@esbuild/linux-arm64": { - "version": "0.25.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.2.tgz", - "integrity": "sha512-gq/sjLsOyMT19I8obBISvhoYiZIAaGF8JpeXu1u8yPv8BE5HlWYobmlsfijFIZ9hIVGYkbdFhEqC0NvM4kNO0g==", + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.4.tgz", + "integrity": "sha512-+89UsQTfXdmjIvZS6nUnOOLoXnkUTB9hR5QAeLrQdzOSWZvNSAXAtcRDHWtqAUtAmv7ZM1WPOOeSxDzzzMogiQ==", "cpu": [ "arm64" ], @@ -213,9 +213,9 @@ } }, "node_modules/@esbuild/linux-ia32": { - "version": "0.25.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.2.tgz", - "integrity": "sha512-bBYCv9obgW2cBP+2ZWfjYTU+f5cxRoGGQ5SeDbYdFCAZpYWrfjjfYwvUpP8MlKbP0nwZ5gyOU/0aUzZ5HWPuvQ==", + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.4.tgz", + "integrity": "sha512-yTEjoapy8UP3rv8dB0ip3AfMpRbyhSN3+hY8mo/i4QXFeDxmiYbEKp3ZRjBKcOP862Ua4b1PDfwlvbuwY7hIGQ==", "cpu": [ "ia32" ], @@ -230,9 +230,9 @@ } }, "node_modules/@esbuild/linux-loong64": { - "version": "0.25.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.2.tgz", - "integrity": "sha512-SHNGiKtvnU2dBlM5D8CXRFdd+6etgZ9dXfaPCeJtz+37PIUlixvlIhI23L5khKXs3DIzAn9V8v+qb1TRKrgT5w==", + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.4.tgz", + "integrity": "sha512-NeqqYkrcGzFwi6CGRGNMOjWGGSYOpqwCjS9fvaUlX5s3zwOtn1qwg1s2iE2svBe4Q/YOG1q6875lcAoQK/F4VA==", "cpu": [ "loong64" ], @@ -247,9 +247,9 @@ } }, "node_modules/@esbuild/linux-mips64el": { - "version": "0.25.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.2.tgz", - "integrity": "sha512-hDDRlzE6rPeoj+5fsADqdUZl1OzqDYow4TB4Y/3PlKBD0ph1e6uPHzIQcv2Z65u2K0kpeByIyAjCmjn1hJgG0Q==", + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.4.tgz", + "integrity": "sha512-IcvTlF9dtLrfL/M8WgNI/qJYBENP3ekgsHbYUIzEzq5XJzzVEV/fXY9WFPfEEXmu3ck2qJP8LG/p3Q8f7Zc2Xg==", "cpu": [ "mips64el" ], @@ -264,9 +264,9 @@ } }, "node_modules/@esbuild/linux-ppc64": { - "version": "0.25.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.2.tgz", - "integrity": "sha512-tsHu2RRSWzipmUi9UBDEzc0nLc4HtpZEI5Ba+Omms5456x5WaNuiG3u7xh5AO6sipnJ9r4cRWQB2tUjPyIkc6g==", + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.4.tgz", + "integrity": "sha512-HOy0aLTJTVtoTeGZh4HSXaO6M95qu4k5lJcH4gxv56iaycfz1S8GO/5Jh6X4Y1YiI0h7cRyLi+HixMR+88swag==", "cpu": [ "ppc64" ], @@ -281,9 +281,9 @@ } }, "node_modules/@esbuild/linux-riscv64": { - "version": "0.25.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.2.tgz", - "integrity": "sha512-k4LtpgV7NJQOml/10uPU0s4SAXGnowi5qBSjaLWMojNCUICNu7TshqHLAEbkBdAszL5TabfvQ48kK84hyFzjnw==", + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.4.tgz", + "integrity": "sha512-i8JUDAufpz9jOzo4yIShCTcXzS07vEgWzyX3NH2G7LEFVgrLEhjwL3ajFE4fZI3I4ZgiM7JH3GQ7ReObROvSUA==", "cpu": [ "riscv64" ], @@ -298,9 +298,9 @@ } }, "node_modules/@esbuild/linux-s390x": { - "version": "0.25.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.2.tgz", - "integrity": "sha512-GRa4IshOdvKY7M/rDpRR3gkiTNp34M0eLTaC1a08gNrh4u488aPhuZOCpkF6+2wl3zAN7L7XIpOFBhnaE3/Q8Q==", + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.4.tgz", + "integrity": "sha512-jFnu+6UbLlzIjPQpWCNh5QtrcNfMLjgIavnwPQAfoGx4q17ocOU9MsQ2QVvFxwQoWpZT8DvTLooTvmOQXkO51g==", "cpu": [ "s390x" ], @@ -315,9 +315,9 @@ } }, "node_modules/@esbuild/linux-x64": { - "version": "0.25.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.2.tgz", - "integrity": "sha512-QInHERlqpTTZ4FRB0fROQWXcYRD64lAoiegezDunLpalZMjcUcld3YzZmVJ2H/Cp0wJRZ8Xtjtj0cEHhYc/uUg==", + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.4.tgz", + "integrity": "sha512-6e0cvXwzOnVWJHq+mskP8DNSrKBr1bULBvnFLpc1KY+d+irZSgZ02TGse5FsafKS5jg2e4pbvK6TPXaF/A6+CA==", "cpu": [ "x64" ], @@ -332,9 +332,9 @@ } }, "node_modules/@esbuild/netbsd-arm64": { - "version": "0.25.2", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.2.tgz", - "integrity": "sha512-talAIBoY5M8vHc6EeI2WW9d/CkiO9MQJ0IOWX8hrLhxGbro/vBXJvaQXefW2cP0z0nQVTdQ/eNyGFV1GSKrxfw==", + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.4.tgz", + "integrity": "sha512-vUnkBYxZW4hL/ie91hSqaSNjulOnYXE1VSLusnvHg2u3jewJBz3YzB9+oCw8DABeVqZGg94t9tyZFoHma8gWZQ==", "cpu": [ "arm64" ], @@ -349,9 +349,9 @@ } }, "node_modules/@esbuild/netbsd-x64": { - "version": "0.25.2", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.2.tgz", - "integrity": "sha512-voZT9Z+tpOxrvfKFyfDYPc4DO4rk06qamv1a/fkuzHpiVBMOhpjK+vBmWM8J1eiB3OLSMFYNaOaBNLXGChf5tg==", + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.4.tgz", + "integrity": "sha512-XAg8pIQn5CzhOB8odIcAm42QsOfa98SBeKUdo4xa8OvX8LbMZqEtgeWE9P/Wxt7MlG2QqvjGths+nq48TrUiKw==", "cpu": [ "x64" ], @@ -366,9 +366,9 @@ } }, "node_modules/@esbuild/openbsd-arm64": { - "version": "0.25.2", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.2.tgz", - "integrity": "sha512-dcXYOC6NXOqcykeDlwId9kB6OkPUxOEqU+rkrYVqJbK2hagWOMrsTGsMr8+rW02M+d5Op5NNlgMmjzecaRf7Tg==", + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.4.tgz", + "integrity": "sha512-Ct2WcFEANlFDtp1nVAXSNBPDxyU+j7+tId//iHXU2f/lN5AmO4zLyhDcpR5Cz1r08mVxzt3Jpyt4PmXQ1O6+7A==", "cpu": [ "arm64" ], @@ -383,9 +383,9 @@ } }, "node_modules/@esbuild/openbsd-x64": { - "version": "0.25.2", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.2.tgz", - "integrity": "sha512-t/TkWwahkH0Tsgoq1Ju7QfgGhArkGLkF1uYz8nQS/PPFlXbP5YgRpqQR3ARRiC2iXoLTWFxc6DJMSK10dVXluw==", + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.4.tgz", + "integrity": "sha512-xAGGhyOQ9Otm1Xu8NT1ifGLnA6M3sJxZ6ixylb+vIUVzvvd6GOALpwQrYrtlPouMqd/vSbgehz6HaVk4+7Afhw==", "cpu": [ "x64" ], @@ -400,9 +400,9 @@ } }, "node_modules/@esbuild/sunos-x64": { - "version": "0.25.2", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.2.tgz", - "integrity": "sha512-cfZH1co2+imVdWCjd+D1gf9NjkchVhhdpgb1q5y6Hcv9TP6Zi9ZG/beI3ig8TvwT9lH9dlxLq5MQBBgwuj4xvA==", + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.4.tgz", + "integrity": "sha512-Mw+tzy4pp6wZEK0+Lwr76pWLjrtjmJyUB23tHKqEDP74R3q95luY/bXqXZeYl4NYlvwOqoRKlInQialgCKy67Q==", "cpu": [ "x64" ], @@ -417,9 +417,9 @@ } }, "node_modules/@esbuild/win32-arm64": { - "version": "0.25.2", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.2.tgz", - "integrity": "sha512-7Loyjh+D/Nx/sOTzV8vfbB3GJuHdOQyrOryFdZvPHLf42Tk9ivBU5Aedi7iyX+x6rbn2Mh68T4qq1SDqJBQO5Q==", + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.4.tgz", + "integrity": "sha512-AVUP428VQTSddguz9dO9ngb+E5aScyg7nOeJDrF1HPYu555gmza3bDGMPhmVXL8svDSoqPCsCPjb265yG/kLKQ==", "cpu": [ "arm64" ], @@ -434,9 +434,9 @@ } }, "node_modules/@esbuild/win32-ia32": { - "version": "0.25.2", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.2.tgz", - "integrity": "sha512-WRJgsz9un0nqZJ4MfhabxaD9Ft8KioqU3JMinOTvobbX6MOSUigSBlogP8QB3uxpJDsFS6yN+3FDBdqE5lg9kg==", + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.4.tgz", + "integrity": "sha512-i1sW+1i+oWvQzSgfRcxxG2k4I9n3O9NRqy8U+uugaT2Dy7kLO9Y7wI72haOahxceMX8hZAzgGou1FhndRldxRg==", "cpu": [ "ia32" ], @@ -451,9 +451,9 @@ } }, "node_modules/@esbuild/win32-x64": { - "version": "0.25.2", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.2.tgz", - "integrity": "sha512-kM3HKb16VIXZyIeVrM1ygYmZBKybX8N4p754bw390wGO3Tf2j4L2/WYL+4suWujpgf6GBYs3jv7TyUivdd05JA==", + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.4.tgz", + "integrity": "sha512-nOT2vZNw6hJ+z43oP1SPea/G/6AbN6X+bGNhNuq8NtRHy4wsMhw765IKLNmnjek7GvjWBYQ8Q5VBoYTFg9y1UQ==", "cpu": [ "x64" ], @@ -468,9 +468,9 @@ } }, "node_modules/@eslint-community/eslint-utils": { - "version": "4.5.1", - "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.5.1.tgz", - "integrity": "sha512-soEIOALTfTK6EjmKMMoLugwaP0rzkad90iIWd1hMO9ARkSAyjfMfkRRhLvD5qH7vvM0Cg72pieUfR6yh6XxC4w==", + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.7.0.tgz", + "integrity": "sha512-dyybb3AcajC7uha6CvhdVRJqaKyn7w2YKqKyAN37NKYgZT36w+iRb0Dymmc5qEJ549c/S31cMMSFd75bteCpCw==", "license": "MIT", "dependencies": { "eslint-visitor-keys": "^3.4.3" @@ -531,18 +531,18 @@ } }, "node_modules/@eslint/config-helpers": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.2.1.tgz", - "integrity": "sha512-RI17tsD2frtDu/3dmI7QRrD4bedNKPM08ziRYaC5AhkGrzIAJelm9kJU1TznK+apx6V+cqRz8tfpEeG3oIyjxw==", + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.2.2.tgz", + "integrity": "sha512-+GPzk8PlG0sPpzdU5ZvIRMPidzAnZDl/s9L+y13iodqvb8leL53bTannOrQ/Im7UkpsmFU5Ily5U60LWixnmLg==", "license": "Apache-2.0", "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, "node_modules/@eslint/core": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.12.0.tgz", - "integrity": "sha512-cmrR6pytBuSMTaBweKoGMwu3EiHiEC+DoyupPmlZ0HxBJBtIxwe+j/E4XPIKNx+Q74c8lXKPwYawBf5glsTkHg==", + "version": "0.14.0", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.14.0.tgz", + "integrity": "sha512-qIbV0/JZr7iSDjqAc60IqbLdsj9GDt16xQtWD+B78d/HAlvysGdZZ6rpJHGAc2T0FQx1X6thsSPdnoiGKdNtdg==", "license": "Apache-2.0", "dependencies": { "@types/json-schema": "^7.0.15" @@ -584,6 +584,15 @@ "concat-map": "0.0.1" } }, + "node_modules/@eslint/eslintrc/node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, "node_modules/@eslint/eslintrc/node_modules/minimatch": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", @@ -597,12 +606,15 @@ } }, "node_modules/@eslint/js": { - "version": "9.24.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.24.0.tgz", - "integrity": "sha512-uIY/y3z0uvOGX8cp1C2fiC4+ZmBhp6yZWkojtHL1YEMnRt1Y63HB9TM17proGEmeG7HeUY+UP36F0aknKYTpYA==", + "version": "9.27.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.27.0.tgz", + "integrity": "sha512-G5JD9Tu5HJEu4z2Uo4aHY2sLV64B7CDMXxFzqzjl3NKd6RVzSXNoE80jk7Y0lJkTTkjiIhBAqmlYwjuBY3tvpA==", "license": "MIT", "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" } }, "node_modules/@eslint/object-schema": { @@ -615,30 +627,18 @@ } }, "node_modules/@eslint/plugin-kit": { - "version": "0.2.8", - "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.2.8.tgz", - "integrity": "sha512-ZAoA40rNMPwSm+AeHpCq8STiNAwzWLJuP8Xv4CHIc9wv/PSuExjMrmjfYNj682vW0OOiZ1HKxzvjQr9XZIisQA==", + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.3.1.tgz", + "integrity": "sha512-0J+zgWxHN+xXONWIyPWKFMgVuJoZuGiIFu8yxk7RJjxkzpGmyja5wRFqZIVtjDVOQpV+Rw0iOAjYPE2eQyjr0w==", "license": "Apache-2.0", "dependencies": { - "@eslint/core": "^0.13.0", + "@eslint/core": "^0.14.0", "levn": "^0.4.1" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, - "node_modules/@eslint/plugin-kit/node_modules/@eslint/core": { - "version": "0.13.0", - "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.13.0.tgz", - "integrity": "sha512-yfkgDw1KR66rkT5A8ci4irzDysN7FRpq3ttJolR88OqQikAWqwA8j5VZyas+vjyBNFIJ7MfybJ9plMILI2UrCw==", - "license": "Apache-2.0", - "dependencies": { - "@types/json-schema": "^7.0.15" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - } - }, "node_modules/@humanfs/core": { "version": "0.19.1", "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", @@ -688,9 +688,9 @@ } }, "node_modules/@humanwhocodes/retry": { - "version": "0.4.2", - "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.2.tgz", - "integrity": "sha512-xeO57FpIu4p1Ri3Jq/EXq4ClRm86dVF2z/+kvFnyqVYRavTZmaFaUBbWCOuuTh0o/g7DSsk6kc2vrS4Vl5oPOQ==", + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz", + "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==", "license": "Apache-2.0", "engines": { "node": ">=18.18" @@ -829,9 +829,9 @@ "license": "MIT" }, "node_modules/@types/node": { - "version": "22.14.0", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.14.0.tgz", - "integrity": "sha512-Kmpl+z84ILoG+3T/zQFyAJsU6EPTmOCj8/2+83fSN6djd6I4o7uOuGIH6vq3PrjY5BGitSbFuMN18j3iknubbA==", + "version": "22.15.21", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.15.21.tgz", + "integrity": "sha512-EV/37Td6c+MgKAbkcLG6vqZ2zEYHD7bvSrzqqs2RIhbA6w3x+Dqz8MZM3sP6kGTeLrdoOgKZe+Xja7tUB2DNkQ==", "dev": true, "license": "MIT", "dependencies": { @@ -839,20 +839,20 @@ } }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "8.29.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.29.1.tgz", - "integrity": "sha512-ba0rr4Wfvg23vERs3eB+P3lfj2E+2g3lhWcCVukUuhtcdUx5lSIFZlGFEBHKr+3zizDa/TvZTptdNHVZWAkSBg==", + "version": "8.32.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.32.1.tgz", + "integrity": "sha512-6u6Plg9nP/J1GRpe/vcjjabo6Uc5YQPAMxsgQyGC/I0RuukiG1wIe3+Vtg3IrSCVJDmqK3j8adrtzXSENRtFgg==", "license": "MIT", "dependencies": { "@eslint-community/regexpp": "^4.10.0", - "@typescript-eslint/scope-manager": "8.29.1", - "@typescript-eslint/type-utils": "8.29.1", - "@typescript-eslint/utils": "8.29.1", - "@typescript-eslint/visitor-keys": "8.29.1", + "@typescript-eslint/scope-manager": "8.32.1", + "@typescript-eslint/type-utils": "8.32.1", + "@typescript-eslint/utils": "8.32.1", + "@typescript-eslint/visitor-keys": "8.32.1", "graphemer": "^1.4.0", - "ignore": "^5.3.1", + "ignore": "^7.0.0", "natural-compare": "^1.4.0", - "ts-api-utils": "^2.0.1" + "ts-api-utils": "^2.1.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -868,15 +868,15 @@ } }, "node_modules/@typescript-eslint/parser": { - "version": "8.29.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.29.1.tgz", - "integrity": "sha512-zczrHVEqEaTwh12gWBIJWj8nx+ayDcCJs06yoNMY0kwjMWDM6+kppljY+BxWI06d2Ja+h4+WdufDcwMnnMEWmg==", + "version": "8.32.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.32.1.tgz", + "integrity": "sha512-LKMrmwCPoLhM45Z00O1ulb6jwyVr2kr3XJp+G+tSEZcbauNnScewcQwtJqXDhXeYPDEjZ8C1SjXm015CirEmGg==", "license": "MIT", "dependencies": { - "@typescript-eslint/scope-manager": "8.29.1", - "@typescript-eslint/types": "8.29.1", - "@typescript-eslint/typescript-estree": "8.29.1", - "@typescript-eslint/visitor-keys": "8.29.1", + "@typescript-eslint/scope-manager": "8.32.1", + "@typescript-eslint/types": "8.32.1", + "@typescript-eslint/typescript-estree": "8.32.1", + "@typescript-eslint/visitor-keys": "8.32.1", "debug": "^4.3.4" }, "engines": { @@ -892,13 +892,13 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "8.29.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.29.1.tgz", - "integrity": "sha512-2nggXGX5F3YrsGN08pw4XpMLO1Rgtnn4AzTegC2MDesv6q3QaTU5yU7IbS1tf1IwCR0Hv/1EFygLn9ms6LIpDA==", + "version": "8.32.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.32.1.tgz", + "integrity": "sha512-7IsIaIDeZn7kffk7qXC3o6Z4UblZJKV3UBpkvRNpr5NSyLji7tvTcvmnMNYuYLyh26mN8W723xpo3i4MlD33vA==", "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.29.1", - "@typescript-eslint/visitor-keys": "8.29.1" + "@typescript-eslint/types": "8.32.1", + "@typescript-eslint/visitor-keys": "8.32.1" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -909,15 +909,15 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "8.29.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.29.1.tgz", - "integrity": "sha512-DkDUSDwZVCYN71xA4wzySqqcZsHKic53A4BLqmrWFFpOpNSoxX233lwGu/2135ymTCR04PoKiEEEvN1gFYg4Tw==", + "version": "8.32.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.32.1.tgz", + "integrity": "sha512-mv9YpQGA8iIsl5KyUPi+FGLm7+bA4fgXaeRcFKRDRwDMu4iwrSHeDPipwueNXhdIIZltwCJv+NkxftECbIZWfA==", "license": "MIT", "dependencies": { - "@typescript-eslint/typescript-estree": "8.29.1", - "@typescript-eslint/utils": "8.29.1", + "@typescript-eslint/typescript-estree": "8.32.1", + "@typescript-eslint/utils": "8.32.1", "debug": "^4.3.4", - "ts-api-utils": "^2.0.1" + "ts-api-utils": "^2.1.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -932,9 +932,9 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "8.29.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.29.1.tgz", - "integrity": "sha512-VT7T1PuJF1hpYC3AGm2rCgJBjHL3nc+A/bhOp9sGMKfi5v0WufsX/sHCFBfNTx2F+zA6qBc/PD0/kLRLjdt8mQ==", + "version": "8.32.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.32.1.tgz", + "integrity": "sha512-YmybwXUJcgGqgAp6bEsgpPXEg6dcCyPyCSr0CAAueacR/CCBi25G3V8gGQ2kRzQRBNol7VQknxMs9HvVa9Rvfg==", "license": "MIT", "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -945,19 +945,19 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "8.29.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.29.1.tgz", - "integrity": "sha512-l1enRoSaUkQxOQnbi0KPUtqeZkSiFlqrx9/3ns2rEDhGKfTa+88RmXqedC1zmVTOWrLc2e6DEJrTA51C9iLH5g==", + "version": "8.32.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.32.1.tgz", + "integrity": "sha512-Y3AP9EIfYwBb4kWGb+simvPaqQoT5oJuzzj9m0i6FCY6SPvlomY2Ei4UEMm7+FXtlNJbor80ximyslzaQF6xhg==", "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.29.1", - "@typescript-eslint/visitor-keys": "8.29.1", + "@typescript-eslint/types": "8.32.1", + "@typescript-eslint/visitor-keys": "8.32.1", "debug": "^4.3.4", "fast-glob": "^3.3.2", "is-glob": "^4.0.3", "minimatch": "^9.0.4", "semver": "^7.6.0", - "ts-api-utils": "^2.0.1" + "ts-api-utils": "^2.1.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -971,15 +971,15 @@ } }, "node_modules/@typescript-eslint/utils": { - "version": "8.29.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.29.1.tgz", - "integrity": "sha512-QAkFEbytSaB8wnmB+DflhUPz6CLbFWE2SnSCrRMEa+KnXIzDYbpsn++1HGvnfAsUY44doDXmvRkO5shlM/3UfA==", + "version": "8.32.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.32.1.tgz", + "integrity": "sha512-DsSFNIgLSrc89gpq1LJB7Hm1YpuhK086DRDJSNrewcGvYloWW1vZLHBTIvarKZDcAORIy/uWNx8Gad+4oMpkSA==", "license": "MIT", "dependencies": { - "@eslint-community/eslint-utils": "^4.4.0", - "@typescript-eslint/scope-manager": "8.29.1", - "@typescript-eslint/types": "8.29.1", - "@typescript-eslint/typescript-estree": "8.29.1" + "@eslint-community/eslint-utils": "^4.7.0", + "@typescript-eslint/scope-manager": "8.32.1", + "@typescript-eslint/types": "8.32.1", + "@typescript-eslint/typescript-estree": "8.32.1" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -994,12 +994,12 @@ } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "8.29.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.29.1.tgz", - "integrity": "sha512-RGLh5CRaUEf02viP5c1Vh1cMGffQscyHe7HPAzGpfmfflFg1wUz2rYxd+OZqwpeypYvZ8UxSxuIpF++fmOzEcg==", + "version": "8.32.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.32.1.tgz", + "integrity": "sha512-ar0tjQfObzhSaW3C3QNmTc5ofj0hDoNQ5XWrCy6zDyabdr0TWhCkClp+rywGNj/odAFBVzzJrK4tEq5M4Hmu4w==", "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.29.1", + "@typescript-eslint/types": "8.32.1", "eslint-visitor-keys": "^4.2.0" }, "engines": { @@ -1068,6 +1068,16 @@ "wrap-ansi": "^7.0.0" } }, + "node_modules/@vscode/test-cli/node_modules/diff": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-5.2.0.tgz", + "integrity": "sha512-uIFDxqpRZGZ6ThOk84hEfqWoHx2devRFvpTZcTHur85vImfaxUbTW9Ryh4CpCuDnToOP1CEtXKIgytHBPVff5A==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.3.1" + } + }, "node_modules/@vscode/test-cli/node_modules/emoji-regex": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", @@ -1237,16 +1247,16 @@ } }, "node_modules/@vscode/test-electron": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/@vscode/test-electron/-/test-electron-2.4.1.tgz", - "integrity": "sha512-Gc6EdaLANdktQ1t+zozoBVRynfIsMKMc94Svu1QreOBC8y76x4tvaK32TljrLi1LI2+PK58sDVbL7ALdqf3VRQ==", + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/@vscode/test-electron/-/test-electron-2.5.2.tgz", + "integrity": "sha512-8ukpxv4wYe0iWMRQU18jhzJOHkeGKbnw7xWRX3Zw1WJA4cEKbHcmmLPdPrPtL6rhDcrlCZN+xKRpv09n4gRHYg==", "dev": true, "license": "MIT", "dependencies": { "http-proxy-agent": "^7.0.2", "https-proxy-agent": "^7.0.5", "jszip": "^3.10.1", - "ora": "^7.0.1", + "ora": "^8.1.0", "semver": "^7.6.2" }, "engines": { @@ -1446,27 +1456,6 @@ "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", "license": "MIT" }, - "node_modules/base64-js": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", - "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT" - }, "node_modules/binary-extensions": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", @@ -1480,33 +1469,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/bl": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/bl/-/bl-5.1.0.tgz", - "integrity": "sha512-tv1ZJHLfTDnXE6tMHv73YgSJaWR2AFuPwMntBe7XL/GBFHnT0CLnsHMogfk5+GzCDC5ZWarSCYaIGATZt9dNsQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "buffer": "^6.0.3", - "inherits": "^2.0.4", - "readable-stream": "^3.4.0" - } - }, - "node_modules/bl/node_modules/readable-stream": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", - "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", - "dev": true, - "license": "MIT", - "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - }, - "engines": { - "node": ">= 6" - } - }, "node_modules/bottleneck": { "version": "2.19.5", "resolved": "https://registry.npmjs.org/bottleneck/-/bottleneck-2.19.5.tgz", @@ -1542,31 +1504,6 @@ "dev": true, "license": "ISC" }, - "node_modules/buffer": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", - "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT", - "dependencies": { - "base64-js": "^1.3.1", - "ieee754": "^1.2.1" - } - }, "node_modules/c8": { "version": "9.1.0", "resolved": "https://registry.npmjs.org/c8/-/c8-9.1.0.tgz", @@ -1719,16 +1656,16 @@ } }, "node_modules/cli-cursor": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-4.0.0.tgz", - "integrity": "sha512-VGtlMu3x/4DOtIUwEkRezxUZ2lBacNJCHash0N0WeZDBS+7Ux1dm3XWAgWYxLJFMMdOeXMHXorshEFhbMSGelg==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-5.0.0.tgz", + "integrity": "sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw==", "dev": true, "license": "MIT", "dependencies": { - "restore-cursor": "^4.0.0" + "restore-cursor": "^5.0.0" }, "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + "node": ">=18" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" @@ -1942,9 +1879,9 @@ } }, "node_modules/debug": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", - "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", + "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", "license": "MIT", "dependencies": { "ms": "^2.1.3" @@ -2014,9 +1951,9 @@ } }, "node_modules/diff": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/diff/-/diff-5.2.0.tgz", - "integrity": "sha512-uIFDxqpRZGZ6ThOk84hEfqWoHx2devRFvpTZcTHur85vImfaxUbTW9Ryh4CpCuDnToOP1CEtXKIgytHBPVff5A==", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-7.0.0.tgz", + "integrity": "sha512-PJWHUb1RFevKCwaFA9RlG5tCd+FO5iRh9A8HEtkmBH2Li03iJriB6m6JIN4rGz3K3JLawI7/veA1xzRKP6ISBw==", "dev": true, "license": "BSD-3-Clause", "engines": { @@ -2077,9 +2014,9 @@ } }, "node_modules/es-abstract": { - "version": "1.23.9", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.23.9.tgz", - "integrity": "sha512-py07lI0wjxAC/DcfK1S6G7iANonniZwTISvdPzk9hzeH0IZIshbuuFxLIU96OyF89Yb9hiqWn8M/bY83KY5vzA==", + "version": "1.23.10", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.23.10.tgz", + "integrity": "sha512-MtUbM072wlJNyeYAe0mhzrD+M6DIJa96CZAOBBrhDbgKnB4MApIKefcyAB1eOdYn8cUNZgvwBvEzdoAYsxgEIw==", "dev": true, "license": "MIT", "dependencies": { @@ -2087,18 +2024,18 @@ "arraybuffer.prototype.slice": "^1.0.4", "available-typed-arrays": "^1.0.7", "call-bind": "^1.0.8", - "call-bound": "^1.0.3", + "call-bound": "^1.0.4", "data-view-buffer": "^1.0.2", "data-view-byte-length": "^1.0.2", "data-view-byte-offset": "^1.0.1", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", - "es-object-atoms": "^1.0.0", + "es-object-atoms": "^1.1.1", "es-set-tostringtag": "^2.1.0", "es-to-primitive": "^1.3.0", "function.prototype.name": "^1.1.8", - "get-intrinsic": "^1.2.7", - "get-proto": "^1.0.0", + "get-intrinsic": "^1.3.0", + "get-proto": "^1.0.1", "get-symbol-description": "^1.1.0", "globalthis": "^1.0.4", "gopd": "^1.2.0", @@ -2114,13 +2051,13 @@ "is-shared-array-buffer": "^1.0.4", "is-string": "^1.1.1", "is-typed-array": "^1.1.15", - "is-weakref": "^1.1.0", + "is-weakref": "^1.1.1", "math-intrinsics": "^1.1.0", - "object-inspect": "^1.13.3", + "object-inspect": "^1.13.4", "object-keys": "^1.1.1", "object.assign": "^4.1.7", "own-keys": "^1.0.1", - "regexp.prototype.flags": "^1.5.3", + "regexp.prototype.flags": "^1.5.4", "safe-array-concat": "^1.1.3", "safe-push-apply": "^1.0.0", "safe-regex-test": "^1.1.0", @@ -2133,7 +2070,7 @@ "typed-array-byte-offset": "^1.0.4", "typed-array-length": "^1.0.7", "unbox-primitive": "^1.1.0", - "which-typed-array": "^1.1.18" + "which-typed-array": "^1.1.19" }, "engines": { "node": ">= 0.4" @@ -2210,9 +2147,9 @@ } }, "node_modules/esbuild": { - "version": "0.25.2", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.2.tgz", - "integrity": "sha512-16854zccKPnC+toMywC+uKNeYSv+/eXkevRAfwRD/G9Cleq66m8XFIrigkbvauLLlCfDL45Q2cWegSg53gGBnQ==", + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.4.tgz", + "integrity": "sha512-8pgjLUcUjcgDg+2Q4NYXnPbo/vncAY4UmyaCm0jZevERqCHZIaWwdJHkf8XQtu4AxSKCdvrUbT0XUr1IdZzI8Q==", "dev": true, "hasInstallScript": true, "license": "MIT", @@ -2223,31 +2160,31 @@ "node": ">=18" }, "optionalDependencies": { - "@esbuild/aix-ppc64": "0.25.2", - "@esbuild/android-arm": "0.25.2", - "@esbuild/android-arm64": "0.25.2", - "@esbuild/android-x64": "0.25.2", - "@esbuild/darwin-arm64": "0.25.2", - "@esbuild/darwin-x64": "0.25.2", - "@esbuild/freebsd-arm64": "0.25.2", - "@esbuild/freebsd-x64": "0.25.2", - "@esbuild/linux-arm": "0.25.2", - "@esbuild/linux-arm64": "0.25.2", - "@esbuild/linux-ia32": "0.25.2", - "@esbuild/linux-loong64": "0.25.2", - "@esbuild/linux-mips64el": "0.25.2", - "@esbuild/linux-ppc64": "0.25.2", - "@esbuild/linux-riscv64": "0.25.2", - "@esbuild/linux-s390x": "0.25.2", - "@esbuild/linux-x64": "0.25.2", - "@esbuild/netbsd-arm64": "0.25.2", - "@esbuild/netbsd-x64": "0.25.2", - "@esbuild/openbsd-arm64": "0.25.2", - "@esbuild/openbsd-x64": "0.25.2", - "@esbuild/sunos-x64": "0.25.2", - "@esbuild/win32-arm64": "0.25.2", - "@esbuild/win32-ia32": "0.25.2", - "@esbuild/win32-x64": "0.25.2" + "@esbuild/aix-ppc64": "0.25.4", + "@esbuild/android-arm": "0.25.4", + "@esbuild/android-arm64": "0.25.4", + "@esbuild/android-x64": "0.25.4", + "@esbuild/darwin-arm64": "0.25.4", + "@esbuild/darwin-x64": "0.25.4", + "@esbuild/freebsd-arm64": "0.25.4", + "@esbuild/freebsd-x64": "0.25.4", + "@esbuild/linux-arm": "0.25.4", + "@esbuild/linux-arm64": "0.25.4", + "@esbuild/linux-ia32": "0.25.4", + "@esbuild/linux-loong64": "0.25.4", + "@esbuild/linux-mips64el": "0.25.4", + "@esbuild/linux-ppc64": "0.25.4", + "@esbuild/linux-riscv64": "0.25.4", + "@esbuild/linux-s390x": "0.25.4", + "@esbuild/linux-x64": "0.25.4", + "@esbuild/netbsd-arm64": "0.25.4", + "@esbuild/netbsd-x64": "0.25.4", + "@esbuild/openbsd-arm64": "0.25.4", + "@esbuild/openbsd-x64": "0.25.4", + "@esbuild/sunos-x64": "0.25.4", + "@esbuild/win32-arm64": "0.25.4", + "@esbuild/win32-ia32": "0.25.4", + "@esbuild/win32-x64": "0.25.4" } }, "node_modules/escalade": { @@ -2273,19 +2210,19 @@ } }, "node_modules/eslint": { - "version": "9.24.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.24.0.tgz", - "integrity": "sha512-eh/jxIEJyZrvbWRe4XuVclLPDYSYYYgLy5zXGGxD6j8zjSAxFEzI2fL/8xNq6O2yKqVt+eF2YhV+hxjV6UKXwQ==", + "version": "9.27.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.27.0.tgz", + "integrity": "sha512-ixRawFQuMB9DZ7fjU3iGGganFDp3+45bPOdaRurcFHSXO1e/sYwUX/FtQZpLZJR6SjMoJH8hR2pPEAfDyCoU2Q==", "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.12.1", "@eslint/config-array": "^0.20.0", - "@eslint/config-helpers": "^0.2.0", - "@eslint/core": "^0.12.0", + "@eslint/config-helpers": "^0.2.1", + "@eslint/core": "^0.14.0", "@eslint/eslintrc": "^3.3.1", - "@eslint/js": "9.24.0", - "@eslint/plugin-kit": "^0.2.7", + "@eslint/js": "9.27.0", + "@eslint/plugin-kit": "^0.3.1", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/retry": "^0.4.2", @@ -2394,6 +2331,15 @@ "node": ">=10.13.0" } }, + "node_modules/eslint/node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, "node_modules/eslint/node_modules/minimatch": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", @@ -2695,6 +2641,19 @@ "node": "6.* || 8.* || >= 10.*" } }, + "node_modules/get-east-asian-width": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.3.0.tgz", + "integrity": "sha512-vpeMIQKxczTD/0s2CdEWHcb0eeJe6TFjxb+J5xgX7hScxqrGuyjmv4c1D4A/gelKfyox0gJJwIHF+fLjeaM8kQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/get-intrinsic": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", @@ -2985,31 +2944,10 @@ "node": ">= 14" } }, - "node_modules/ieee754": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", - "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "BSD-3-Clause" - }, "node_modules/ignore": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", - "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.4.tgz", + "integrity": "sha512-gJzzk+PQNznz8ysRrC0aOkBNVRBDtE1n53IqyqEf3PXrYwomFs5q4pGMizBMJF+ykh03insJ27hB8gSrD2Hn8A==", "license": "MIT", "engines": { "node": ">= 4" @@ -3803,14 +3741,17 @@ "node": ">=8.6" } }, - "node_modules/mimic-fn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", - "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "node_modules/mimic-function": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/mimic-function/-/mimic-function-5.0.1.tgz", + "integrity": "sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==", "dev": true, "license": "MIT", "engines": { - "node": ">=6" + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/minimatch": { @@ -3839,25 +3780,25 @@ } }, "node_modules/mocha": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/mocha/-/mocha-11.1.0.tgz", - "integrity": "sha512-8uJR5RTC2NgpY3GrYcgpZrsEd9zKbPDpob1RezyR2upGHRQtHWofmzTMzTMSV6dru3tj5Ukt0+Vnq1qhFEEwAg==", + "version": "11.5.0", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-11.5.0.tgz", + "integrity": "sha512-VKDjhy6LMTKm0WgNEdlY77YVsD49LZnPSXJAaPNL9NRYQADxvORsyG1DIQY6v53BKTnlNbEE2MbVCDbnxr4K3w==", "dev": true, "license": "MIT", "dependencies": { - "ansi-colors": "^4.1.3", "browser-stdout": "^1.3.1", - "chokidar": "^3.5.3", + "chokidar": "^4.0.1", "debug": "^4.3.5", - "diff": "^5.2.0", + "diff": "^7.0.0", "escape-string-regexp": "^4.0.0", "find-up": "^5.0.0", "glob": "^10.4.5", "he": "^1.2.0", "js-yaml": "^4.1.0", "log-symbols": "^4.1.0", - "minimatch": "^5.1.6", + "minimatch": "^9.0.5", "ms": "^2.1.3", + "picocolors": "^1.1.1", "serialize-javascript": "^6.0.2", "strip-json-comments": "^3.1.1", "supports-color": "^8.1.1", @@ -3874,17 +3815,34 @@ "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, - "node_modules/mocha/node_modules/minimatch": { - "version": "5.1.6", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", - "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "node_modules/mocha/node_modules/chokidar": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", + "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", "dev": true, - "license": "ISC", + "license": "MIT", "dependencies": { - "brace-expansion": "^2.0.1" + "readdirp": "^4.0.1" }, "engines": { - "node": ">=10" + "node": ">= 14.16.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/mocha/node_modules/readdirp": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", + "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14.18.0" + }, + "funding": { + "type": "individual", + "url": "https://paulmillr.com/funding/" } }, "node_modules/mocha/node_modules/supports-color": { @@ -4211,16 +4169,16 @@ } }, "node_modules/onetime": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", - "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-7.0.0.tgz", + "integrity": "sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==", "dev": true, "license": "MIT", "dependencies": { - "mimic-fn": "^2.1.0" + "mimic-function": "^5.0.0" }, "engines": { - "node": ">=6" + "node": ">=18" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" @@ -4244,24 +4202,24 @@ } }, "node_modules/ora": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/ora/-/ora-7.0.1.tgz", - "integrity": "sha512-0TUxTiFJWv+JnjWm4o9yvuskpEJLXTcng8MJuKd+SzAzp2o+OP3HWqNhB4OdJRt1Vsd9/mR0oyaEYlOnL7XIRw==", + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/ora/-/ora-8.2.0.tgz", + "integrity": "sha512-weP+BZ8MVNnlCm8c0Qdc1WSWq4Qn7I+9CJGm7Qali6g44e/PUzbjNqJX5NJ9ljlNMosfJvg1fKEGILklK9cwnw==", "dev": true, "license": "MIT", "dependencies": { "chalk": "^5.3.0", - "cli-cursor": "^4.0.0", - "cli-spinners": "^2.9.0", + "cli-cursor": "^5.0.0", + "cli-spinners": "^2.9.2", "is-interactive": "^2.0.0", - "is-unicode-supported": "^1.3.0", - "log-symbols": "^5.1.0", - "stdin-discarder": "^0.1.0", - "string-width": "^6.1.0", + "is-unicode-supported": "^2.0.0", + "log-symbols": "^6.0.0", + "stdin-discarder": "^0.2.2", + "string-width": "^7.2.0", "strip-ansi": "^7.1.0" }, "engines": { - "node": ">=16" + "node": ">=18" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" @@ -4288,28 +4246,41 @@ "license": "MIT" }, "node_modules/ora/node_modules/is-unicode-supported": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-1.3.0.tgz", - "integrity": "sha512-43r2mRvz+8JRIKnWJ+3j8JtjRKZ6GmjzfaE/qiBJnikNnYv/6bagRJ1kUhNk8R5EX/GkobD+r+sfxCPJsiKBLQ==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-2.1.0.tgz", + "integrity": "sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ==", "dev": true, "license": "MIT", "engines": { - "node": ">=12" + "node": ">=18" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/ora/node_modules/log-symbols": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-5.1.0.tgz", - "integrity": "sha512-l0x2DvrW294C9uDCoQe1VSU4gf529FkSZ6leBl4TiqZH/e+0R7hSfHQBNut2mNygDgHwvYHfFLn6Oxb3VWj2rA==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-6.0.0.tgz", + "integrity": "sha512-i24m8rpwhmPIS4zscNzK6MSEhk0DUWa/8iYQWxhffV8jkI4Phvs3F+quL5xvS0gdQR0FyTCMMH33Y78dDTzzIw==", "dev": true, "license": "MIT", "dependencies": { - "chalk": "^5.0.0", - "is-unicode-supported": "^1.1.0" + "chalk": "^5.3.0", + "is-unicode-supported": "^1.3.0" }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ora/node_modules/log-symbols/node_modules/is-unicode-supported": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-1.3.0.tgz", + "integrity": "sha512-43r2mRvz+8JRIKnWJ+3j8JtjRKZ6GmjzfaE/qiBJnikNnYv/6bagRJ1kUhNk8R5EX/GkobD+r+sfxCPJsiKBLQ==", + "dev": true, + "license": "MIT", "engines": { "node": ">=12" }, @@ -4318,18 +4289,18 @@ } }, "node_modules/ora/node_modules/string-width": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-6.1.0.tgz", - "integrity": "sha512-k01swCJAgQmuADB0YIc+7TuatfNvTBVOoaUWJjTB9R4VJzR5vNWzf5t42ESVZFPS8xTySF7CAdV4t/aaIm3UnQ==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", + "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", "dev": true, "license": "MIT", "dependencies": { - "eastasianwidth": "^0.2.0", - "emoji-regex": "^10.2.1", - "strip-ansi": "^7.0.1" + "emoji-regex": "^10.3.0", + "get-east-asian-width": "^1.0.0", + "strip-ansi": "^7.1.0" }, "engines": { - "node": ">=16" + "node": ">=18" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" @@ -4488,6 +4459,13 @@ "node": ">=4" } }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, "node_modules/picomatch": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", @@ -4723,29 +4701,22 @@ } }, "node_modules/restore-cursor": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-4.0.0.tgz", - "integrity": "sha512-I9fPXU9geO9bHOt9pHHOhOkYerIMsmVaWB0rA2AI9ERh/+x/i7MV5HKBNrg+ljO5eoPVgCcnFuRjJ9uH6I/3eg==", + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-5.1.0.tgz", + "integrity": "sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==", "dev": true, "license": "MIT", "dependencies": { - "onetime": "^5.1.0", - "signal-exit": "^3.0.2" + "onetime": "^7.0.0", + "signal-exit": "^4.1.0" }, "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + "node": ">=18" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/restore-cursor/node_modules/signal-exit": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", - "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", - "dev": true, - "license": "ISC" - }, "node_modules/reusify": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", @@ -4856,9 +4827,9 @@ } }, "node_modules/semver": { - "version": "7.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz", - "integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==", + "version": "7.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", + "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", "license": "ISC", "bin": { "semver": "bin/semver.js" @@ -5093,16 +5064,13 @@ "license": "CC0-1.0" }, "node_modules/stdin-discarder": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/stdin-discarder/-/stdin-discarder-0.1.0.tgz", - "integrity": "sha512-xhV7w8S+bUwlPTb4bAOUQhv8/cSS5offJuX8GQGq32ONF0ZtDWKfkdomM3HMRA+LhX6um/FZ0COqlwsjD53LeQ==", + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/stdin-discarder/-/stdin-discarder-0.2.2.tgz", + "integrity": "sha512-UhDfHmA92YAlNnCfhmq0VeNL5bDbiZGg7sZ2IvPsXubGkiNa9EC+tUTsjBRsYUAz87btI6/1wf4XoVvQ3uRnmQ==", "dev": true, "license": "MIT", - "dependencies": { - "bl": "^5.0.0" - }, "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + "node": ">=18" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" @@ -5349,9 +5317,9 @@ } }, "node_modules/tapable": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", - "integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==", + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.2.tgz", + "integrity": "sha512-Re10+NauLTMCudc7T5WLFLAwDhQ0JWdrMK+9B2M8zR5hRExKmsRDCBA7/aV/pNJFltmBFO5BAMlQFi/vq3nKOg==", "dev": true, "license": "MIT", "engines": { @@ -5450,9 +5418,9 @@ "license": "0BSD" }, "node_modules/tsyringe": { - "version": "4.9.1", - "resolved": "https://registry.npmjs.org/tsyringe/-/tsyringe-4.9.1.tgz", - "integrity": "sha512-dJCWk0RolAnGk0j839M0lcuS/PtNUPaMsnBosn+wg5N16xy0tofcVuvsidMs0JuRbaJ0wVIT7RsuHWbVIZ5Rcg==", + "version": "4.10.0", + "resolved": "https://registry.npmjs.org/tsyringe/-/tsyringe-4.10.0.tgz", + "integrity": "sha512-axr3IdNuVIxnaK5XGEUFTu3YmAQ6lllgrvqfEoR16g/HGnYY/6We4oWENtAnzK6/LpJ2ur9PAb80RBt7/U4ugw==", "license": "MIT", "dependencies": { "tslib": "^1.9.3" @@ -5565,14 +5533,14 @@ } }, "node_modules/typescript-eslint": { - "version": "8.29.1", - "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.29.1.tgz", - "integrity": "sha512-f8cDkvndhbQMPcysk6CUSGBWV+g1utqdn71P5YKwMumVMOG/5k7cHq0KyG4O52nB0oKS4aN2Tp5+wB4APJGC+w==", + "version": "8.32.1", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.32.1.tgz", + "integrity": "sha512-D7el+eaDHAmXvrZBy1zpzSNIRqnCOrkwTgZxTu3MUqRWk8k0q9m9Ho4+vPf7iHtgUfrK/o8IZaEApsxPlHTFCg==", "license": "MIT", "dependencies": { - "@typescript-eslint/eslint-plugin": "8.29.1", - "@typescript-eslint/parser": "8.29.1", - "@typescript-eslint/utils": "8.29.1" + "@typescript-eslint/eslint-plugin": "8.32.1", + "@typescript-eslint/parser": "8.32.1", + "@typescript-eslint/utils": "8.32.1" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" diff --git a/package.json b/package.json index 3b51272..3d929c6 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,7 @@ "icon": "images/vba-lsp-icon.png", "author": "SSlinky", "license": "MIT", - "version": "1.5.13", + "version": "1.7.1", "repository": { "type": "git", "url": "https://github.com/SSlinky/VBA-LanguageServer" @@ -49,9 +49,9 @@ ], "iconThemes": [ { - "id": "vba-lsp", - "label": "VBA Icons", - "path": "icon-theme.json" + "id": "vs-seti-vba", + "label": "Seti + VBA", + "path": "icons/theme-seti/icons/vs-seti-icon-theme.json" } ], "configurationDefaults": { @@ -170,21 +170,21 @@ "dependencies": { "antlr4ng": "^3.0.16", "reflect-metadata": "^0.2.2", - "tsyringe": "^4.9.1", - "typescript-eslint": "^8.29.1" + "tsyringe": "^4.10.0", + "typescript-eslint": "^8.32.1" }, "devDependencies": { "@types/mocha": "^10.0.10", - "@types/node": "^22.14.0", + "@types/node": "^22.15.21", "@typescript-eslint/eslint-plugin": "^8.29.0", "@typescript-eslint/parser": "^8.29.0", "@vscode/test-cli": "^0.0.10", - "@vscode/test-electron": "^2.4.1", + "@vscode/test-electron": "^2.5.2", "antlr4ng-cli": "^2.0.0", - "esbuild": "^0.25.2", - "eslint": "^9.24.0", + "esbuild": "^0.25.4", + "eslint": "^9.27.0", "js-yaml": "^4.1.0", - "mocha": "^11.1.0", + "mocha": "^11.5.0", "npm-run-all": "^4.1.5", "typescript": "^5.8.3", "vscode-tmgrammar-test": "^0.1.3" diff --git a/server/src/antlr/vba.g4 b/server/src/antlr/vba.g4 index 7122bf2..399378c 100644 --- a/server/src/antlr/vba.g4 +++ b/server/src/antlr/vba.g4 @@ -861,7 +861,7 @@ identifierStatementLabel : ambiguousIdentifier ; -resetNumberLable +resetNumberLabel : MINUS INTEGERLITERAL ; @@ -1312,7 +1312,7 @@ onErrorStatement errorBehavior : RESUME wsc NEXT | GOTO wsc? statementLabel - | GOTO wsc? resetNumberLable + | GOTO wsc? resetNumberLabel ; // 5.4.4.2 Resume Statement @@ -2062,11 +2062,6 @@ reservedTypeIdentifierB | VARIANT_B ; -typeableReservedName - : DATE - | STRING - ; - literalIdentifier : booleanLiteralIdentifier | objectLiteralIdentifier @@ -2135,7 +2130,6 @@ builtinType // This probably could be turned into a token typedName : ambiguousIdentifier typeSuffix - | typeableReservedName typeSuffix ; typeSuffix diff --git a/server/src/capabilities/capabilities.ts b/server/src/capabilities/capabilities.ts index 23b0839..dea7f41 100644 --- a/server/src/capabilities/capabilities.ts +++ b/server/src/capabilities/capabilities.ts @@ -1,9 +1,9 @@ // Core import { - Diagnostic, + DiagnosticSeverity, + LocationLink, + Position, Range, - SemanticTokenModifiers, - SemanticTokenTypes, SymbolInformation, SymbolKind } from 'vscode-languageserver'; @@ -12,11 +12,12 @@ import { import { ParserRuleContext, TerminalNode } from 'antlr4ng'; // Project -import { SemanticToken } from '../capabilities/semanticTokens'; +import { SemanticToken, SemanticTokenModifiers, SemanticTokenTypes } from '../capabilities/semanticTokens'; import { FoldingRange, FoldingRangeKind } from '../capabilities/folding'; import { BaseRuleSyntaxElement, BaseIdentifyableSyntaxElement, BaseSyntaxElement, Context, HasSemanticTokenCapability } from '../project/elements/base'; -import { BaseDiagnostic, DuplicateDeclarationDiagnostic, MethodVariableIsPublicDiagnostic, ShadowDeclarationDiagnostic, SubOrFunctionNotDefinedDiagnostic, VariableNotDefinedDiagnostic } from './diagnostics'; +import { AmbiguousNameDiagnostic, BaseDiagnostic, DuplicateDeclarationDiagnostic, ShadowDeclarationDiagnostic, SubOrFunctionNotDefinedDiagnostic, UnusedDiagnostic, VariableNotDefinedDiagnostic } from './diagnostics'; import { Services } from '../injection/services'; +import { isPositionInsideRange } from '../utils/helpers'; abstract class BaseCapability { @@ -55,10 +56,10 @@ export class FoldingRangeCapability extends BaseCapability { export class DiagnosticCapability extends BaseCapability { - diagnostics: Diagnostic[] = []; - evaluate: (...args: any[]) => Diagnostic[]; + diagnostics: BaseDiagnostic[] = []; + evaluate: (...args: any[]) => BaseDiagnostic[]; - constructor(element: BaseSyntaxElement, evaluate?: (...args: any[]) => Diagnostic[]) { + constructor(element: BaseSyntaxElement, evaluate?: (...args: any[]) => BaseDiagnostic[]) { super(element); this.evaluate = evaluate ?? (() => this.diagnostics); } @@ -66,11 +67,6 @@ export class DiagnosticCapability extends BaseCapability { export class SemanticTokenCapability extends BaseCapability { - private tokenType: SemanticTokenTypes; - private tokenModifiers: SemanticTokenModifiers[]; - private overrideRange?: Range; - private overrideLength?: number; - get semanticToken(): SemanticToken { const element = this.element as BaseRuleSyntaxElement & HasSemanticTokenCapability; const context = element.identifierCapability @@ -92,19 +88,17 @@ export class SemanticTokenCapability extends BaseCapability { ); } - constructor(element: BaseRuleSyntaxElement & HasSemanticTokenCapability, tokenType: SemanticTokenTypes, tokenModifiers: SemanticTokenModifiers[], overrideRange?: Range, overrideLength?: number) { + constructor(element: BaseRuleSyntaxElement, + private tokenType: SemanticTokenTypes, + private tokenModifiers: SemanticTokenModifiers[], + private overrideRange?: Range, + private overrideLength?: number) { super(element); - this.tokenType = tokenType; - this.tokenModifiers = tokenModifiers; - this.overrideRange = overrideRange; - this.overrideLength = overrideLength; } } export class SymbolInformationCapability extends BaseCapability { - private symbolKind: SymbolKind; - get SymbolInformation(): SymbolInformation { const element = this.element as BaseIdentifyableSyntaxElement; return SymbolInformation.create( @@ -115,55 +109,69 @@ export class SymbolInformationCapability extends BaseCapability { ); } - constructor(element: BaseIdentifyableSyntaxElement, symbolKind: SymbolKind) { + constructor(element: BaseIdentifyableSyntaxElement, private symbolKind: SymbolKind) { super(element); - this.symbolKind = symbolKind; } } -interface IdentifierArgs { - element: BaseRuleSyntaxElement, - getNameContext?: () => ParserRuleContext | TerminalNode | null | undefined, - formatName?: (name: string) => string, - defaultName?: string; - defaultRange?: () => Range; -} - export class IdentifierCapability extends BaseCapability { - nameContext: ParserRuleContext | TerminalNode; - range: Range; - name: string; - isDefaultMode: boolean; - - constructor(args: IdentifierArgs) { - super(args.element); + private get unformattedName(): string { + const nameCtx = this.getNameContext ? this.getNameContext() : this.nameContext; + return nameCtx?.getText() ?? this.defaultName ?? "Unknown Element"; + } - this.nameContext = ((args.getNameContext ?? (() => args.element.context.rule))() ?? args.element.context.rule); - this.isDefaultMode = !(!!args.getNameContext && !!args.getNameContext()); + get name(): string { + return this.formatName + ? this.formatName(this.unformattedName) + : this.unformattedName; + } - if (!this.isDefaultMode) { - // Use the context to set the values. - this.name = (args.formatName ?? ((name: string) => name))(this.nameContext.getText()); - this.range = this.nameContext.toRange(args.element.context.document); - } else { - // Use the defaults to set the values. - if (!args.defaultRange) throw new Error("Default range not optional where name context not found."); - this.name = (args.defaultName ?? "Unknown Element"); - this.range = args.defaultRange ? args.defaultRange() : args.element.context.range; + get range(): Range { + if (this.getNameContext) { + const ctx = this.getNameContext(); + if (ctx) { + return ctx.toRange(this.element.context.document); + } else if (this.defaultRange) { + return this.defaultRange(); + } + Services.logger.warn(`Unable to get name context or default for ${this.name}`); } + return this.element.context.range; + } + + get nameContext(): ParserRuleContext | TerminalNode { + return this.getNameContext + ? this.getNameContext() ?? this.element.context.rule + : this.element.context.rule; + } + + get isDefaultMode(): boolean { + return !(!!this.getNameContext && !!this.getNameContext()); + } + + constructor( + readonly element: BaseRuleSyntaxElement, + private getNameContext?: () => ParserRuleContext | TerminalNode | null | undefined, + private formatName?: (name: string) => string, + private defaultName?: string, + private defaultRange?: () => Range + ) { + super(element); } } -export enum ItemType { +export enum ScopeType { /** Base language. */ VBA, /** Application model. */ APPLICATION, /** The user's project. */ PROJECT, - /** Class/Module/Form. */ + /** Class/Form. */ + CLASS, + /** Module. */ MODULE, /** Function declaration. */ FUNCTION, @@ -175,6 +183,8 @@ export enum ItemType { TYPE, /** Variable declaration. */ VARIABLE, + /** A variable declaration in a signature */ + PARAMETER, /** Any reference type that isn't a declaration. */ REFERENCE } @@ -184,7 +194,8 @@ export enum AssignmentType { GET = 1 << 0, LET = 1 << 1, SET = 1 << 2, - CALL = 1 << 3 + CALL = 1 << 3, + CONST = 1 << 4 } export class ScopeItemCapability { @@ -198,30 +209,74 @@ export class ScopeItemCapability { setters?: Map, letters?: Map }; + parameters?: Map; references?: Map; + // Special scope references for easier resolution of names. + implicitDeclarations?: Map; + // Links link?: ScopeItemCapability; - backLinks?: ScopeItemCapability[]; + backlinks?: ScopeItemCapability[]; // Technical isDirty: boolean = true; - private get isMethodScope(): boolean { - return [ - ItemType.SUBROUTINE, - ItemType.FUNCTION, - ItemType.PROPERTY, - ].includes(this.type); + isInvalidated = false; + + get maps() { + const result: Map[] = []; + const addToResult = (map: Map | undefined) => { + if (map) result.push(map); + }; + addToResult(this.types); + addToResult(this.modules); + addToResult(this.functions); + addToResult(this.subroutines); + addToResult(this.properties?.getters); + addToResult(this.properties?.letters); + addToResult(this.properties?.setters); + addToResult(this.references); + + return result; + } + + get explicitDeclarations() { + const result: Map[] = []; + const addToResult = (map: Map | undefined) => { + if (map) result.push(map); + }; + addToResult(this.types); + addToResult(this.modules); + addToResult(this.functions); + addToResult(this.subroutines); + + return result; + } + + get hasScopeBody(): boolean { + return this.type !== ScopeType.VARIABLE + && this.type !== ScopeType.REFERENCE + && this.type !== ScopeType.PARAMETER; + } + + get assignmentTypeText(): string { + const enumNamesAndValues = Object.values(AssignmentType); + const enumValues = enumNamesAndValues.slice(enumNamesAndValues.length / 2 + 1); + const result = `${enumValues.map(x => AssignmentType[this.assignmentType & x as number]).filter(x => x !== 'NONE').join('|')}`; + + return result === '' ? 'NONE' : result; } // Item Properties - explicitSetName?: string; - isPublicScope = false; - visibilityModifierContext?: ParserRuleContext; + locationUri?: string; + isPublicScope?: boolean; + accessMembers?: ParserRuleContext[]; + isOptionExplicitScope = false; + classTypeName?: string; constructor( readonly element?: BaseRuleSyntaxElement, - public type: ItemType = ItemType.REFERENCE, + public type: ScopeType = ScopeType.REFERENCE, public assignmentType: AssignmentType = AssignmentType.NONE, public parent?: ScopeItemCapability, ) { } @@ -230,27 +285,41 @@ export class ScopeItemCapability { * Recursively build from this node down. */ build(): void { - if (this.type === ItemType.REFERENCE) { + this.deleteInvalidatedScopes(); + this.cleanInvalidatedLinks(); + + // Don't build self if invalidated. + if (this.isInvalidated) { + return; + } + + if (this.type === ScopeType.REFERENCE) { // Link to declaration if it exists. this.resolveLinks(); + const abc = 0; if (!this.link) { // TODO: // References to variables should get a diagnostic if they aren't declared. - // -- No option explicit: gets a hint with code action to declare. - // -- Option explicit: gets an error with code action to declare. + // -- No option explicit: Hint with code action to declare. + // GET before declared gets a warning. + // -- Option explicit: Error with code action to declare. // -- Subsequent explicit declaration should raise duplicate declaration (current bahaviour). + // -- All declarations with no GET references get a warning. // References to function or sub calls should raise an error if they aren't declared. // -- Must always throw even when option explicit not present. // -- Nothing required on first reference as declaration may come later. - const diagnosticType = this.assignmentType & AssignmentType.CALL - ? SubOrFunctionNotDefinedDiagnostic - : VariableNotDefinedDiagnostic; - this.pushDiagnostic(diagnosticType); + const severity = this.isOptionExplicitScope + ? DiagnosticSeverity.Error + : DiagnosticSeverity.Hint; + const _ = this.assignmentType & AssignmentType.CALL + ? this.pushDiagnostic(SubOrFunctionNotDefinedDiagnostic, this, this.name) + : this.pushDiagnostic(VariableNotDefinedDiagnostic, this, this.name, severity); } } else { // Diagnostic checks on declarations. - this.resolveDuplicateDeclarations(); - this.resolveShadowedDeclarations(); + const ancestors = this.getParentChain(); + this.resolveDuplicateDeclarations(ancestors); + this.resolveShadowedDeclarations(ancestors); } // Call build on children. @@ -261,14 +330,66 @@ export class ScopeItemCapability { this.properties?.getters?.forEach(items => items.forEach(item => item.build())); this.properties?.letters?.forEach(items => items.forEach(item => item.build())); this.properties?.setters?.forEach(items => items.forEach(item => item.build())); + this.references?.forEach(items => items.forEach(item => item.build())); this.isDirty = false; } + resolveUnused(): void { + // Don't diagnose projects, classes or modules. + // Don't diagnose publically declared items. + // Don't diagnose if we have backlinks. + const isUsed: boolean = this.type === ScopeType.CLASS + || this.type === ScopeType.MODULE + || this.isPublicScope + || (!!this.backlinks && this.backlinks.length > 0) + || !this.element + || !this.element.identifierCapability; + + if (!isUsed) { + const identifier = this.element?.identifierCapability; + const diagnostics = this.element?.diagnosticCapability?.diagnostics; + if (identifier && diagnostics) { + diagnostics.push(new UnusedDiagnostic(identifier.range, identifier.name)); + } + } + + if (!this.hasScopeBody) { + return; + } + + // Recursively call this method on child declarations. + this.types?.forEach(items => items.forEach(item => item.resolveUnused())); + this.modules?.forEach(items => items.forEach(item => item.resolveUnused())); + this.functions?.forEach(items => items.forEach(item => item.resolveUnused())); + this.subroutines?.forEach(items => items.forEach(item => item.resolveUnused())); + this.properties?.getters?.forEach(items => items.forEach(item => item.resolveUnused())); + this.properties?.letters?.forEach(items => items.forEach(item => item.resolveUnused())); + this.properties?.setters?.forEach(items => items.forEach(item => item.resolveUnused())); + } + + /** Returns the chain of parents for bottom up name resolution. */ + getParentChain(items: ScopeItemCapability[] = []): ScopeItemCapability[] { + items.push(this); + return this.parent?.getParentChain(items) ?? items; + } + + /** Prints the hierarchy of scopes from this node down. */ + printToDebug(level: number = 0) { + const p = this.isPublicScope ? '[P] ' : ' '; + Services.logger.debug(`${p}${this.name}`, level); + + this.maps.forEach( + maps => maps.forEach( + items => items.forEach( + item => item.printToDebug(level + 1) + ))); + } + /** Resolves for the current scope, i.e., children of the current item. */ - private resolveDuplicateDeclarations() { + private resolveDuplicateDeclarations(ancestors: ScopeItemCapability[]) { // Reference types are never relevant. - if (this.type == ItemType.REFERENCE) { + if (this.type == ScopeType.REFERENCE) { return; } @@ -340,87 +461,99 @@ export class ScopeItemCapability { }); } - private resolveShadowedDeclaration(item: ScopeItemCapability | undefined): void { - if (item) { - const diagnostic = this.pushDiagnostic(ShadowDeclarationDiagnostic); - this.addDiagnosticReference(diagnostic, item); + private resolveShadowedDeclarations(ancestors: ScopeItemCapability[]) { + // Don't check for shadowed declarations if we're above project level. + if (ancestors.length < 3) { + return; } - } - private resolveShadowedDeclarations() { - // Get the parent of the scope where this element is registered. - const parent = this.parent?.parent; - if (!parent) { - return; + for (let i = 2; i < ancestors.length; i++) { + const ancestor = ancestors[i]; + const shadowing = ancestor + .getAccessibleScopes(this.name) + .filter(item => item.parent?.name !== this.parent?.name); + + if (shadowing.length > 0) { + const diagnostic = this.pushDiagnostic(ShadowDeclarationDiagnostic, this, this.name); + if (diagnostic === undefined) { + continue; + } + shadowing.forEach(item => this.addDiagnosticReference(diagnostic, item)); + } } + } - // All declaration types check for modules. - this.resolveShadowedDeclaration(parent.findType(this.identifier)); - this.resolveShadowedDeclaration(parent.findModule(this.identifier)); - this.resolveShadowedDeclaration(parent.findFunction(this.identifier)); - this.resolveShadowedDeclaration(parent.findSubroutine(this.identifier)); + private resolveLinks() { - // Properties care about everything except properties that - // aren't the same type. Everything else cares about everything. + // Resolve where we have no member access names. + if (!this.accessMembers || this.accessMembers.length === 0) { + const declarations = this.findDeclarations(this.identifier, this.assignmentType); + if (declarations === undefined || declarations.length === 0) { + return; + } - // ToDo: - // Variables are registered as props so should also squash their - // get/set/let diagnostics into one single diagnostic. + if (declarations.length > 1) { + const diagnostic = this.pushDiagnostic(AmbiguousNameDiagnostic, this, this.identifier); + this.addScopesAsRelatedInformation(diagnostic, declarations); + return; + } - // Check get properties. - if (this.assignmentType & AssignmentType.GET) { - this.resolveShadowedDeclaration(parent.findPropertyGetter(this.identifier)); + this.linkThisToItem(declarations[0]); + return; } - // Check let properties. - if (this.assignmentType & AssignmentType.LET) { - this.resolveShadowedDeclaration(parent.findPropertyLetter(this.identifier)); - } + // Resolve for member accessed names. + let foundDeclarations: ScopeItemCapability[] = []; + for (const [i, ctx] of this.accessMembers.entries()) { + // Get the scope item to search and exit if we don't have anything. + const searchScope = i === 0 ? this + : foundDeclarations.length === 0 + ? this.project + : foundDeclarations[0]; + + // Can't do anything more if we don't have a scope to search. + if (searchScope === undefined) { + return; + } - // Check set properties. - if (this.assignmentType & AssignmentType.SET) { - this.resolveShadowedDeclaration(parent.findPropertySetter(this.identifier)); - } - } + // Get the details of what we're searching for. + const name = ctx.getText(); + const assignmentType = i < this.accessMembers.length - 1 + ? AssignmentType.GET + : this.assignmentType; - private resolveLinks() { - /** - * Call Foo(bar) - * ^^^ NONE - * ^^^ GET - * - * Let foo = bar - * ^^^ LET - * ^^^ GET - * - * Set foo = bar - * ^^^ SET - * ^^^ GET - */ - - // Handle calls that aren't assignments. - if (this.assignmentType === AssignmentType.CALL) { - this.linkThisToItem(this.findFunction(this.identifier)); - this.linkThisToItem(this.findSubroutine(this.identifier)); - return; - } + // Search the immediate scope hierarchy if this is the first member. + foundDeclarations = searchScope.findDeclarations(name, assignmentType) ?? []; - // Handle get/set/let relationships. - if (this.assignmentType & AssignmentType.GET) { - this.linkThisToItem(this.findFunction(this.identifier)); - this.linkThisToItem(this.findPropertyGetter(this.identifier)); - return; - } + // If we didn't find anything, try searching the type. + if (foundDeclarations.length === 0 && searchScope.classTypeName !== undefined) { + foundDeclarations = this.project?.findDeclarations(searchScope.classTypeName, assignmentType) ?? []; + foundDeclarations = foundDeclarations[0]?.findDeclarations(name, assignmentType) ?? []; + } - if (this.assignmentType & AssignmentType.LET) { - this.linkThisToItem(this.findPropertyLetter(this.identifier)); - return; - } + // Exactly one means we found something. + if (foundDeclarations.length === 1) { + continue; + } - if (this.assignmentType & AssignmentType.SET) { - this.linkThisToItem(this.findPropertySetter(this.identifier)); - return; + // Nothing found means we can't continue. + if (foundDeclarations.length === 0) { + return; + } + + // More than one declaration is ambiguous. + if (foundDeclarations.length > 1) { + const document = this.element?.context.document; + if (document) { + const diagnostic = new AmbiguousNameDiagnostic(ctx.toRange(document), ''); + this.addScopesAsRelatedInformation(diagnostic, foundDeclarations); + } + return; + } } + + // If we get here, we have resolved the member access name. + this.linkThisToItem(foundDeclarations[0]); } private linkThisToItem(linkItem?: ScopeItemCapability): void { @@ -429,22 +562,67 @@ export class ScopeItemCapability { } this.link = linkItem; - linkItem.backLinks ??= []; - linkItem.backLinks.push(this); + linkItem.backlinks ??= []; + linkItem.backlinks.push(this); } - private removeBacklink(backlinkedItem: ScopeItemCapability): void { - if (!this.backLinks) { - return; + private deleteInvalidatedScopes() { + const removeInvalidatedScopes = (map: Map | undefined) => { + if (!map) return; + + const result = new Map(); + for (const [name, scopes] of map) { + const filteredScopes = scopes.filter(x => !x.isInvalidated); + if (filteredScopes.length > 0) { + result.set(name, filteredScopes); + } + } + if (result.size !== 0) { + return result; + } + }; + + this.types = removeInvalidatedScopes(this.types); + this.modules = removeInvalidatedScopes(this.modules); + this.functions = removeInvalidatedScopes(this.functions); + this.subroutines = removeInvalidatedScopes(this.subroutines); + if (this.properties) { + this.properties.getters = removeInvalidatedScopes(this.properties?.getters); + this.properties.setters = removeInvalidatedScopes(this.properties?.setters); + this.properties.letters = removeInvalidatedScopes(this.properties?.letters); } + this.parameters = removeInvalidatedScopes(this.parameters); + this.references = removeInvalidatedScopes(this.references); + this.implicitDeclarations = removeInvalidatedScopes(this.implicitDeclarations); + } + + private cleanInvalidatedLinks() { + const removeLinks = (map: Map | undefined) => { + map?.forEach((scopes) => scopes.forEach(scope => { + if (scope.link && scope.link.isInvalidated) { + scope.link = undefined; + } + if (scope.backlinks) { + scope.backlinks = scope.backlinks.filter(link => !link.isInvalidated); + if (scope.backlinks.length === 0) scope.backlinks = undefined; + } + })); + }; - const keep = this.backLinks.filter(x => x !== backlinkedItem); - this.backLinks = keep.length === 0 ? undefined : keep; + removeLinks(this.types); + removeLinks(this.modules); + removeLinks(this.functions); + removeLinks(this.subroutines); + removeLinks(this.properties?.getters); + removeLinks(this.properties?.setters); + removeLinks(this.properties?.letters); + removeLinks(this.parameters); + removeLinks(this.references); } /** Returns the module this scope item falls under */ get module(): ScopeItemCapability | undefined { - if (this.type == ItemType.MODULE) { + if (this.type === ScopeType.MODULE || this.type === ScopeType.CLASS) { return this; } return this.parent?.module; @@ -452,23 +630,57 @@ export class ScopeItemCapability { /** Returns the project this scope item falls under */ get project(): ScopeItemCapability | undefined { - if (this.type == ItemType.PROJECT) { + if (this.type == ScopeType.PROJECT) { return this; } return this.parent?.project; } get identifier(): string { - if (this.type === ItemType.PROPERTY) { + if (this.type === ScopeType.PROPERTY) { return this.name.split(' ')[1]; } return this.name; } get name(): string { - return this.explicitSetName - ?? this.element?.identifierCapability?.name - ?? 'Unknown'; + return this.element?.identifierCapability?.name ?? 'Unknown'; + } + + has(identifier: string): boolean { + for (const map of this.maps) { + if (map.has(identifier)) { + return true; + } + } + return false; + } + + /** Get accessible declarations */ + getAccessibleScopes(identifier: string, results: ScopeItemCapability[] = []): ScopeItemCapability[] { + // Add any non-public items we find at this level. + this.maps.forEach(map => { + map.get(identifier)?.forEach(item => { + if (!item.isPublicScope) { + results.push(item); + } + }); + }); + + // Get all public scope types if we're at the project level. + if (this.type === ScopeType.PROJECT) { + this.modules?.forEach(modules => modules.forEach( + module => module.maps.forEach(map => { + map.get(identifier)?.forEach(item => { + if (item.isPublicScope) { + results.push(item); + } + }); + }) + )); + } + + return this.parent?.getAccessibleScopes(identifier, results) ?? results; } findType(identifier: string): ScopeItemCapability | undefined { @@ -506,77 +718,125 @@ export class ScopeItemCapability { ?? this.parent?.findPropertySetter(identifier); } + findDeclarations(identifier: string, assignmentType: AssignmentType): ScopeItemCapability[] | undefined { + const explicitResult = this.explicitDeclarations + .map(x => x.get(identifier)) + .filter(x => !!x) + .flat(); + + if (assignmentType & AssignmentType.GET) { + this.properties?.getters?.get(identifier)?.forEach(x => + explicitResult.push(x) + ); + } + + if (assignmentType & AssignmentType.SET) { + this.properties?.setters?.get(identifier)?.forEach(x => + explicitResult.push(x) + ); + } + + if (assignmentType & AssignmentType.LET) { + this.properties?.letters?.get(identifier)?.forEach(x => + explicitResult.push(x) + ); + } + + if (explicitResult.length > 0) { + return explicitResult; + } + + const implicitResult = this.implicitDeclarations + ?.get(identifier); + + if (implicitResult && implicitResult.length > 0) { + return implicitResult; + } + + return this.parent?.findDeclarations(identifier, assignmentType); + } + /** * Registers a scope and returns the new current scope. * @param item The scope item to register. * @returns The current scope. */ registerScopeItem(item: ScopeItemCapability): ScopeItemCapability { - // ToDo: Get the parent based on visibility. - // Public scoped elements should get the project. - // Check pub/priv declares in same document treated as duplicate instead of shadowed. - - /** - * Visibility on a method-scoped variable does nothing but isn't invalid. - * These should declare as if they're private and raise a warning. - */ - - const getParent = (item: ScopeItemCapability): ScopeItemCapability => - (item.isPublicScope ? this.project : this) ?? this; - - // Method-scoped variables are always private. - if (this.isMethodScope && item.type === ItemType.VARIABLE && item.isPublicScope) { - item.isPublicScope = false; - if (item.visibilityModifierContext && item.element) { - const ctx = item.visibilityModifierContext; - const diagnostic = new MethodVariableIsPublicDiagnostic( - ctx.toRange(item.element.context.document), - ItemType[this.type] - ); - item.element?.diagnosticCapability?.diagnostics.push(diagnostic); - } + // Immediately invalidate if we're an Unknown Module + if (item.type === ScopeType.MODULE && item.name === 'Unknown Module') { + item.isInvalidated = true; } // Set the parent for the item. - item.parent = getParent(item); + item.parent = this; // getParent(item); item.parent.isDirty = true; + // Set the URI from the parent if we don't have one. + if (item.locationUri === undefined) { + item.locationUri = item.parent.locationUri; + } + // Get the scope level for logging. const getAncestorLevel = (item: ScopeItemCapability, level: number): number => item.parent ? getAncestorLevel(item.parent, level + 1) : level; + + + item.isPublicScope = this.getVisibility(item); + const visibility = item.isPublicScope ? 'public' : 'private'; + const assignment = item.assignmentTypeText; const ancestorLevel = getAncestorLevel(this, 0); - Services.logger.debug(`Registering [${item.isPublicScope ? 'public' : 'private'} ${ItemType[item.type]}] ${item.name}`, ancestorLevel); + Services.logger.debug(`Registering [${visibility} ${ScopeType[item.type]} ${assignment}] ${item.name}`, ancestorLevel); + + // Inherit option explicit property. + if (item.parent.isOptionExplicitScope) { + this.isOptionExplicitScope = true; + } // Reference types are not declarations. - if (item.type === ItemType.REFERENCE) { + if (item.type === ScopeType.REFERENCE) { item.parent.references ??= new Map(); item.parent.addItem(item.parent.references, item); return this; } + // Add implicitly accessible names to the project scope. + if (item.isPublicScope && this.project) { + this.project.implicitDeclarations ??= new Map(); + this.addItem(this.project.implicitDeclarations, item, item.name); + } + // Register functions. - if (item.type === ItemType.FUNCTION) { + if (item.type === ScopeType.FUNCTION) { item.parent.functions ??= new Map(); item.parent.addItem(item.parent.functions, item); return item; } // Register subroutine. - if (item.type === ItemType.SUBROUTINE) { + if (item.type === ScopeType.SUBROUTINE) { item.parent.subroutines ??= new Map(); item.parent.addItem(item.parent.subroutines, item); return item; } // Register enum or type. - if (item.type === ItemType.TYPE) { + if (item.type === ScopeType.TYPE) { item.parent.types ??= new Map(); item.parent.addItem(item.parent.types, item); return item; } + // Register parameters. + if (item.type === ScopeType.PARAMETER) { + item.parent.parameters ??= new Map(); + item.parent.addItem(item.parent.parameters, item); + } + // Register properties and variables. - if (item.type === ItemType.PROPERTY || item.type === ItemType.VARIABLE) { + const isGetSetLetType = item.type === ScopeType.PROPERTY + || item.type === ScopeType.VARIABLE + || item.type === ScopeType.PARAMETER; + if (isGetSetLetType) { item.parent.properties ??= {}; if (item.assignmentType & AssignmentType.GET) { item.parent.properties.getters ??= new Map(); @@ -590,11 +850,11 @@ export class ScopeItemCapability { item.parent.properties.setters ??= new Map(); item.parent.addItem(item.parent.properties.setters, item); } - return item.type === ItemType.PROPERTY ? item : this; + return item.type === ScopeType.PROPERTY ? item : this; } // Handle module registration - if (item.type === ItemType.MODULE) { + if (item.type === ScopeType.MODULE || item.type === ScopeType.CLASS) { item.parent.modules ??= new Map(); item.parent.addItem(item.parent.modules, item); return item; @@ -604,70 +864,166 @@ export class ScopeItemCapability { return item; } - // Would be relatively simple to also do this via a "dirty" flag. - /** Removes all elements with references to the document uri. */ - invalidate(uri: string): void { - const unlink = (item: ScopeItemCapability): void => { - // Remove backlink from linked item. - item.link?.removeBacklink(item); - // Remove link from any backlinked items. - item.backLinks?.forEach(node => node.link = undefined); - }; + invalidateModule(uri: string): void { + const module = this.findModuleByUri(uri); + module?.invalidate(); + } - const scan = (map: Map | undefined, uri: string) => { - if (map === undefined) { - return; - } + invalidate(): void { + this.isInvalidated = true; + this.maps.forEach( + map => map.forEach( + scopes => scopes.forEach( + scope => scope.invalidate() + ))); + } + + /** Returns true for public and false for private */ + private getVisibility(item: ScopeItemCapability): boolean { + // Classes and modules are always public. + if (item.parent?.type === ScopeType.PROJECT) { + return true; + } + + // Module members can explicitly set their access or default + // to private. TODO: multi-project requires another scope layer + // to control access between projects. + // Public members of Option Private Modules are scoped to project. + if (item.parent?.type === ScopeType.MODULE) { + // Variables default to private, everything else deafults public. + return item.isPublicScope ?? item.type !== ScopeType.VARIABLE; + } + + // Everything else is private. + return false; + } - const keys = Array.from(map.keys()); - keys.forEach(key => { - const items = map.get(key)!; - const keep = items.filter(item => item.element?.context.document.uri !== uri); - const remove = items.filter(item => item.element?.context.document.uri === uri); + private findModuleByUri(uri: string): ScopeItemCapability | undefined { + const moduleName = uri.split('/').at(-1)?.split('.').slice(0, -1).join('.'); + if (!moduleName) { + Services.logger.error(`Bad URI or name: ${moduleName} from ${uri}`); + return; + } - // Sever links for items to be removed. - remove.forEach(x => unlink(x)); + const modules = this.modules?.get(moduleName); + if (!modules) { + Services.logger.error(`No such module: ${moduleName}`); + return; + } - // Update the map. - if (keep.length === 0) { - map.delete(key); - } else { - map.set(key, keep); + if (modules.length > 1) { + Services.logger.error(`Module name ambiguity: ${modules.length} found.`); + return; + } + + return modules[0]; + } + + private getItemsIdentifiedAtPosition(position: Position, results: ScopeItemCapability[] = [], searchItems: ScopeItemCapability[] = []): void { + while (searchItems.length > 0) { + const scope = searchItems.pop(); + + // Check all items for whether they have a name overlap or a scope overlap. + scope?.maps.forEach(map => map.forEach(items => items.forEach(item => { + const elementRange = item.element?.context.range; + const identifierRange = item.element?.identifierCapability?.range; + if (identifierRange && isPositionInsideRange(position, identifierRange)) { + // Position is inside the identifier, push to results. + results.push(item); + } else if (elementRange && isPositionInsideRange(position, elementRange)) { + // Position is inside element, queue to be searched. + searchItems.push(item); } - }); - }; + }))); + } + } + + getRenameItems(uri: string, position: Position): ScopeItemCapability[] { + const module = this.findModuleByUri(uri); + if (!module) { + return []; + } + + const itemsAtPosition: ScopeItemCapability[] = []; + this.getItemsIdentifiedAtPosition(position, itemsAtPosition, [module]); + if (itemsAtPosition.length === 0) { + Services.logger.warn(`Nothing to rename.`); + return []; + } + + // Switch to the linked declaration if we have one. + const swapRefsForDeclarations = itemsAtPosition.map( + item => item.link ? item.link : item); + + // Replace property items with all properties of same name. + const propertyIncludedItems = swapRefsForDeclarations.map(item => + item.type === ScopeType.PROPERTY && item.parent?.properties + ? [ + item.parent.properties.getters?.get(item.identifier), + item.parent.properties.setters?.get(item.identifier), + item.parent.properties.letters?.get(item.identifier) + ] + : item + ).flat().flat().flat().filter(x => !!x); + + // Add backlinks for each item. + const addedBacklinks = propertyIncludedItems.map(item => + item.backlinks ? [item, ...item.backlinks] : item + ).flat().flat(); + + const uniqueItemsAtPosition = this.removeDuplicatesByRange(addedBacklinks); + return uniqueItemsAtPosition; + } + + getDeclarationLocation(uri: string, position: Position): LocationLink[] | undefined { + const module = this.findModuleByUri(uri); + if (!module) { + return; + } + + const itemsAtPosition: ScopeItemCapability[] = []; + this.getItemsIdentifiedAtPosition(position, itemsAtPosition, [module]); + return itemsAtPosition.map(x => x.toLocationLink()).filter(x => !!x); + } + + toLocationLink(): LocationLink | undefined { + const link = this.link; + + if (!link || !link.locationUri || !link.element || !link.element.identifierCapability) { + return; + } + + return LocationLink.create( + link.locationUri, + link.element.context.range, + link.element.identifierCapability.range, + this.element?.context.range + ); + } - // Invalidate and unlink items. - scan(this.types, uri); - scan(this.modules, uri); - scan(this.functions, uri); - scan(this.subroutines, uri); - if (this.properties !== undefined) { - scan(this.properties.getters, uri); - scan(this.properties.letters, uri); - scan(this.properties.setters, uri); - } - scan(this.references, uri); - - // Call invalidate on children. - this.types?.forEach(items => items.forEach(item => item.invalidate(uri))); - this.modules?.forEach(items => items.forEach(item => item.invalidate(uri))); - this.functions?.forEach(items => items.forEach(item => item.invalidate(uri))); - this.subroutines?.forEach(items => items.forEach(item => item.invalidate(uri))); - this.properties?.getters?.forEach(items => items.forEach(item => item.invalidate(uri))); - this.properties?.letters?.forEach(items => items.forEach(item => item.invalidate(uri))); - this.properties?.setters?.forEach(items => items.forEach(item => item.invalidate(uri))); - } - - private addItem(target: Map, item: ScopeItemCapability): void { + private addItem(target: Map, item: ScopeItemCapability, name?: string): void { const items = target.get(item.identifier) ?? []; items.push(item); - target.set(item.identifier, items); + target.set(name ?? item.identifier, items); + } + + private hasDiagnostic(diagnostic: BaseDiagnostic): boolean { + const diagnostics = this.element?.diagnosticCapability?.diagnostics; + if (!diagnostics || diagnostics.length === 0) { + return false; + } + + for (const pushedDiagnostic of diagnostics) { + if (diagnostic.equals(pushedDiagnostic)) { + return true; + } + } + return false; } /** * Generates and pushes a diagnostic to the underlying element on the scope item. - * No diagnostic is created unless we have both a range and the capability on the element. + * No diagnostic is created unless we have a range, the capability on the element, and no duplicate exists. * Range is automatically injected into the constructor but arguments can't be verified at compile time, so it's on you to check. * @param ctor The diagnostic we're creating. * @param item The scope item to get the range from. @@ -679,8 +1035,42 @@ export class ScopeItemCapability { const diagnostics = (item ?? this).element?.diagnosticCapability?.diagnostics; if (range && diagnostics) { const diagnostic = new ctor(...[range, args].flat()); + if (this.hasDiagnostic(diagnostic)) { + return; + } diagnostics.push(diagnostic); return diagnostic; } } + + private addScopesAsRelatedInformation(diagnostic: BaseDiagnostic | undefined, items: ScopeItemCapability[]): void { + if (diagnostic === undefined) { + return; + } + + items.forEach(item => { + const ctx = item.element?.context; + if (ctx === undefined) { + return; + } + diagnostic.addRelatedInformation({ + message: "Related Information", + location: { + uri: ctx.document.uri, + range: ctx.range + } + }); + }); + } + + private removeDuplicatesByRange(results: ScopeItemCapability[]): ScopeItemCapability[] { + const rangeString = (r: Range | undefined): string => { + if (!r) return 'null'; + return `${r.start.line}.${r.start.character}:${r.end.line}.${r.end.character}`; + }; + + const m = new Map(); + results.forEach(s => m.set(rangeString(s.element?.context.range), s)); + return Array.from(m.values()); + } } \ No newline at end of file diff --git a/server/src/capabilities/codeActions.ts b/server/src/capabilities/codeActions.ts index 3c3dde3..b668f89 100644 --- a/server/src/capabilities/codeActions.ts +++ b/server/src/capabilities/codeActions.ts @@ -1,4 +1,7 @@ +// Core import { CodeAction, Command, Diagnostic } from "vscode-languageserver"; + +// Project import { BaseDiagnostic } from "./diagnostics"; import { Services } from "../injection/services"; @@ -33,19 +36,19 @@ export class CodeActionsRegistry { // Example params that are related to a diagnostic. const onCodeActionParams = { - "textDocument":{ - "uri":"file:///c%3A/Repos/vba-LanguageServer/sample/b.bas" + "textDocument": { + "uri": "file:///c%3A/Repos/vba-LanguageServer/sample/b.bas" }, - "range":{"start":{"line":4,"character":1},"end":{"line":4,"character":1}}, - "context":{ - "diagnostics":[{ - "range":{"start":{"line":4,"character":1},"end":{"line":4,"character":1}}, - "message":"Option Explicit is missing from module header.", - "data":{"uri":"file:///c%3A/Repos/vba-LanguageServer/sample/b.bas"}, - "code":"W001", - "severity":2 + "range": { "start": { "line": 4, "character": 1 }, "end": { "line": 4, "character": 1 } }, + "context": { + "diagnostics": [{ + "range": { "start": { "line": 4, "character": 1 }, "end": { "line": 4, "character": 1 } }, + "message": "Option Explicit is missing from module header.", + "data": { "uri": "file:///c%3A/Repos/vba-LanguageServer/sample/b.bas" }, + "code": "W001", + "severity": 2 }], - "only":["quickfix"], - "triggerKind":1 + "only": ["quickfix"], + "triggerKind": 1 } }; diff --git a/server/src/capabilities/diagnostics.ts b/server/src/capabilities/diagnostics.ts index 4dd28d8..309a70c 100644 --- a/server/src/capabilities/diagnostics.ts +++ b/server/src/capabilities/diagnostics.ts @@ -32,6 +32,15 @@ export abstract class BaseDiagnostic implements Diagnostic { } this.relatedInformation.push(information); } + + equals(diagnostic: Diagnostic) { + return this.severity === diagnostic.severity + && this.message === diagnostic.message + && this.range.end.line === diagnostic.range.end.line + && this.range.start.line === diagnostic.range.start.line + && this.range.end.character === diagnostic.range.end.character + && this.range.start.character === diagnostic.range.start.character; + } } @@ -78,28 +87,37 @@ export class DuplicateDeclarationDiagnostic extends BaseDiagnostic { } +// test +export class AmbiguousNameDiagnostic extends BaseDiagnostic { + severity = DiagnosticSeverity.Error; + constructor(range: Range, message: string) { + super(range); + this.message = `Ambiguous name detected: '${message}'.`; + } +} + + // test export class ShadowDeclarationDiagnostic extends BaseDiagnostic { - message = "Declaration is shadowed in the local scope."; severity = DiagnosticSeverity.Warning; - constructor(range: Range) { + constructor(range: Range, message: string) { super(range); + this.message = `${message} is shadowed in the local scope.`; } } export class VariableNotDefinedDiagnostic extends BaseDiagnostic { - message = "Variable not defined."; - severity = DiagnosticSeverity.Error; - constructor(range: Range) { + constructor(range: Range, message: string, public severity: DiagnosticSeverity) { super(range); + this.message = `Variable ${message} not defined.`; } } export class SubOrFunctionNotDefinedDiagnostic extends BaseDiagnostic { - message = "Sub or Function not defined."; severity = DiagnosticSeverity.Error; - constructor(range: Range) { + constructor(range: Range, message: string) { super(range); + this.message = `Method ${message} not defined.`; } } @@ -123,10 +141,9 @@ export class MethodVariableIsPublicDiagnostic extends BaseDiagnostic { // test export class UnexpectedLineEndingDiagnostic extends BaseDiagnostic { - message = "Unexpected line ending."; severity = DiagnosticSeverity.Error; constructor(range: Range) { - super(range); + super(range, 'Unexpected line ending.'); } } @@ -135,22 +152,31 @@ export class UnreachableCodeDiagnostic extends BaseDiagnostic { severity = DiagnosticSeverity.Hint; tags = [DiagnosticTag.Unnecessary]; constructor(range: Range) { - super(range); + super(range, 'Unreachable code detected.'); + } +} + +// test +export class UnusedDiagnostic extends BaseDiagnostic { + severity = DiagnosticSeverity.Hint; + tags = [DiagnosticTag.Unnecessary]; + constructor(range: Range, message: string) { + super(range, `${message} is declared but its value is never read.`); } } -export class IgnoredAttributeDiagnostic extends BaseDiagnostic { - severity = DiagnosticSeverity.Warning; +export class UnknownAttributeDiagnostic extends BaseDiagnostic { + severity = DiagnosticSeverity.Error; constructor(range: Range, attributeName: string) { - super(range, `Unknown attribute '${attributeName}' will be ignored.`); + super(range, `Unknown attribute '${attributeName}'.`); } } export class MissingOptionExplicitDiagnostic extends BaseDiagnostic { message = "Option Explicit is missing from module header."; - severity = DiagnosticSeverity.Warning; + severity = DiagnosticSeverity.Hint; constructor(range: Range) { super(range); @@ -159,10 +185,14 @@ export class MissingOptionExplicitDiagnostic extends BaseDiagnostic { this.actionFactory = (diagnostic: Diagnostic, uri: string) => CodeAction.create( "Insert Option Explicit", - { changes: { [uri]: [{ - range: diagnostic.range, - newText: "\nOption Explicit" - }]}}, + { + changes: { + [uri]: [{ + range: diagnostic.range, + newText: "\nOption Explicit" + }] + } + }, CodeActionKind.QuickFix ); diff --git a/server/src/capabilities/folding.ts b/server/src/capabilities/folding.ts index 2456032..922a1df 100644 --- a/server/src/capabilities/folding.ts +++ b/server/src/capabilities/folding.ts @@ -1,3 +1,4 @@ +// Core import { FoldingRange as VscFoldingRange, Range } from 'vscode-languageserver'; /** diff --git a/server/src/capabilities/semanticTokens.ts b/server/src/capabilities/semanticTokens.ts index c6ddc8f..1f2abe4 100644 --- a/server/src/capabilities/semanticTokens.ts +++ b/server/src/capabilities/semanticTokens.ts @@ -2,8 +2,6 @@ import { InitializeResult, Range, - SemanticTokenModifiers, - SemanticTokenTypes, uinteger, SemanticTokens } from 'vscode-languageserver'; @@ -14,6 +12,45 @@ import { ParserRuleContext } from 'antlr4ng/dist/ParserRuleContext'; // Project import { BaseRuleSyntaxElement, HasSemanticTokenCapability } from '../project/elements/base'; +export enum SemanticTokenTypes { + namespace = "namespace", + type = "type", + class = "class", + enum = "enum", + interface = "interface", + struct = "struct", + typeParameter = "typeParameter", + parameter = "parameter", + variable = "variable", + property = "property", + enumMember = "enumMember", + event = "event", + function = "function", + method = "method", + macro = "macro", + keyword = "keyword", + modifier = "modifier", + comment = "comment", + string = "string", + number = "number", + regexp = "regexp", + operator = "operator", + decorator = "decorator" +} + +export enum SemanticTokenModifiers { + declaration = "declaration", + definition = "definition", + readonly = "readonly", + static = "static", + deprecated = "deprecated", + abstract = "abstract", + async = "async", + modification = "modification", + documentation = "documentation", + defaultLibrary = "defaultLibrary" +} + const registeredTokenTypes = new Map((Object.keys(SemanticTokenTypes) as (keyof typeof SemanticTokenTypes)[]).map((k, i) => ([k, i]))); const registeredTokenModifiers = new Map((Object.keys(SemanticTokenModifiers) as (keyof typeof SemanticTokenModifiers)[]).map((k, i) => ([k, 2 ** i]))); @@ -33,18 +70,17 @@ type SemanticElementType = HasSemanticTokenCapability & BaseRuleSyntaxElement; export class SemanticToken { - line: uinteger; - char: uinteger; - length: uinteger; tokenType: uinteger; tokenModifiers: uinteger = 0; - element: SemanticElementType; - constructor(element: SemanticElementType, line: uinteger, startChar: uinteger, length: uinteger, tokenType: SemanticTokenTypes, tokenModifiers: SemanticTokenModifiers[]) { - this.element = element; - this.line = line; - this.char = startChar; - this.length = length; + constructor( + public element: SemanticElementType, + public line: uinteger, + public char: uinteger, + public length: uinteger, + tokenType: SemanticTokenTypes, + tokenModifiers: SemanticTokenModifiers[] + ) { this.tokenType = registeredTokenTypes.get(tokenType)!; tokenModifiers.forEach((x) => this.tokenModifiers += registeredTokenModifiers.get(x) ?? 0); } diff --git a/server/src/extensions/antlrCoreExtensions.ts b/server/src/extensions/antlrCoreExtensions.ts new file mode 100644 index 0000000..d067897 --- /dev/null +++ b/server/src/extensions/antlrCoreExtensions.ts @@ -0,0 +1,153 @@ +// Core +import { Range } from 'vscode-languageserver'; +import { TextDocument } from 'vscode-languageserver-textdocument'; + +// Antlr +import { ParserRuleContext, TerminalNode } from 'antlr4ng'; + +// Project +import { + EndOfStatementContext, + EndOfStatementNoWsContext, + ProcedureTailContext +} from '../antlr/out/vbaParser'; +import { LineEndingContext } from '../antlr/out/vbafmtParser'; + + +declare module 'antlr4ng' { + interface ParserRuleContext { + /** Convert the node to a range. */ + toRange(doc: TextDocument): Range; + startIndex(): number; + stopIndex(): number; + hasPositionOf(ctx: ParserRuleContext): boolean; + endsWithLineEnding: boolean; + countTrailingLineEndings(): number; + } + + interface TerminalNode { + /** Convert the node to a range. */ + toRange(doc: TextDocument): Range; + startIndex(): number; + stopIndex(): number; + } +} + +ParserRuleContext.prototype.toRange = function (doc: TextDocument): Range { + const startIndex = this.start?.start ?? 0; + const stopIndex = this.stop?.stop ?? startIndex; + return Range.create( + doc.positionAt(startIndex), + doc.positionAt(stopIndex + 1) + ); +}; + +ParserRuleContext.prototype.startIndex = function (): number { + return this.start?.start ?? 0; +}; + +ParserRuleContext.prototype.stopIndex = function (): number { + return this.stop?.stop ?? this.startIndex(); +}; + +ParserRuleContext.prototype.hasPositionOf = function (ctx: ParserRuleContext): boolean { + return this.startIndex() === ctx.startIndex() && this.stopIndex() === ctx.stopIndex(); +}; + +Object.defineProperty(ParserRuleContext.prototype, 'endsWithLineEnding', { + get: function endsWithLineEnding() { + // Ensure we have a context. + if (!(this instanceof ParserRuleContext)) + return false; + + // Check last child is a line ending. + const child = this.children.at(-1); + if (!child) + return false; + + // Check the various line ending contexts. + if (child instanceof LineEndingContext) + return true; + if (child instanceof EndOfStatementContext) + return true; + if (child instanceof EndOfStatementNoWsContext) + return true; + if (child instanceof ProcedureTailContext) + return true; + + // Run it again! + if (child.getChildCount() > 0) + return (child as ParserRuleContext).endsWithLineEnding; + + // Not a line ending and no more children. + return false; + } +}); + +interface LineEndingParserRuleContext { + NEWLINE(): TerminalNode | null; +} + +function isLineEndingParserRuleContext(ctx: unknown): ctx is LineEndingParserRuleContext { + return typeof ctx === 'object' + && ctx !== null + && typeof (ctx as any).NEWLINE === 'function'; +} + +function countTrailingLineEndings(ctx: ParserRuleContext): number { + // This function recursively loops through last child of + // the context to find one that has a NEWLINE terminal node. + + // Check if we have a NEWLINE node. + if (isLineEndingParserRuleContext(ctx)) { + const lines = ctx.NEWLINE()?.getText(); + if (!lines) { + return 0; + } + + let i = 0; + let result = 0; + while (i < lines.length) { + const char = lines[i]; + + if (char === '\r') { + result++; + i += lines[i + 1] === '\n' ? 2 : 1; + } else if (char === '\n') { + result++; + i++; + } + } + + return result; + } + + // Recursive call on last child. + const lastChild = ctx.children.at(-1); + if (lastChild instanceof ParserRuleContext) { + return countTrailingLineEndings(lastChild); + } + + // If we get here, we have no trailing lines. + return 0; +} + +ParserRuleContext.prototype.countTrailingLineEndings = function (): number { + return countTrailingLineEndings(this); +}; + + +TerminalNode.prototype.toRange = function (doc: TextDocument): Range { + return Range.create( + doc.positionAt(this.startIndex()), + doc.positionAt(this.stopIndex() + 1) + ); +}; + +TerminalNode.prototype.startIndex = function (): number { + return this.getPayload()?.start ?? 0; +}; + +TerminalNode.prototype.stopIndex = function (): number { + return this.getPayload()?.stop ?? this.startIndex(); +}; \ No newline at end of file diff --git a/server/src/extensions/antlrVbaParserExtensions.ts b/server/src/extensions/antlrVbaParserExtensions.ts new file mode 100644 index 0000000..f4c37b7 --- /dev/null +++ b/server/src/extensions/antlrVbaParserExtensions.ts @@ -0,0 +1,288 @@ +// Core +import { SymbolKind } from 'vscode-languageserver'; + +// Project +import { + AmbiguousIdentifierContext, + ArrayClauseContext, + ArrayDesignatorContext, + ArrayDimContext, + AsClauseContext, + ConstItemContext, + LowerBoundContext, + PositionalParamContext, + TypeExpressionContext, + TypeSuffixContext, + UpperBoundContext, + VariableDclContext, + WitheventsVariableDclContext +} from '../antlr/out/vbaParser'; +import { ParserRuleContext } from 'antlr4ng'; + +export type ArrayDimension = { + lowerBound: number; + upperBound: number; +} + +declare module '../antlr/out/vbaParser' { + + interface ConstItemContext { + /** Shortcut to get the type context */ + typeContext(): ParserRuleContext | undefined; + /** Extension method to get the symbol kind. */ + toSymbolKind(): SymbolKind; + } + + interface VariableDclContext { + /** Shortcut the identifier as we know it will always exist. */ + ambiguousIdentifier(): AmbiguousIdentifierContext; + /** Shortcut to get the type context */ + typeContext(): ParserRuleContext | undefined; + /** Extension method to get the symbol kind. */ + toSymbolKind(): SymbolKind; + } + + interface WitheventsVariableDclContext { + /** Shortcut the identifier as we know it will always exist. */ + ambiguousIdentifier(): AmbiguousIdentifierContext; + /** Shortcut to get the type context */ + typeContext(): ParserRuleContext | undefined; + /** Extension method to get the symbol kind. */ + toSymbolKind(): SymbolKind; + } + + interface PositionalParamContext { + /** Shortcut the identifier as we know it will always exist. */ + ambiguousIdentifier(): AmbiguousIdentifierContext; + /** Shortcut to get the type context */ + typeContext(): ParserRuleContext | undefined; + /** Extension method to get the symbol kind. */ + toSymbolKind(): SymbolKind; + } + + interface AsClauseContext { + isObject: boolean; + isVariant: boolean; + isPrimative: boolean; + classTypeName: string | undefined; + } + + interface TypeExpressionContext { + isObject: boolean; + isVariant: boolean; + isPrimative: boolean; + classTypeName: string | undefined; + } + + interface TypeSuffixContext { + isObject: boolean; + isVariant: boolean; + isPrimative: boolean; + classTypeName: string | undefined; + } + + interface ArrayDesignatorContext { + isResizable: boolean; + getDimensions(): ArrayDimension[] | undefined; + } + + interface ArrayDimContext { + isResizable: boolean; + getDimensions(): ArrayDimension[] | undefined; + } +} + +ConstItemContext.prototype.typeContext = function (): ParserRuleContext | undefined { + return this.typeSuffix() + ?? this.constAsClause()?.builtinType(); +}; + +VariableDclContext.prototype.ambiguousIdentifier = function (): AmbiguousIdentifierContext { + // A variable will always be typed or untyped. + return this.typedVariableDcl()?.typedName().ambiguousIdentifier() + ?? this.untypedVariableDcl()!.ambiguousIdentifier(); +}; + + +VariableDclContext.prototype.typeContext = function (): ParserRuleContext | undefined { + return this.typedVariableDcl()?.typedName().typeSuffix() + ?? this.untypedVariableDcl()?.asClause()?.asType()?.typeSpec() + ?? this.untypedVariableDcl()?.asClause()?.asAutoObject()?.classTypeName(); +}; + +PositionalParamContext.prototype.ambiguousIdentifier = function (): AmbiguousIdentifierContext { + return this.paramDcl().untypedNameParamDcl()?.ambiguousIdentifier() + ?? this.paramDcl().typedNameParamDcl()!.typedName().ambiguousIdentifier()!; +}; + +PositionalParamContext.prototype.typeContext = function (): ParserRuleContext | undefined { + return this.paramDcl().untypedNameParamDcl()?.parameterType()?.typeExpression() + ?? this.paramDcl().typedNameParamDcl()?.typedName().typeSuffix(); +}; + + +// Type Properties + +Object.defineProperty(TypeExpressionContext.prototype, "isPrimative", { + get: function (this: TypeExpressionContext): boolean { + return !!this.builtinType() && !this.isVariant; + } +}); + +Object.defineProperty(TypeExpressionContext.prototype, "isVariant", { + get: function (this: TypeExpressionContext): boolean { + return !!this.builtinType()?.reservedTypeIdentifier()?.VARIANT() + || !!this.builtinType()?.reservedTypeIdentifierB()?.VARIANT_B(); + } +}); + +Object.defineProperty(TypeExpressionContext.prototype, "isObject", { + get: function (this: TypeExpressionContext): boolean { + return !this.isPrimative && !this.isVariant; + } +}); + +Object.defineProperty(TypeExpressionContext.prototype, "classTypeName", { + get: function (this: TypeExpressionContext): string | undefined { + return this.definedTypeExpression()?.getText(); + } +}); + +Object.defineProperty(TypeSuffixContext.prototype, "isPrimative", { + get: function (this: TypeSuffixContext): boolean { + return true; + } +}); + +Object.defineProperty(TypeSuffixContext.prototype, "isVariant", { + get: function (this: TypeSuffixContext): boolean { + return false; + } +}); + +Object.defineProperty(TypeSuffixContext.prototype, "isObject", { + get: function (this: TypeSuffixContext): boolean { + return false; + } +}); + +Object.defineProperty(TypeSuffixContext.prototype, "classTypeName", { + get: function (this: TypeSuffixContext): string | undefined { + return undefined; + } +}); + +Object.defineProperty(AsClauseContext.prototype, "isPrimative", { + get: function (this: AsClauseContext): boolean { + const typeSpecContext = this.asType()?.typeSpec(); + return !!(typeSpecContext + && (typeSpecContext.fixedLengthStringSpec() || typeSpecContext.typeExpression()?.isPrimative) + ); + } +}); + +Object.defineProperty(AsClauseContext.prototype, "isVariant", { + get: function (this: AsClauseContext): boolean { + return this.asType()?.typeSpec().typeExpression()?.isVariant ?? false; + } +}); + +Object.defineProperty(AsClauseContext.prototype, "isObject", { + get: function (this: AsClauseContext): boolean { + const isAutoObject = !!this.asAutoObject(); + const isTypeExprObject = !!(this.asType()?.typeSpec().typeExpression() ?? false); + return isAutoObject || isTypeExprObject; + } +}); + +Object.defineProperty(AsClauseContext.prototype, "classTypeName", { + get: function (this: AsClauseContext): string | undefined { + return this.isObject + ? this.asType()?.typeSpec().getText() + ?? this.asAutoObject()?.classTypeName().getText() + : undefined; + } +}); + +Object.defineProperty(ArrayDesignatorContext.prototype, "isResizable", { + get: function (this: ArrayDesignatorContext): boolean { + return false; + } +}); + +ArrayDesignatorContext.prototype.getDimensions = function (): ArrayDimension[] | undefined { + return undefined; +}; + +Object.defineProperty(ArrayClauseContext.prototype, "isResizable", { + get: function (this: ArrayClauseContext): boolean { + return !!this.arrayDim().boundsList(); + } +}); + +ArrayDimContext.prototype.getDimensions = function (): ArrayDimension[] | undefined { + const boundsListCtx = this.boundsList(); + const getNum = (boundCtx: LowerBoundContext | UpperBoundContext | null): number => { + const FIXME_DEFAULT = 0; + const n = boundCtx?.constantExpression().expression().literalExpression()?.INTEGERLITERAL()?.getText(); + return n === undefined ? FIXME_DEFAULT : Number.parseInt(n); + }; + return !boundsListCtx ? undefined : boundsListCtx.dimSpec().map(dimensionCtx => { + return { + lowerBound: getNum(dimensionCtx.lowerBound()), + upperBound: getNum(dimensionCtx.upperBound()) + }; + }); +}; + + +// SymbolKind + +ConstItemContext.prototype.toSymbolKind = function (): SymbolKind { + return toSymbolKind(this.typeContext()); +}; + + +WitheventsVariableDclContext.prototype.toSymbolKind = function (): SymbolKind { + return toSymbolKind(this.typeContext()); +}; + + +VariableDclContext.prototype.toSymbolKind = function (): SymbolKind { + return toSymbolKind(this.typeContext()); +}; + +PositionalParamContext.prototype.toSymbolKind = function (): SymbolKind { + return toSymbolKind(this.typeContext()); +}; + + +function toSymbolKind(context: ParserRuleContext | undefined): SymbolKind { + switch (context?.getText().toLocaleLowerCase()) { + case undefined: + return SymbolKind.Class; + case 'boolean': + return SymbolKind.Boolean; + case '$': // string + case 'byte': + case 'string': + return SymbolKind.String; + case '%': // integer + case '&': // long + case '^': // longlong + case '@': // decimal + case '!': // single + case '#': // double + case 'double': + case 'currency': + case 'integer': + case 'long': + case 'longPtr': + case 'longLong': + return SymbolKind.Number; + case 'object': + return SymbolKind.Object; + default: + return SymbolKind.Class; + } +} diff --git a/server/src/extensions/antlrVbaPreParserExtensions.ts b/server/src/extensions/antlrVbaPreParserExtensions.ts new file mode 100644 index 0000000..992afbb --- /dev/null +++ b/server/src/extensions/antlrVbaPreParserExtensions.ts @@ -0,0 +1,17 @@ +// Project +import { CompilerConditionalStatementContext } from '../antlr/out/vbapreParser'; + + +declare module '../antlr/out/vbapreParser' { + interface CompilerConditionalStatementContext { + vbaExpression(): string; + } +} + + +CompilerConditionalStatementContext.prototype.vbaExpression = function (): string { + return (this.compilerIfStatement() ?? this.compilerElseIfStatement())! + .booleanExpression() + .getText() + .toLowerCase(); +}; diff --git a/server/src/extensions/parserExtensions.ts b/server/src/extensions/parserExtensions.ts deleted file mode 100644 index 482da9e..0000000 --- a/server/src/extensions/parserExtensions.ts +++ /dev/null @@ -1,301 +0,0 @@ -// Core -import { Range, SymbolKind } from 'vscode-languageserver'; -import { TextDocument } from 'vscode-languageserver-textdocument'; - -// Antlr -import { ParserRuleContext, TerminalNode } from 'antlr4ng'; - -// Project -import { CompilerConditionalStatementContext } from '../antlr/out/vbapreParser'; -import { - AmbiguousIdentifierContext, - BuiltinTypeContext, - ClassTypeNameContext, - ConstItemContext, - EndOfStatementContext, - EndOfStatementNoWsContext, - ProcedureTailContext, - TypeSpecContext, - TypeSuffixContext, - VariableDclContext, - WitheventsVariableDclContext -} from '../antlr/out/vbaParser'; -import { LineEndingContext } from '../antlr/out/vbafmtParser'; - - -declare module 'antlr4ng' { - interface ParserRuleContext { - /** Convert the node to a range. */ - toRange(doc: TextDocument): Range; - startIndex(): number; - stopIndex(): number; - hasPositionOf(ctx: ParserRuleContext): boolean; - endsWithLineEnding: boolean; - countTrailingLineEndings(): number; - } - - interface TerminalNode { - /** Convert the node to a range. */ - toRange(doc: TextDocument): Range; - startIndex(): number; - stopIndex(): number; - } -} - - -declare module '../antlr/out/vbapreParser' { - interface CompilerConditionalStatementContext { - vbaExpression(): string; - } -} - - -declare module '../antlr/out/vbaParser' { - - interface ConstItemContext { - /** Shortcut the identifier as we know it will always exist. */ - ambiguousIdentifier(): AmbiguousIdentifierContext; - /** Shortcut to get the type context */ - typeContext(): BuiltinTypeContext | TypeSuffixContext | undefined; - /** Extension method to get the symbol kind. */ - toSymbolKind(): SymbolKind; - } - - interface VariableDclContext { - /** Shortcut the identifier as we know it will always exist. */ - ambiguousIdentifier(): AmbiguousIdentifierContext; - /** Shortcut to get the type context */ - typeContext(): TypeSpecContext | TypeSuffixContext | ClassTypeNameContext | undefined; - /** Extension method to get the symbol kind. */ - toSymbolKind(): SymbolKind; - } - - interface WitheventsVariableDclContext { - /** Shortcut the identifier as we know it will always exist. */ - ambiguousIdentifier(): AmbiguousIdentifierContext; - /** Shortcut to get the type context */ - typeContext(): TypeSpecContext | TypeSuffixContext | ClassTypeNameContext | undefined; - /** Extension method to get the symbol kind. */ - toSymbolKind(): SymbolKind; - } -} - - -ParserRuleContext.prototype.toRange = function (doc: TextDocument): Range { - const startIndex = this.start?.start ?? 0; - const stopIndex = this.stop?.stop ?? startIndex; - return Range.create( - doc.positionAt(startIndex), - doc.positionAt(stopIndex + 1) - ); -}; - -ParserRuleContext.prototype.startIndex = function (): number { - return this.start?.start ?? 0; -}; - -ParserRuleContext.prototype.stopIndex = function (): number { - return this.stop?.stop ?? this.startIndex(); -}; - -ParserRuleContext.prototype.hasPositionOf = function (ctx: ParserRuleContext): boolean { - return this.startIndex() === ctx.startIndex() && this.stopIndex() === ctx.stopIndex(); -}; - -Object.defineProperty(ParserRuleContext.prototype, 'endsWithLineEnding', { - get: function endsWithLineEnding() { - // Ensure we have a context. - if (!(this instanceof ParserRuleContext)) - return false; - - // Check last child is a line ending. - const child = this.children.at(-1); - if (!child) - return false; - - // Check the various line ending contexts. - if (child instanceof LineEndingContext) - return true; - if (child instanceof EndOfStatementContext) - return true; - if (child instanceof EndOfStatementNoWsContext) - return true; - if (child instanceof ProcedureTailContext) - return true; - - // Run it again! - if (child.getChildCount() > 0) - return (child as ParserRuleContext).endsWithLineEnding; - - // Not a line ending and no more children. - return false; - } -}); - -interface LineEndingParserRuleContext { - NEWLINE(): TerminalNode | null; -} - -function isLineEndingParserRuleContext(ctx: unknown): ctx is LineEndingParserRuleContext { - return typeof ctx === 'object' - && ctx !== null - && typeof (ctx as any).NEWLINE === 'function'; -} - -function countTrailingLineEndings(ctx: ParserRuleContext): number { - // This function recursively loops through last child of - // the context to find one that has a NEWLINE terminal node. - - // Check if we have a NEWLINE node. - if (isLineEndingParserRuleContext(ctx)) { - const lines = ctx.NEWLINE()?.getText(); - if (!lines) { - return 0; - } - - let i = 0; - let result = 0; - while (i < lines.length) { - const char = lines[i]; - - if (char === '\r') { - result++; - i += lines[i + 1] === '\n' ? 2 : 1; - } else if (char === '\n') { - result++; - i++; - } - } - - return result; - } - - // Recursive call on last child. - const lastChild = ctx.children.at(-1); - if (lastChild instanceof ParserRuleContext) { - return countTrailingLineEndings(lastChild); - } - - // If we get here, we have no trailing lines. - return 0; -} - -ParserRuleContext.prototype.countTrailingLineEndings = function (): number { - return countTrailingLineEndings(this); -}; - - -TerminalNode.prototype.toRange = function (doc: TextDocument): Range { - return Range.create( - doc.positionAt(this.startIndex()), - doc.positionAt(this.stopIndex() + 1) - ); -}; - -TerminalNode.prototype.startIndex = function (): number { - return this.getPayload()?.start ?? 0; -}; - -TerminalNode.prototype.stopIndex = function (): number { - return this.getPayload()?.stop ?? this.startIndex(); -}; - - -CompilerConditionalStatementContext.prototype.vbaExpression = function (): string { - return (this.compilerIfStatement() ?? this.compilerElseIfStatement())! - .booleanExpression() - .getText() - .toLowerCase(); -}; - - - -VariableDclContext.prototype.ambiguousIdentifier = function (): AmbiguousIdentifierContext { - // A variable will always be typed or untyped. - return this.typedVariableDcl()?.typedName().ambiguousIdentifier() - ?? this.untypedVariableDcl()!.ambiguousIdentifier(); -}; - - -VariableDclContext.prototype.typeContext = function (): TypeSpecContext | TypeSuffixContext | ClassTypeNameContext | undefined { - return this.typedVariableDcl()?.typedName().typeSuffix() - ?? this.untypedVariableDcl()?.asClause()?.asType()?.typeSpec() - ?? this.untypedVariableDcl()?.asClause()?.asAutoObject()?.classTypeName(); -}; - - -// SymbolKind - -ConstItemContext.prototype.toSymbolKind = function (): SymbolKind { - return toSymbolKind(this.typeContext()); -}; - - -WitheventsVariableDclContext.prototype.toSymbolKind = function (): SymbolKind { - return toSymbolKind(this.typeContext()); -}; - - -VariableDclContext.prototype.toSymbolKind = function (): SymbolKind { - return toSymbolKind(this.typeContext()); -}; - - -function toSymbolKind(context: BuiltinTypeContext | TypeSuffixContext | TypeSpecContext | ClassTypeNameContext | undefined): SymbolKind { - switch (context?.getText().toLocaleLowerCase()) { - case undefined: - return SymbolKind.Class; - case 'boolean': - return SymbolKind.Boolean; - case '$': // string - case 'byte': - case 'string': - return SymbolKind.String; - case '%': // integer - case '&': // long - case '^': // longlong - case '@': // decimal - case '!': // single - case '#': // double - case 'double': - case 'currency': - case 'integer': - case 'long': - case 'longPtr': - case 'longLong': - return SymbolKind.Number; - case 'object': - return SymbolKind.Object; - default: - return SymbolKind.Class; - } -} - -/** - * const File: 1; - const Module: 2; - const Namespace: 3; - const Package: 4; - const Class: 5; - const Method: 6; - const Property: 7; - const Field: 8; - const Constructor: 9; - const Enum: 10; - const Interface: 11; - const Function: 12; - const Variable: 13; - const Constant: 14; - const String: 15; - const Number: 16; - const Boolean: 17; - const Array: 18; - const Object: 19; - const Key: 20; - const Null: 21; - const EnumMember: 22; - const Struct: 23; - const Event: 24; - const Operator: 25; - const TypeParameter: 26; - */ \ No newline at end of file diff --git a/server/src/extensions/stringExtensions.ts b/server/src/extensions/stringExtensions.ts index f8fb6fb..7f48100 100644 --- a/server/src/extensions/stringExtensions.ts +++ b/server/src/extensions/stringExtensions.ts @@ -1,9 +1,27 @@ -interface String { - stripQuotes(): string; -} +// Core +import { fileURLToPath, pathToFileURL } from "url"; +declare global { + interface String { + stripQuotes(): string; + toFilePath(): string; + toFileUri(): string; + } +} String.prototype.stripQuotes = function (): string { const exp = /^"?(.*?)"?$/; return exp.exec(this.toString())![1]; }; + +String.prototype.toFilePath = function (): string { + return this.startsWith('file://') + ? fileURLToPath(this.toString()) + : this.toString(); +}; + +String.prototype.toFileUri = function (): string { + return !this.startsWith('file://') + ? pathToFileURL(this.toString()).href + : this.toString(); +}; diff --git a/server/src/injection/interface.ts b/server/src/injection/interface.ts index 5e11959..3ff1ce3 100644 --- a/server/src/injection/interface.ts +++ b/server/src/injection/interface.ts @@ -1,15 +1,19 @@ +// Core +import { TextDocument } from 'vscode-languageserver-textdocument'; +import { CancellationToken, WorkspaceFolder } from 'vscode-languageserver'; + +// Project import { LanguageServerConfiguration } from '../server'; import { BaseProjectDocument } from '../project/document'; -import { TextDocument } from 'vscode-languageserver-textdocument'; import { VbaFmtListener } from '../project/parser/vbaListener'; -import { CancellationToken } from 'vscode-languageserver'; + export interface Logger { - error(msg: string, lvl?: number): void; - warn(msg: string, lvl?: number): void; - info(msg: string, lvl?: number): void; - log(msg: string, lvl?: number): void; - debug(msg: string, lvl?: number): void; + error(msg: string, lvl?: number, e?: unknown): void; + warn(msg: string, lvl?: number, e?: unknown): void; + info(msg: string, lvl?: number, e?: unknown): void; + log(msg: string, lvl?: number, e?: unknown): void; + debug(msg: string, lvl?: number, e?: unknown): void; stack(e: Error): void; } @@ -40,4 +44,5 @@ export interface IWorkspace { parseDocument(projectDocument: BaseProjectDocument): Promise; openDocument(document: TextDocument): void; closeDocument(document: TextDocument): void; + addWorkspaceFolder(params: WorkspaceFolder): void; } \ No newline at end of file diff --git a/server/src/injection/services.ts b/server/src/injection/services.ts index 8cf22a0..39ecdd9 100644 --- a/server/src/injection/services.ts +++ b/server/src/injection/services.ts @@ -1,9 +1,12 @@ +// Core import { container, InjectionToken } from 'tsyringe'; -import { Logger, IWorkspace, ILanguageServer } from './interface'; -import { LspLogger } from '../utils/logger'; import { _Connection, createConnection, ProposedFeatures } from 'vscode-languageserver/node'; -import { ScopeItemCapability } from '../capabilities/capabilities'; + +// Project +import { LspLogger } from '../utils/logger'; +import { Logger, IWorkspace, ILanguageServer } from './interface'; import { CodeActionsRegistry } from '../capabilities/codeActions'; +import { ScopeItemCapability } from '../capabilities/capabilities'; export class Services { diff --git a/server/src/project/document.ts b/server/src/project/document.ts index 31d3458..fb6a941 100644 --- a/server/src/project/document.ts +++ b/server/src/project/document.ts @@ -6,31 +6,30 @@ import { CancellationToken, Diagnostic, SymbolInformation, SymbolKind } from 'vs import { ParserRuleContext } from 'antlr4ng'; // Project +import { Services } from '../injection/services'; +import { IWorkspace } from '../injection/interface'; import { Dictionary } from '../utils/helpers'; import { SyntaxParser } from './parser/vbaParser'; import { FoldingRange } from '../capabilities/folding'; +import { VbaFmtListener } from './parser/vbaListener'; +import { BaseDiagnostic } from '../capabilities/diagnostics'; import { SemanticTokensManager } from '../capabilities/semanticTokens'; +import { ScopeItemCapability } from '../capabilities/capabilities'; import { BaseRuleSyntaxElement, BaseSyntaxElement, - DeclarableElement, HasDiagnosticCapability, HasFoldingRangeCapability, HasScopeItemCapability, HasSemanticTokenCapability, HasSymbolInformationCapability } from './elements/base'; - import { PropertyDeclarationElement, PropertyGetDeclarationElement, PropertyLetDeclarationElement, PropertySetDeclarationElement } from './elements/procedure'; -import { VbaFmtListener } from './parser/vbaListener'; -import { Services } from '../injection/services'; -import { IWorkspace } from '../injection/interface'; -import { ScopeItemCapability } from '../capabilities/capabilities'; // TODO --------------------------------------------- @@ -78,35 +77,6 @@ export abstract class BaseProjectDocument { return this.subtractTextFromRanges(this.redactedElements.map(x => x.context.range)); } - // async getDocumentConfiguration(): Promise { - // // Get the stored configuration. - // if (this.documentConfiguration) { - // return this.documentConfiguration; - // } - - // // Get the configuration from the client. - // if (this.workspace.hasConfigurationCapability) { - // this.documentConfiguration = await this.workspace.requestDocumentSettings(this.textDocument.uri); - // if (this.documentConfiguration) { - // return this.documentConfiguration; - // } - // } - - // // Use the defaults. - // this.documentConfiguration = { - // maxDocumentLines: 1500, - // maxNumberOfProblems: 100, - // doWarnOptionExplicitMissing: true, - // environment: { - // os: "Win64", - // version: "Vba7" - // } - // }; - // return this.documentConfiguration; - // } - - // clearDocumentConfiguration = () => this.documentConfiguration = undefined; - constructor(name: string, document: TextDocument) { this.textDocument = document; this.workspace = Services.workspace; @@ -164,23 +134,27 @@ export abstract class BaseProjectDocument { // }; // } languageServerDiagnostics() { + const diagnostics = this.isClosed ? [] : this.diagnostics; + Services.logger.debug(`Sending diagnostics for ${this.textDocument.uri}`); + Services.logger.debug(JSON.stringify(diagnostics)); + return { uri: this.textDocument.uri, version: this.version, - diagnostics: this.isClosed ? [] : this.diagnostics + diagnostics: diagnostics }; } async formatParseVisit(token: CancellationToken): Promise { try { - return await (new SyntaxParser(Services.logger)).formatParseAsync(token, this); + return await (new SyntaxParser(Services.logger)).formatParse(token, this); } catch (e) { Services.logger.debug('caught doc'); throw e; } } - async parseAsync(token: CancellationToken): Promise { + async parse(token: CancellationToken): Promise { // Don't parse oversize documents. if (await this.isOversize) { Services.logger.debug(`Document oversize: ${this.textDocument.lineCount} lines.`); @@ -190,20 +164,29 @@ export abstract class BaseProjectDocument { } // Parse the document. - await (new SyntaxParser(Services.logger)).parseAsync(token, this); + await (new SyntaxParser(Services.logger)).parse(token, this); const projectScope = this.currentScope.project; const buildScope = projectScope?.isDirty ? projectScope : this.currentScope; buildScope.build(); + buildScope.resolveUnused(); // Evaluate the diagnostics. - this.diagnostics = this.hasDiagnosticElements + const diagnostics = this.hasDiagnosticElements .map(e => e.diagnosticCapability.evaluate()) .flat(); + // Ensure diagnostics aren't reported twice. + // TODO: Redesign diagnostics so this isn't required. + diagnostics.forEach(diagnostic => { + if (!this.hasDiagnostic(diagnostic)) { + this.diagnostics.push(diagnostic); + } + }); + this._isBusy = false; }; - async formatParseAsync(token: CancellationToken): Promise { + async formatParse(token: CancellationToken): Promise { // Don't parse oversize documents. if (await this.isOversize) { Services.logger.debug(`Document oversize: ${this.textDocument.lineCount} lines.`); @@ -212,7 +195,7 @@ export abstract class BaseProjectDocument { } // Parse the document. - return await (new SyntaxParser(Services.logger)).formatParseAsync(token, this); + return await (new SyntaxParser(Services.logger)).formatParse(token, this); } /** @@ -316,6 +299,15 @@ export abstract class BaseProjectDocument { return result.join('\r\n'); } } + + private hasDiagnostic(diagnostic: BaseDiagnostic): boolean { + for (const pushedDiagnostic of this.diagnostics) { + if (diagnostic.equals(pushedDiagnostic)) { + return true; + } + } + return false; + } } diff --git a/server/src/project/elements/base.ts b/server/src/project/elements/base.ts index 1026e0f..0a053db 100644 --- a/server/src/project/elements/base.ts +++ b/server/src/project/elements/base.ts @@ -3,7 +3,7 @@ import { Position, Range } from 'vscode-languageserver'; import { TextDocument } from 'vscode-languageserver-textdocument'; // Antlr -import { Parser, ParserRuleContext, TerminalNode } from 'antlr4ng'; +import { ParserRuleContext, TerminalNode } from 'antlr4ng'; // Project import { @@ -33,7 +33,7 @@ export abstract class BaseSyntaxElement { */ constructor() { let x: TerminalNode | ParserRuleContext; - + } } diff --git a/server/src/project/elements/flow.ts b/server/src/project/elements/flow.ts index 1228cc1..4d00190 100644 --- a/server/src/project/elements/flow.ts +++ b/server/src/project/elements/flow.ts @@ -9,7 +9,7 @@ import { DiagnosticCapability, FoldingRangeCapability } from '../../capabilities import { BaseRuleSyntaxElement, HasDiagnosticCapability } from './base'; import { MultipleOperatorsDiagnostic, WhileWendDeprecatedDiagnostic } from '../../capabilities/diagnostics'; -export class IfElseBlock extends BaseRuleSyntaxElement { +export class IfElseBlockElement extends BaseRuleSyntaxElement { constructor(context: IfStatementContext, document: TextDocument) { super(context, document); this.foldingRangeCapability = new FoldingRangeCapability(this); diff --git a/server/src/project/elements/generic.ts b/server/src/project/elements/generic.ts index 57e9b5d..3e66dab 100644 --- a/server/src/project/elements/generic.ts +++ b/server/src/project/elements/generic.ts @@ -2,10 +2,10 @@ import { TextDocument } from 'vscode-languageserver-textdocument'; // Antlr -import { ErrorNode, ParserRuleContext, TerminalNode } from 'antlr4ng'; +import { ErrorNode, TerminalNode } from 'antlr4ng'; // Project -import { BaseRuleSyntaxElement, BaseSyntaxElement, Context } from './base'; +import { BaseSyntaxElement, Context } from './base'; import { DiagnosticCapability } from '../../capabilities/capabilities'; import { ParserErrorDiagnostic } from '../../capabilities/diagnostics'; diff --git a/server/src/project/elements/module.ts b/server/src/project/elements/module.ts index c8bdc2f..6277b78 100644 --- a/server/src/project/elements/module.ts +++ b/server/src/project/elements/module.ts @@ -3,7 +3,7 @@ import { TextDocument } from 'vscode-languageserver-textdocument'; import { Diagnostic, Range, SymbolKind } from 'vscode-languageserver'; // Antlr -import { ParserRuleContext } from 'antlr4ng'; +import { ParserRuleContext, TerminalNode } from 'antlr4ng'; import { ClassModuleCodeElementContext, ClassModuleContext, @@ -17,8 +17,8 @@ import { // Project import { BaseRuleSyntaxElement, BaseIdentifyableSyntaxElement, HasDiagnosticCapability } from './base'; -import { DiagnosticCapability, IdentifierCapability, ItemType, ScopeItemCapability, SymbolInformationCapability } from '../../capabilities/capabilities'; -import { DuplicateAttributeDiagnostic, IgnoredAttributeDiagnostic, MissingAttributeDiagnostic, MissingOptionExplicitDiagnostic } from '../../capabilities/diagnostics'; +import { DiagnosticCapability, IdentifierCapability, ScopeType, ScopeItemCapability, SymbolInformationCapability } from '../../capabilities/capabilities'; +import { DuplicateAttributeDiagnostic, UnknownAttributeDiagnostic, MissingAttributeDiagnostic, MissingOptionExplicitDiagnostic } from '../../capabilities/diagnostics'; interface DocumentSettings { @@ -29,7 +29,7 @@ interface DocumentSettings { abstract class BaseModuleElement extends BaseIdentifyableSyntaxElement { abstract attrubutes: ParserRuleContext[]; abstract diagnosticCapability: DiagnosticCapability; - abstract hasOptionExplicit: boolean; + abstract scopeItemCapability: ScopeItemCapability; settings: DocumentSettings; symbolInformationCapability: SymbolInformationCapability; @@ -38,7 +38,6 @@ abstract class BaseModuleElement extends BaseIdenti super(ctx, doc); this.settings = documentSettings; this.symbolInformationCapability = new SymbolInformationCapability(this, symbolKind); - this.scopeItemCapability = new ScopeItemCapability(this, ItemType.MODULE); } // Helpers @@ -60,7 +59,7 @@ abstract class BaseModuleElement extends BaseIdenti } protected addOptionExplicitMissingDiagnostic(diagnostics: Diagnostic[], header: ClassModuleHeaderContext | ProceduralModuleHeaderContext): void { - if (this.settings.doWarnOptionExplicitMissing && !this.hasOptionExplicit) { + if (this.settings.doWarnOptionExplicitMissing && !this.scopeItemCapability.isOptionExplicitScope) { const startLine = header.stop?.line ?? 0 + 1; diagnostics.push(new MissingOptionExplicitDiagnostic( Range.create(startLine, 1, startLine, 1) @@ -104,32 +103,38 @@ abstract class BaseModuleElement extends BaseIdenti export class ModuleElement extends BaseModuleElement { diagnosticCapability: DiagnosticCapability; identifierCapability: IdentifierCapability; + scopeItemCapability: ScopeItemCapability; attrubutes: ParserRuleContext[]; - hasOptionExplicit: boolean; constructor(ctx: ProceduralModuleContext, doc: TextDocument, documentSettings: DocumentSettings) { super(ctx, doc, documentSettings, SymbolKind.File); this.attrubutes = ctx.proceduralModuleHeader().proceduralModuleAttr(); this.diagnosticCapability = new DiagnosticCapability(this); + this.scopeItemCapability = new ScopeItemCapability(this, ScopeType.MODULE); + this.scopeItemCapability.locationUri = doc.uri.toFileUri(); - this.hasOptionExplicit = this.evaluateHasOptionExplicit(ctx + this.scopeItemCapability.isOptionExplicitScope = this.evaluateHasOptionExplicit(ctx .proceduralModuleBody() .proceduralModuleCode() .proceduralModuleCodeElement()); - this.identifierCapability = new IdentifierCapability({ - element: this, - formatName: (x: string) => x.stripQuotes(), - defaultName: 'Unknown Module', - defaultRange: () => Range.create(this.context.range.start, this.context.range.start), - getNameContext: () => ctx - .proceduralModuleHeader() - .proceduralModuleAttr() - .map(x => x.nameAttr()) - .filter(x => !!x)[0] - ?.STRINGLITERAL() - }); + const getIdentifierNameContext = () => this.context.rule + .proceduralModuleHeader() + .proceduralModuleAttr() + .map(x => x.nameAttr()) + .filter(x => !!x)[0] + ?.STRINGLITERAL(); + const getIdentifierFormattedName = (x: string) => x.stripQuotes(); + const getIdentifierDefaultRange = () => Range.create(this.context.range.start, this.context.range.start); + + this.identifierCapability = new IdentifierCapability( + this, + getIdentifierNameContext, + getIdentifierFormattedName, + 'Unknown Module', + getIdentifierDefaultRange + ); this.resolveConfiguration( this.diagnosticCapability.diagnostics, @@ -142,9 +147,9 @@ export class ModuleElement extends BaseModuleElement { export class ClassElement extends BaseModuleElement { diagnosticCapability: DiagnosticCapability; identifierCapability: IdentifierCapability; + scopeItemCapability: ScopeItemCapability; attrubutes: ParserRuleContext[]; - hasOptionExplicit: boolean; constructor(ctx: ClassModuleContext, doc: TextDocument, documentSettings: DocumentSettings) { super(ctx, doc, documentSettings, SymbolKind.File); @@ -154,26 +159,31 @@ export class ClassElement extends BaseModuleElement { ctx.classModuleHeader().ignoredClassAttr() ].flat(); this.diagnosticCapability = new DiagnosticCapability(this); + this.scopeItemCapability = new ScopeItemCapability(this, ScopeType.CLASS); + this.scopeItemCapability.locationUri = doc.uri.toFileUri(); - this.hasOptionExplicit = this.evaluateHasOptionExplicit(ctx + this.scopeItemCapability.isOptionExplicitScope = this.evaluateHasOptionExplicit(ctx .classModuleBody() .classModuleCode() .classModuleCodeElement()); - let nameContextGetter; + let getIdentifierNameContext = (): TerminalNode | undefined => undefined; if (ctx.classModuleHeader().nameAttr().length > 0) { - nameContextGetter = () => ctx + getIdentifierNameContext = () => ctx .classModuleHeader() .nameAttr()[0] .STRINGLITERAL(); } - this.identifierCapability = new IdentifierCapability({ - element: this, - formatName: (x: string) => x.stripQuotes(), - defaultName: 'Unknown Class', - defaultRange: () => Range.create(this.context.range.start, this.context.range.start), - getNameContext: nameContextGetter - }); + const getIdentifierFormattedName = (x: string) => x.stripQuotes(); + const getIdentifierDefaultRange = () => Range.create(this.context.range.start, this.context.range.start); + + this.identifierCapability = new IdentifierCapability( + this, + getIdentifierNameContext, + getIdentifierFormattedName, + 'Unknown Class', + getIdentifierDefaultRange + ); this.resolveConfiguration( this.diagnosticCapability.diagnostics, @@ -189,7 +199,7 @@ export class ModuleIgnoredAttributeElement extends BaseRuleSyntaxElement { - this.diagnosticCapability.diagnostics.push(new IgnoredAttributeDiagnostic( + this.diagnosticCapability.diagnostics.push(new UnknownAttributeDiagnostic( this.context.range, this.context.text.split(' ')[1] )); return this.diagnosticCapability.diagnostics; diff --git a/server/src/project/elements/naming.ts b/server/src/project/elements/naming.ts new file mode 100644 index 0000000..81f7fc3 --- /dev/null +++ b/server/src/project/elements/naming.ts @@ -0,0 +1,84 @@ +// Core +import { TextDocument } from "vscode-languageserver-textdocument"; + +// Antlr +import { ParserRuleContext } from "antlr4ng"; +import { + AmbiguousIdentifierContext, + DictionaryAccessExpressionContext, + IndexExpressionContext, + LExpressionContext, + MemberAccessExpressionContext, + PositionalParamContext, + SimpleNameExpressionContext, + UnrestrictedNameContext, + WithMemberAccessExpressionContext, + WithStatementContext +} from "../../antlr/out/vbaParser"; + +// Project +import { BaseRuleSyntaxElement } from "./base"; +import { AssignmentType, IdentifierCapability, ScopeType, ScopeItemCapability } from "../../capabilities/capabilities"; + + +export class WithStatementElement extends BaseRuleSyntaxElement { + nameExpressionElement?: NameExpressionElement; + + get nameStack(): ParserRuleContext[] { + return this.nameExpressionElement?.nameStack ?? []; + } + + constructor(ctx: WithStatementContext, doc: TextDocument) { + super(ctx, doc); + } +} + + +export type NameExpressionContext = LExpressionContext + | SimpleNameExpressionContext + | MemberAccessExpressionContext + | PositionalParamContext + | IndexExpressionContext + | WithMemberAccessExpressionContext + | DictionaryAccessExpressionContext; + +export class NameExpressionElement extends BaseRuleSyntaxElement { + scopeItemCapability: ScopeItemCapability; + identifierCapability: IdentifierCapability; + + nameContexts: (SimpleNameExpressionContext | UnrestrictedNameContext | AmbiguousIdentifierContext)[] = []; + withStatementElement?: WithStatementElement; + + get hasNames(): boolean { + return this.nameContexts.length > 0; + } + + get nameStack(): ParserRuleContext[] { + return [(this.withStatementElement?.nameStack ?? []), this.nameContexts].flat(); + } + + get accessMembers(): ParserRuleContext[] | undefined { + const names = this.nameStack; + if (names.length > 1) { + return names; + } + } + + constructor(ctx: NameExpressionContext, doc: TextDocument) { + super(ctx, doc); + this.identifierCapability = new IdentifierCapability(this, () => this.nameStack.at(-1)); + this.scopeItemCapability = new ScopeItemCapability(this, ScopeType.REFERENCE, AssignmentType.GET); + } + + setAsCallType = () => this.scopeItemCapability.assignmentType = AssignmentType.CALL; + setAsLetType = () => this.scopeItemCapability.assignmentType = AssignmentType.LET; + setAsSetType = () => this.scopeItemCapability.assignmentType = AssignmentType.SET; + + addName = (ctx: SimpleNameExpressionContext | UnrestrictedNameContext | AmbiguousIdentifierContext) => { + this.nameContexts.push(ctx); + }; + + evaluateNameStack(): void { + this.scopeItemCapability.accessMembers = this.accessMembers; + } +} \ No newline at end of file diff --git a/server/src/project/elements/procedure.ts b/server/src/project/elements/procedure.ts index 769ebf4..a55473c 100644 --- a/server/src/project/elements/procedure.ts +++ b/server/src/project/elements/procedure.ts @@ -16,7 +16,7 @@ import { // Project import { BaseRuleSyntaxElement, HasDiagnosticCapability, HasSymbolInformationCapability } from './base'; -import { AssignmentType, DiagnosticCapability, FoldingRangeCapability, IdentifierCapability, ItemType, ScopeItemCapability, SymbolInformationCapability } from '../../capabilities/capabilities'; +import { AssignmentType, DiagnosticCapability, FoldingRangeCapability, IdentifierCapability, ScopeType, ScopeItemCapability, SymbolInformationCapability } from '../../capabilities/capabilities'; interface HasProcedureScope { procedureScope(): ProcedureScopeContext | null @@ -55,16 +55,12 @@ export class SubDeclarationElement extends BaseProcedureElement ctx.subroutineName()?.ambiguousIdentifier(), - // For some reason the IdentifierCapability throws if no default is given - // despite it not actually ever needing it. Most unusual. - defaultRange: () => this.context.range - }); + + const getIdentifierNameContext = () => ctx.subroutineName()?.ambiguousIdentifier(); + this.identifierCapability = new IdentifierCapability(this, getIdentifierNameContext); this.foldingRangeCapability.openWord = `Sub ${this.identifierCapability.name}`; this.foldingRangeCapability.closeWord = 'End Sub'; - this.scopeItemCapability.type = ItemType.SUBROUTINE; + this.scopeItemCapability.type = ScopeType.SUBROUTINE; } } @@ -74,13 +70,11 @@ export class FunctionDeclarationElement extends BaseProcedureElement ctx.functionName()?.ambiguousIdentifier(), - }); + const getIdentifierNameContext = () => ctx.functionName()?.ambiguousIdentifier(); + this.identifierCapability = new IdentifierCapability(this, getIdentifierNameContext); this.foldingRangeCapability.openWord = `Function ${this.identifierCapability.name}`; this.foldingRangeCapability.closeWord = 'End Function'; - this.scopeItemCapability.type = ItemType.FUNCTION; + this.scopeItemCapability.type = ScopeType.FUNCTION; } } @@ -117,11 +111,13 @@ abstract class BasePropertyDeclarationElement this.nameContext, - formatName: (x: string) => `${this.propertyType} ${x}` - }); + const getIdentifierNameContext = () => this.nameContext; + const getIdentifierFormattedName = (x: string) => `${this.propertyType} ${x}`; + this.identifierCapability = new IdentifierCapability( + this, + getIdentifierNameContext, + getIdentifierFormattedName + ); } } @@ -131,7 +127,7 @@ export class PropertyGetDeclarationElement extends BasePropertyDeclarationElemen super(ctx, doc, 'Get', ctx.functionName()?.ambiguousIdentifier() ?? undefined); this.foldingRangeCapability.openWord = `Get Property ${this.identifierCapability.name}`; this.foldingRangeCapability.closeWord = 'End Property'; - this.scopeItemCapability.type = ItemType.PROPERTY; + this.scopeItemCapability.type = ScopeType.PROPERTY; this.scopeItemCapability.assignmentType = AssignmentType.GET; } } @@ -142,7 +138,7 @@ export class PropertySetDeclarationElement extends BasePropertyDeclarationElemen super(ctx, doc, 'Set', ctx.subroutineName()?.ambiguousIdentifier() ?? undefined); this.foldingRangeCapability.openWord = `Set Property ${this.identifierCapability.name}`; this.foldingRangeCapability.closeWord = 'End Property'; - this.scopeItemCapability.type = ItemType.PROPERTY; + this.scopeItemCapability.type = ScopeType.PROPERTY; this.scopeItemCapability.assignmentType = AssignmentType.SET; } } @@ -153,7 +149,7 @@ export class PropertyLetDeclarationElement extends BasePropertyDeclarationElemen super(ctx, doc, 'Let', ctx.subroutineName()?.ambiguousIdentifier() ?? undefined); this.foldingRangeCapability.openWord = `Let Property ${this.identifierCapability.name}`; this.foldingRangeCapability.closeWord = 'End Property'; - this.scopeItemCapability.type = ItemType.PROPERTY; + this.scopeItemCapability.type = ScopeType.PROPERTY; this.scopeItemCapability.assignmentType = AssignmentType.LET; } } \ No newline at end of file diff --git a/server/src/project/elements/typing.ts b/server/src/project/elements/typing.ts index 667af9f..ebb6a5e 100644 --- a/server/src/project/elements/typing.ts +++ b/server/src/project/elements/typing.ts @@ -1,18 +1,21 @@ // Core import { TextDocument } from 'vscode-languageserver-textdocument'; -import { SemanticTokenModifiers, SemanticTokenTypes, SymbolKind } from 'vscode-languageserver'; +import { SymbolKind } from 'vscode-languageserver'; // Antlr import { ParserRuleContext } from 'antlr4ng'; import { + ArrayDesignatorContext, ArrayDimContext, AsClauseContext, ConstDeclarationContext, ConstItemContext, EnumDeclarationContext, EnumMemberContext, + PositionalParamContext, PublicEnumDeclarationContext, PublicTypeDeclarationContext, + TypeExpressionContext, TypeSuffixContext, UdtDeclarationContext, UnrestrictedNameContext, @@ -22,9 +25,10 @@ import { } from '../../antlr/out/vbaParser'; // Project -import { ElementOutOfPlaceDiagnostic, LegacyFunctionalityDiagnostic } from '../../capabilities/diagnostics'; +import { ElementOutOfPlaceDiagnostic, LegacyFunctionalityDiagnostic, UnusedDiagnostic } from '../../capabilities/diagnostics'; import { BaseRuleSyntaxElement, HasDiagnosticCapability, HasSemanticTokenCapability, HasSymbolInformationCapability } from './base'; -import { AssignmentType, DiagnosticCapability, IdentifierCapability, ItemType, ScopeItemCapability, SemanticTokenCapability, SymbolInformationCapability } from '../../capabilities/capabilities'; +import { AssignmentType, DiagnosticCapability, IdentifierCapability, ScopeType, ScopeItemCapability, SemanticTokenCapability, SymbolInformationCapability } from '../../capabilities/capabilities'; +import { SemanticTokenModifiers, SemanticTokenTypes } from '../../capabilities/semanticTokens'; abstract class BaseTypeDeclarationElement extends BaseRuleSyntaxElement implements HasDiagnosticCapability, HasSymbolInformationCapability, HasSemanticTokenCapability { @@ -42,7 +46,7 @@ abstract class BaseTypeDeclarationElement extends B this.semanticTokenCapability = new SemanticTokenCapability(this, tokenType, tokenModifiers ?? []); // An enum is public unless explicitly set to private. - this.scopeItemCapability = new ScopeItemCapability(this, ItemType.TYPE); + this.scopeItemCapability = new ScopeItemCapability(this, ScopeType.TYPE); this.scopeItemCapability.isPublicScope = this.isPublicScope; } @@ -61,10 +65,9 @@ export class EnumDeclarationElement extends BaseTypeDeclarationElement ctx.untypedName().ambiguousIdentifier() - }); + + const getIdentifierNameContext = () => ctx.untypedName().ambiguousIdentifier(); + this.identifierCapability = new IdentifierCapability(this, getIdentifierNameContext); if (isAfterProcedure) this.diagnosticCapability.diagnostics.push( new ElementOutOfPlaceDiagnostic(this.context.range, "Enum declaration") ); @@ -81,11 +84,9 @@ export class EnumMemberDeclarationElement extends BaseRuleSyntaxElement ctx.untypedName() - }); - this.scopeItemCapability = new ScopeItemCapability(this, ItemType.VARIABLE, AssignmentType.GET); + const getIdentifierNameContext = () => ctx.untypedName(); + this.identifierCapability = new IdentifierCapability(this, getIdentifierNameContext); + this.scopeItemCapability = new ScopeItemCapability(this, ScopeType.VARIABLE, AssignmentType.GET); } } @@ -99,10 +100,8 @@ export class TypeDeclarationElement extends BaseTypeDeclarationElement ctx.untypedName() - }); + const getIdentifierNameContext = () => ctx.untypedName(); + this.identifierCapability = new IdentifierCapability(this, getIdentifierNameContext); } } @@ -129,7 +128,7 @@ export class VariableDeclarationStatementElement extends BaseRuleSyntaxElement ctx.ambiguousIdentifier() }); - + this.identifierCapability = new IdentifierCapability(this, () => ctx.ambiguousIdentifier()); + // VariableDcl > TypedVariableDcl > TypedName > TypeSuffix // > UntypedVariableDcl > AsClause @@ -186,29 +185,18 @@ export class VariableDeclarationElement extends BaseRuleSyntaxElement { +class VariableTypeInformation extends BaseRuleSyntaxElement { get isObjectType(): boolean { - // Type hints are never an object. - const ctx = this.context.rule; - if (ctx instanceof TypeSuffixContext) { - return false; - } + return this.context.rule.isObject; + } - // Check builtins for variant type. - const builtin = ctx.asType()?.typeSpec().typeExpression()?.builtinType(); - if (builtin?.reservedTypeIdentifier()?.VARIANT() || builtin?.reservedTypeIdentifierB()?.VARIANT_B()) { - return true; - } + get isPrimativeType(): boolean { + return this.context.rule.isPrimative; + } - // Don't trust anything else. Just check not a primative. - return !this.isPrimativeType; + get isVariantType(): boolean { + return this.context.rule.isVariant; } - get isPrimativeType(): boolean { - // Type hints are always primitive. - const ctx = this.context.rule; - if (ctx instanceof TypeSuffixContext) { - return true; - } + // TODO: + // - Variables in array bounds should validate they are constants or literals. + // - Build the capability to evaluate constant expressions. + get isResizable(): boolean { + return this.arrayCtx?.isResizable ?? false; + } - // A newed object is always an object. - if (ctx.asAutoObject()) { - return false; - } + get classTypeName(): string | undefined { + return this.context.rule.classTypeName; + } - // Fixed length strings are primative. - const typeSpec = ctx.asType()!.typeSpec(); - if (typeSpec.fixedLengthStringSpec()) { - return true; - } + constructor(ctx: TypeContext, doc: TextDocument, private readonly arrayCtx?: ArrayDimContext | ArrayDesignatorContext) { + super(ctx, doc); + } +} - // Built ins are primative (or can be in Variant's case) unless object. - const builtin = typeSpec.typeExpression()?.builtinType(); - if (builtin?.reservedTypeIdentifier() || builtin?.reservedTypeIdentifierB()) { - return true; - } else if (builtin?.OBJECT() || builtin?.OBJECT_B()) { - return false; - } +export class PositionalParamElement extends BaseRuleSyntaxElement { + identifierCapability: IdentifierCapability; + diagnosticCapability: DiagnosticCapability; + + private variableTypeInformation?: VariableTypeInformation; + + constructor(ctx: PositionalParamContext, doc: TextDocument) { + super(ctx, doc); - // Defined names can be all sorts of things but if we got here, we're an object. - const definedType = typeSpec.typeExpression()?.definedTypeExpression(); - if (definedType?.simpleNameExpression()) { - return false; + const typeCtx = ctx.paramDcl().untypedNameParamDcl()?.parameterType()?.typeExpression() + ?? ctx.paramDcl().typedNameParamDcl()?.typedName().typeSuffix(); + + const arrayCtx = ctx.paramDcl().untypedNameParamDcl()?.parameterType()?.arrayDesignator() + ?? ctx.paramDcl().typedNameParamDcl()?.arrayDesignator() + ?? undefined; + + if (typeCtx) { + this.variableTypeInformation = new VariableTypeInformation(typeCtx, doc, arrayCtx); } - // If we have a member accessed type, we need to do more digging... - const memberAccessed = definedType?.memberAccessExpression()?.unrestrictedName(); - const isPrimativeMember = (ctx: UnrestrictedNameContext | undefined): boolean => !!memberAccessed?.reservedIdentifier()?.reservedTypeIdentifier(); - const isTypeSuffixMember = (ctx: UnrestrictedNameContext | undefined): boolean => !!memberAccessed?.name()?.typedName(); - return isPrimativeMember(memberAccessed) || isTypeSuffixMember(memberAccessed); + const identifierCtx = ctx.paramDcl().untypedNameParamDcl()?.ambiguousIdentifier() + ?? ctx.paramDcl().typedNameParamDcl()?.typedName().ambiguousIdentifier(); + this.identifierCapability = new IdentifierCapability(this, () => identifierCtx); + this.diagnosticCapability = new DiagnosticCapability(this); + this.scopeItemCapability = new ScopeItemCapability(this, ScopeType.PARAMETER,); + this.scopeItemCapability.assignmentType = AssignmentType.GET + | (this.hasLetAccessor ? AssignmentType.LET : AssignmentType.NONE) + | (this.hasSetAccessor ? AssignmentType.SET : AssignmentType.NONE); } - get isFixedArrayType(): boolean { - return !this.arrayCtx?.boundsList(); + get hasLetAccessor(): boolean { + return this.variableTypeInformation?.isPrimativeType ?? true; } - constructor(ctx: TypeSuffixContext | AsClauseContext, doc: TextDocument, private readonly arrayCtx?: ArrayDimContext) { - super(ctx, doc); + get hasSetAccessor(): boolean { + return this.variableTypeInformation?.isObjectType ?? true; } } diff --git a/server/src/project/formatter.ts b/server/src/project/formatter.ts index 9e6b564..132f089 100644 --- a/server/src/project/formatter.ts +++ b/server/src/project/formatter.ts @@ -1,6 +1,8 @@ // Core import { Range, TextEdit } from 'vscode-languageserver'; import { TextDocument } from 'vscode-languageserver-textdocument'; + +// Project import { VbaFmtListener } from './parser/vbaListener'; diff --git a/server/src/project/parser/vbaAntlr.ts b/server/src/project/parser/vbaAntlr.ts index 0204663..ba58c13 100644 --- a/server/src/project/parser/vbaAntlr.ts +++ b/server/src/project/parser/vbaAntlr.ts @@ -97,7 +97,6 @@ export class VbaErrorHandler extends DefaultErrorStrategy { const inputStream = recognizer.inputStream; if (inputStream.LA(1) !== Token.EOF) { const intervalSet = this.getErrorRecoverySet(recognizer); - console.log(`recover consuming ${recognizer.getCurrentToken()}`); inputStream.consume(); // this.consumeUntil(recognizer, intervalSet); } @@ -108,12 +107,12 @@ export class VbaErrorHandler extends DefaultErrorStrategy { // Attempt to recover using token deletion. const matchedSymbol = this.singleTokenDeletion(recognizer); if (matchedSymbol) { - recognizer.consume(); - return matchedSymbol; + recognizer.consume(); + return matchedSymbol; } // Attempt to recover using token insertion. if (this.singleTokenInsertion(recognizer)) { - return this.getMissingSymbol(recognizer); + return this.getMissingSymbol(recognizer); } // When we don't know what could come next, invalidate the entire line. @@ -123,13 +122,11 @@ export class VbaErrorHandler extends DefaultErrorStrategy { const invalidTokens: Token[] = []; while (![vbaLexer.NEWLINE, vbaLexer.EOF].includes(stream.LA(1))) { invalidTokens.push(stream.LT(1)!); - console.log(`inline consuming ${recognizer.getCurrentToken()}`); recognizer.consume(); } if (invalidTokens.length > 0) { const missingToken = this.createErrorToken(recognizer, invalidTokens); this.reportMatch(recognizer); - console.log(`inline consuming ${recognizer.getCurrentToken()}`); recognizer.consume(); return missingToken; } diff --git a/server/src/project/parser/vbaListener.ts b/server/src/project/parser/vbaListener.ts index f33080f..479e4a4 100644 --- a/server/src/project/parser/vbaListener.ts +++ b/server/src/project/parser/vbaListener.ts @@ -8,24 +8,40 @@ import { vbapreListener } from '../../antlr/out/vbapreListener'; import { vbafmtListener } from '../../antlr/out/vbafmtListener'; import { CompilerIfBlockContext } from '../../antlr/out/vbapreParser'; import { + AmbiguousIdentifierContext, AnyOperatorContext, + ArgumentListContext, + CallStatementContext, ClassModuleContext, + DictionaryAccessExpressionContext, EnumDeclarationContext, EnumMemberContext, FunctionDeclarationContext, IfStatementContext, IgnoredClassAttrContext, IgnoredProceduralAttrContext, + LetStatementContext, + LExpressionContext, + MemberAccessExpressionContext, + OptionalParamContext, + ParamArrayContext, + PositionalParamContext, ProceduralModuleContext, ProcedureDeclarationContext, PropertyGetDeclarationContext, PropertySetDeclarationContext, + SetStatementContext, + SimpleNameExpressionContext, SubroutineDeclarationContext, TypeSuffixContext, UdtDeclarationContext, UnexpectedEndOfLineContext, + UnrestrictedNameContext, VariableDeclarationContext, - WhileStatementContext + WhileStatementContext, + WithExpressionContext, + WithMemberAccessExpressionContext, + WithStatementContext } from '../../antlr/out/vbaParser'; import { AttributeStatementContext, @@ -48,16 +64,39 @@ import { } from '../../antlr/out/vbafmtParser'; // Project +import { Services } from '../../injection/services'; +import { ExtensionConfiguration } from '../workspace'; +import { ErrorRuleElement } from '../elements/generic'; import { CompilerLogicalBlock } from '../elements/precompiled'; import { UnexpectedEndOfLineElement } from '../elements/utils'; -import { DuplicateOperatorElement, IfElseBlock as IfStatementElement, WhileLoopElement } from '../elements/flow'; import { VbaClassDocument, VbaModuleDocument } from '../document'; +import { DuplicateOperatorElement, IfElseBlockElement, WhileLoopElement } from '../elements/flow'; import { ClassElement, ModuleElement, ModuleIgnoredAttributeElement } from '../elements/module'; -import { VariableDeclarationStatementElement, EnumDeclarationElement, EnumMemberDeclarationElement, TypeDeclarationElement, TypeSuffixElement } from '../elements/typing'; -import { FunctionDeclarationElement, PropertyGetDeclarationElement, PropertyLetDeclarationElement, PropertySetDeclarationElement, SubDeclarationElement } from '../elements/procedure'; -import { ExtensionConfiguration } from '../workspace'; -import { Services } from '../../injection/services'; -import { ErrorRuleElement } from '../elements/generic'; +import { NameExpressionContext, NameExpressionElement, WithStatementElement } from '../elements/naming'; +import { + TypeSuffixElement, + EnumDeclarationElement, + TypeDeclarationElement, + PositionalParamElement, + EnumMemberDeclarationElement, + VariableDeclarationStatementElement, +} from '../elements/typing'; +import { + SubDeclarationElement, + FunctionDeclarationElement, + PropertyGetDeclarationElement, + PropertyLetDeclarationElement, + PropertySetDeclarationElement, +} from '../elements/procedure'; + + +enum ParserAssignmentState { + NONE, + LET, + SET, + CALL +} + export class CommonParserCapability { document: VbaClassDocument | VbaModuleDocument; @@ -80,18 +119,50 @@ export class CommonParserCapability { } } +type ParserState = { + inCallExp: boolean + isWithExp: boolean; + isWithStmt: boolean; + isDictAccess: boolean; + assignment: ParserAssignmentState; + nameElements: NameExpressionElement[]; + callMembers?: number; +}; export class VbaListener extends vbaListener { document: VbaClassDocument | VbaModuleDocument; protected documentSettings?: ExtensionConfiguration; protected isAfterMethodDeclaration = false; + private withStatementStack: WithStatementElement[] = []; + private parserStateStack: ParserState[] = []; + + private readonly verbose = false; + + private get parserState(): ParserState { + if (this.parserStateStack.length === 0) { + Services.logger.error('State stack is empty.'); + this.pushNewState(); + } + return this.parserStateStack.at(-1)!; + } + + private pushNewState = () => + this.parserStateStack.push({ + inCallExp: false, + isWithExp: false, + isWithStmt: false, + isDictAccess: false, + nameElements: [], + assignment: ParserAssignmentState.NONE + }); constructor(document: VbaClassDocument | VbaModuleDocument) { super(); + this.pushNewState(); this.document = document; } - static async createAsync(document: VbaClassDocument | VbaModuleDocument): Promise { + static async create(document: VbaClassDocument | VbaModuleDocument): Promise { const result = new VbaListener(document); await result.ensureHasSettingsAsync(); return result; @@ -126,7 +197,7 @@ export class VbaListener extends vbaListener { }; enterIfStatement = (ctx: IfStatementContext) => - this.document.registerElement(new IfStatementElement(ctx, this.document.textDocument)); + this.document.registerElement(new IfElseBlockElement(ctx, this.document.textDocument)); enterIgnoredClassAttr = (ctx: IgnoredClassAttrContext) => this.registerIgnoredAttribute(ctx); enterIgnoredProceduralAttr = (ctx: IgnoredProceduralAttrContext) => this.registerIgnoredAttribute(ctx); @@ -171,6 +242,227 @@ export class VbaListener extends vbaListener { this.document.registerElement(element); }; + enterArgumentList = (_: ArgumentListContext) => this.pushNewState(); + exitArgumentList = (_: ArgumentListContext) => this.parserStateStack.pop(); + + enterLetStatement = (_: LetStatementContext) => { + if (this.verbose) Services.logger.debug(`enterLetStatement`, this.parserStateStack.length); + this.parserState.assignment = ParserAssignmentState.LET; + }; + + enterSetStatement = (_: SetStatementContext) => { + if (this.verbose) Services.logger.debug(`enterSetStatement`, this.parserStateStack.length); + this.parserState.assignment = ParserAssignmentState.SET; + }; + + enterCallStatement = (ctx: CallStatementContext) => { + if (this.verbose) Services.logger.debug(`enterCallStatement: ${ctx.getText()}`, this.parserStateStack.length); + this.parserState.inCallExp = true; + + // Sometimes a call statement does not have the normal trigger context to add a name expression. + const simpleNameCtx = ctx.simpleNameExpression(); + if (!ctx.memberAccessExpression() && !ctx.indexExpression() && simpleNameCtx) { + this.pushNameElement(simpleNameCtx); + } + }; + + exitCallStatement = (ctx: CallStatementContext) => { + if (this.verbose) Services.logger.debug(`exitCallStatement: ${ctx.getText()}`, this.parserStateStack.length); + + // Sometimes a call statement does not have the normal trigger context to exit a name expression. + const simpleNameCtx = ctx.simpleNameExpression(); + if (!ctx.memberAccessExpression() && !ctx.indexExpression() && simpleNameCtx) { + this.parserState.assignment = ParserAssignmentState.CALL; + this.registerNameElement(); + } + this.parserState.inCallExp = false; + }; + + enterMemberAccessExpression = (ctx: MemberAccessExpressionContext) => { + if (this.verbose) Services.logger.debug(`enterMemberAccessExpression: ${ctx.getText()}`, this.parserStateStack.length); + this.pushNameElement(ctx); + }; + + enterDictionaryAccessExpression = (ctx: DictionaryAccessExpressionContext) => { + if (this.verbose) Services.logger.debug(`enterDictionaryAccessExpression: ${ctx.getText()}`, this.parserStateStack.length); + this.parserState.isDictAccess = true; + this.pushNameElement(ctx); + }; + + exitDictionaryAccessExpression = (ctx: DictionaryAccessExpressionContext) => { + if (this.verbose) Services.logger.debug(`exitDictionaryAccessExpression`, this.parserStateStack.length); + this.parserState.isDictAccess = false; + }; + + enterWithMemberAccessExpression = (ctx: WithMemberAccessExpressionContext) => { + if (this.verbose) Services.logger.debug(`enterWithMemberAccessExpression: ${ctx.getText()}`, this.parserStateStack.length); + this.pushNameElement(ctx); + }; + + exitMemberAccessExpression = (ctx: MemberAccessExpressionContext) => { + if (this.verbose) Services.logger.debug(`exitMemberAccessExpression: ${ctx.getText()}`, this.parserStateStack.length); + if (this.parserState.inCallExp) this.parserState.assignment = ParserAssignmentState.CALL; + this.registerNameElement(); + }; + + enterLExpression = (ctx: LExpressionContext) => { + if (this.verbose) Services.logger.debug(`enterLExpression: ${ctx.getText()}`, this.parserStateStack.length); + if (ctx.LPAREN()) { + // FIXME: Will need to track function calls properly when we have types... maybe. + return; + } + + // Will be handled by the WithExpression. + if (ctx.withExpression()) { + return; + } + this.pushNameElement(ctx); + }; + + exitLExpression = (ctx: LExpressionContext) => { + if (this.verbose) Services.logger.debug(`exitLExpression: ${ctx.getText()}`, this.parserStateStack.length); + if (ctx.LPAREN()) { + return; + } + // Was handled by the WithExpression. + if (ctx.withExpression()) { + return; + } + this.registerNameElement(); + }; + + enterPositionalParam = (ctx: PositionalParamContext) => { + if (this.verbose) Services.logger.debug(`enterPositionalParam: ${ctx.getText()}`, this.parserStateStack.length); + const element = new PositionalParamElement(ctx, this.document.textDocument); + this.document.registerElement(element); + }; + + enterOptionalParam = (ctx: OptionalParamContext) => { + if (this.verbose) Services.logger.debug(`enterOptionalParam: ${ctx.getText()}`, this.parserStateStack.length); + const identifierCtx = ctx.paramDcl().untypedNameParamDcl()?.ambiguousIdentifier() + ?? ctx.paramDcl().typedNameParamDcl()?.typedName().ambiguousIdentifier(); + + if (identifierCtx) { + this.addNameElementContext(identifierCtx, 'ambigiousNameContext'); + } + }; + + exitOptionalParam = (ctx: OptionalParamContext) => { + if (this.verbose) Services.logger.debug(`exitOptionalParam: ${ctx.getText()}`, this.parserStateStack.length); + this.registerNameElement(); + }; + + enterParamArray = (ctx: ParamArrayContext) => { + if (this.verbose) Services.logger.debug(`enterParamArray: ${ctx.getText()}`, this.parserStateStack.length); + this.addNameElementContext(ctx.ambiguousIdentifier(), 'ambigiousNameContext'); + }; + + exitParamArray = (ctx: ParamArrayContext) => { + if (this.verbose) Services.logger.debug(`exitParamArray: ${ctx.getText()}`, this.parserStateStack.length); + this.registerNameElement(); + }; + + enterUnrestrictedName = (ctx: UnrestrictedNameContext) => this.addNameElementContext(ctx, 'enterUnrestrictedName'); + enterSimpleNameExpression = (ctx: SimpleNameExpressionContext) => this.addNameElementContext(ctx, 'enterSimpleNameExpression'); + + private addNameElementContext(ctx: UnrestrictedNameContext | SimpleNameExpressionContext | AmbiguousIdentifierContext, source: string) { + if (this.verbose) Services.logger.debug(`${source}: ${ctx.getText()}`, this.parserStateStack.length); + const nameElement = this.parserState.nameElements.at(-1); + if (!nameElement) { + Services.logger.error(`Cannot add name ${ctx.getText()}`, this.parserStateStack.length); + return; + } + + nameElement.addName(ctx); + } + + private pushNameElement(ctx: NameExpressionContext): void { + if (this.verbose) Services.logger.debug('Pushing name', this.parserStateStack.length); + const element = new NameExpressionElement(ctx, this.document.textDocument); + + // Push the name element to the stack. + this.parserState.nameElements.push(element); + + // Link to WithStatement if we have one. + const withStatement = this.withStatementStack.at(-1); + if (withStatement && !withStatement.nameExpressionElement) { + withStatement.nameExpressionElement = element; + } + } + + private registerNameElement(): void { + // Pop the current element and return if we don't have one. + const nameElement = this.parserState.nameElements.pop(); + if (!nameElement) { + return; + } + + // Add with names if we're in a WithExpression + if (this.parserState.isWithExp) { + nameElement.withStatementElement = this.withStatementStack.at(-1); + this.parserState.isWithExp = false; + } + + // Resolve the access members if we have them. + nameElement.evaluateNameStack(); + + // Set the type based on parser state. + switch (this.parserState.assignment) { + case ParserAssignmentState.SET: + nameElement.setAsSetType(); + break; + case ParserAssignmentState.LET: + nameElement.setAsLetType(); + break; + case ParserAssignmentState.CALL: + nameElement.setAsCallType(); + break; + } + this.parserState.assignment = ParserAssignmentState.NONE; + + // Register this name element. + this.document.registerElement(nameElement); + if (this.verbose) { + const name = nameElement.identifierCapability.name; + const fqName = nameElement.accessMembers?.map(x => x.getText).join('.'); + Services.logger.debug(`Registered ${fqName} as ${name}`, this.parserStateStack.length); + } + + // Add the name(s) to the next element down. + const nextElement = this.parserState.nameElements.at(-1); + if (nextElement) { + nextElement.nameContexts = [ + nameElement.nameContexts, + nextElement.nameContexts + ].flat(); + } + } + + enterWithStatement = (ctx: WithStatementContext) => { + if (this.verbose) Services.logger.debug(`enterWithStatement: ${ctx.getText().split('\n')[0]}...`, this.parserStateStack.length); + const element = new WithStatementElement(ctx, this.document.textDocument); + this.withStatementStack.push(element); + }; + + exitWithStatement = (ctx: WithStatementContext) => { + if (this.verbose) Services.logger.debug(`exitWithStatement`, this.parserStateStack.length); + if (this.withStatementStack.at(-1)?.context.rule === ctx) { + this.withStatementStack.pop(); + } else { + Services.logger.error(`Can't exit With`); + } + }; + + enterWithExpression = (_: WithExpressionContext) => { + if (this.verbose) Services.logger.debug(`enterWithExpression`, this.parserStateStack.length); + this.parserState.isWithExp = true; + }; + + exitWithExpression = (_: WithExpressionContext) => { + if (this.verbose) Services.logger.debug(`exitWithExpression`, this.parserStateStack.length); + this.registerNameElement(); + }; + enterTypeSuffix = (ctx: TypeSuffixContext) => this.document.registerElement(new TypeSuffixElement(ctx, this.document.textDocument)); @@ -207,7 +499,7 @@ export class VbaPreListener extends vbapreListener { this.common = new CommonParserCapability(document); } - static async createAsync(document: VbaClassDocument | VbaModuleDocument): Promise { + static async create(document: VbaClassDocument | VbaModuleDocument): Promise { const result = new VbaPreListener(document); await result.common.ensureHasSettingsAsync(); return result; @@ -277,7 +569,7 @@ export class VbaFmtListener extends vbafmtListener { this.indentationKeys = new Array(document.textDocument.lineCount); } - static async createAsync(document: VbaClassDocument | VbaModuleDocument): Promise { + static async create(document: VbaClassDocument | VbaModuleDocument): Promise { const result = new VbaFmtListener(document); await result.common.ensureHasSettingsAsync(); return result; diff --git a/server/src/project/parser/vbaParser.ts b/server/src/project/parser/vbaParser.ts index 9c02671..3f3b0bf 100644 --- a/server/src/project/parser/vbaParser.ts +++ b/server/src/project/parser/vbaParser.ts @@ -1,6 +1,6 @@ // Antlr import { ParseCancellationException, ParseTreeWalker } from 'antlr4ng'; -import { VbaErrorHandler, VbaFmtParser, VbaParser, VbaPreParser } from './vbaAntlr'; +import { VbaFmtParser, VbaParser, VbaPreParser } from './vbaAntlr'; import { VbaFmtListener, VbaListener, VbaPreListener } from './vbaListener'; // Project @@ -12,69 +12,39 @@ import { Logger } from '../../injection/interface'; export class SyntaxParser { constructor(private logger: Logger) { } - async parseAsync(token: CancellationToken, document: VbaClassDocument | VbaModuleDocument): Promise { + async parse(token: CancellationToken, document: VbaClassDocument | VbaModuleDocument): Promise { // Preparse the document if we find a precompiler statement. const regexp = new RegExp(/^\s*#If/gmi); let docText = document.textDocument.getText(); if (regexp.test(docText)) { this.logger.debug(`Beginning pre-parse.`); - const prelistener = await VbaPreListener.createAsync(document); + const prelistener = await VbaPreListener.create(document); const preparser = VbaPreParser.create(docText); - await this.parseDocumentAsync(token, prelistener, preparser); + await this.parseDocument(token, prelistener, preparser); docText = prelistener.text; this.logger.debug(`Completed pre-parse.`); } // Perform main document parse without compiler directives. this.logger.debug(`Beginning main parse.`); - const listener = await VbaListener.createAsync(document); + const listener = await VbaListener.create(document); const parser = VbaParser.create(docText); - await this.parseDocumentAsync(token, listener, parser); + await this.parseDocument(token, listener, parser); this.logger.debug(`Completed main parse.`); } - async formatParseAsync(token: CancellationToken, document: VbaClassDocument | VbaModuleDocument, range?: Range): Promise { + async formatParse(token: CancellationToken, document: VbaClassDocument | VbaModuleDocument, range?: Range): Promise { // Special parser focused on document format. this.logger.debug(`Beginning format parse.`); - const listener = await VbaFmtListener.createAsync(document); + const listener = await VbaFmtListener.create(document); const parser = VbaFmtParser.create(document.textDocument.getText(range)); - await this.parseDocumentAsync(token, listener, parser); + await this.parseDocument(token, listener, parser); this.logger.debug(`Completed format parse.`); return listener; } - // async formatVisit(token: CancellationToken, document: VbaClassDocument | VbaModuleDocument, range?: Range): Promise { - // // // Handle already cancelled. - // if (token.isCancellationRequested) { - // this.logger.debug(`Format visit cancelled before start.`); - // throw new ParseCancellationException(Error('Parse operation cancelled before it started.')); - // } - - // // Listen for cancellation event. - // token.onCancellationRequested(() => { - // this.logger.debug(`Format visit cancelled during run.`); - // throw new ParseCancellationException(new Error('Parse operation cancelled during parse.')); - // }); - - - // this.logger.debug(`Beginning format visit.`); - // const parser = VbaFmtParser.create(document.textDocument.getText(range)); - // const tree = parser.startRule(); - - // // const visitor = container.resolve(VbaFormatVisitor); - // // const visitor = forceAsync(container.resolve(VbaFormatVisitor)); - // // const visitor = forceAsync(new VbaFormatVisitor(this.logger)); - // const visitor = new VbaFormatVisitor(this.logger); - // visitor.token = token; - // await visitor.visit(tree); - // this.logger.debug(`Operation ${token.isCancellationRequested ? 'cancelled' : 'completed'}.`) - - // // Temporary call so I don't have to refactor everything just to call it. - // return await VbaFmtListener.createAsync(document); - // } - - private async parseDocumentAsync(token: CancellationToken, listener: VbaListener | VbaPreListener | VbaFmtListener, parser: VbaParser | VbaPreParser | VbaFmtParser) { + private async parseDocument(token: CancellationToken, listener: VbaListener | VbaPreListener | VbaFmtListener, parser: VbaParser | VbaPreParser | VbaFmtParser) { // Handle already cancelled. if (token.isCancellationRequested) { throw new ParseCancellationException(Error('Parse operation cancelled before it started.')); diff --git a/server/src/project/workspace.ts b/server/src/project/workspace.ts index 14ab298..45fe2fe 100644 --- a/server/src/project/workspace.ts +++ b/server/src/project/workspace.ts @@ -1,4 +1,5 @@ // Core +import { inject, injectable } from 'tsyringe'; import { TextDocument } from 'vscode-languageserver-textdocument'; import { CancellationToken, @@ -8,6 +9,7 @@ import { Command, CompletionItem, CompletionParams, + DefinitionParams, DidChangeConfigurationNotification, DidChangeWatchedFilesParams, DocumentFormattingParams, @@ -16,25 +18,32 @@ import { FoldingRangeParams, Hover, HoverParams, + LocationLink, + RenameParams, SemanticTokensRangeParams, SymbolInformation, TextDocuments, TextEdit, + WorkspaceEdit, + WorkspaceFolder, WorkspaceFoldersChangeEvent, _Connection } from 'vscode-languageserver'; -import { BaseProjectDocument } from './document'; -import { hasWorkspaceConfigurationCapability } from '../capabilities/workspaceFolder'; -import { sleep } from '../utils/helpers'; +// Antlr import { ParseCancellationException } from 'antlr4ng'; + +// Project +import { sleep, walk } from '../utils/helpers'; +import { Services } from '../injection/services'; import { getFormattingEdits } from './formatter'; +import { BaseProjectDocument } from './document'; +import { SyntaxParser } from './parser/vbaParser'; import { VbaFmtListener } from './parser/vbaListener'; -import { returnDefaultOnCancelClientRequest } from '../utils/wrappers'; -import { inject, injectable } from 'tsyringe'; +import { hasWorkspaceConfigurationCapability } from '../capabilities/workspaceFolder'; import { Logger, ILanguageServer, IWorkspace } from '../injection/interface'; -import { Services } from '../injection/services'; -import { ItemType, ScopeItemCapability } from '../capabilities/capabilities'; +import { returnDefaultOnCancelClientRequest } from '../utils/wrappers'; +import { ScopeType, ScopeItemCapability } from '../capabilities/capabilities'; export interface ExtensionConfiguration { maxDocumentLines: number; @@ -56,10 +65,8 @@ export interface ExtensionConfiguration { @injectable() export class Workspace implements IWorkspace { private events?: WorkspaceEvents; - private documents: BaseProjectDocument[] = []; private parseCancellationTokenSource?: CancellationTokenSource; - private _activeDocument?: BaseProjectDocument; private readonly _hasConfigurationCapability: boolean; private _extensionConfiguration?: ExtensionConfiguration; @@ -82,10 +89,6 @@ export class Workspace implements IWorkspace { })(); } - get activeDocument() { - return this._activeDocument; - } - constructor( @inject("_Connection") public readonly connection: _Connection, @inject("ILanguageServer") private server: ILanguageServer) { @@ -95,63 +98,89 @@ export class Workspace implements IWorkspace { this._hasConfigurationCapability = hasWorkspaceConfigurationCapability(this.server); // Configure scopes - const languageScope = new ScopeItemCapability(undefined, ItemType.VBA); - const applicationScope = new ScopeItemCapability(undefined, ItemType.APPLICATION, undefined, languageScope); - const projectScope = new ScopeItemCapability(undefined, ItemType.PROJECT, undefined, applicationScope); + const languageScope = new ScopeItemCapability(undefined, ScopeType.VBA); + const applicationScope = new ScopeItemCapability(undefined, ScopeType.APPLICATION, undefined, languageScope); + const projectScope = new ScopeItemCapability(undefined, ScopeType.PROJECT, undefined, applicationScope); Services.registerProjectScope(projectScope); } - activateDocument(document?: BaseProjectDocument) { - if (document) { - this._activeDocument = document; + // Initially parse everything in the folder. + // ToDo: Handle removal of a workspace folder. + async addWorkspaceFolder(params: WorkspaceFolder): Promise { + this.logger.info(`Adding workspace: ${params.name}`); + const workspaceFiles = walk(params.uri, /\.(cls|bas|frm)$/i); + + // No need to continue if we have no files. + if (workspaceFiles.size === 0) { + return; } + + // Set up parser and dummy token because we won't cancel this. + const parser = new SyntaxParser(this.logger); + const token = new CancellationTokenSource().token; + + // Handle each file in the workspace. + for (const [uri, file] of workspaceFiles) { + const normalisedUri = uri.toFilePath().toFileUri(); + // Don't parse files that we're already tracking. + if (this.projectDocuments.has(normalisedUri)) { + this.logger.debug(`Skipping file: ${normalisedUri}`, 1); + continue; + } + + try { + // Read and parse the project document. + this.logger.debug(`Reading file: ${normalisedUri}`, 1); + const textDocument = TextDocument.create(`${normalisedUri}`, 'vba', 1, file); + const projectDocument = BaseProjectDocument.create(textDocument); + this.projectDocuments.set(normalisedUri, projectDocument); + await parser.parse(token, projectDocument); + this.logger.info(`Parsed ${projectDocument.name}`, 1); + } catch (e) { + // Log errors and anything else without failing. + this.logger.error(`Failed to parse ${normalisedUri}`, 0, e); + } + } + + // Rebuild scopes from Project level. + Services.projectScope.build(); + // Services.projectScope.printToDebug(); } - async parseDocument(document?: BaseProjectDocument) { - this.activateDocument(document); + async parseDocument(document: BaseProjectDocument) { + // this.activateDocument(document); this.parseCancellationTokenSource?.cancel(); this.parseCancellationTokenSource = new CancellationTokenSource(); - if (!this.activeDocument) { - this.logger.error('No active document.'); - return; - } - // Exceptions thrown by the parser should be ignored. try { - await this.activeDocument.parseAsync(this.parseCancellationTokenSource.token); - this.logger.info(`Parsed ${this.activeDocument.name}`); - this.connection.sendDiagnostics(this.activeDocument.languageServerDiagnostics()); + await document.parse(this.parseCancellationTokenSource.token); + this.logger.info(`Parsed ${document.name}`); + this.connection.sendDiagnostics(document.languageServerDiagnostics()); } catch (e) { if (e instanceof ParseCancellationException) { // Swallow cancellation exceptions. They're good. We like these. - } else if (e instanceof Error) { - this.logger.stack(e); } else { - this.logger.error('Something went wrong.'); + this.logger.debug('Parser did not cancel or complete.', 0, e); } } this.parseCancellationTokenSource = undefined; } - async formatParseDocument(document: TextDocument): Promise { - this.parseCancellationTokenSource?.cancel(); - this.parseCancellationTokenSource = new CancellationTokenSource(); - + async formatParseDocument(document: TextDocument, token: CancellationToken): Promise { // Exceptions thrown by the parser should be ignored. let result: VbaFmtListener | undefined; try { - result = await this.activeDocument?.formatParseAsync(this.parseCancellationTokenSource.token); + const projectDocument = this.projectDocuments.get(document.uri.toFilePath().toFileUri()); + result = await projectDocument?.formatParse(token); this.logger.info(`Formatted ${document.uri}`); } catch (e) { if (e instanceof ParseCancellationException) { this.logger.debug('Parse cancelled successfully.'); - } else if (e instanceof Error) { - this.logger.stack(e); } else { - this.logger.error(`Parse failed: ${e}`); + this.logger.error(`Parse failed.`, 0, e); } } @@ -160,15 +189,20 @@ export class Workspace implements IWorkspace { } openDocument(document: TextDocument): void { - const projectDocument = this.projectDocuments.get(document.uri); - if (document.version === projectDocument?.version) { + const normalisedUri = document.uri.toFilePath().toFileUri(); + const projectDocument = this.projectDocuments.get(normalisedUri); + if (projectDocument) { projectDocument.open(); + if (document.version > projectDocument?.version) { + this.parseDocument(projectDocument); + } this.connection.sendDiagnostics(projectDocument.languageServerDiagnostics()); } } closeDocument(document: TextDocument): void { - const projectDocument = this.projectDocuments.get(document.uri); + const normalisedUri = document.uri.toFilePath().toFileUri(); + const projectDocument = this.projectDocuments.get(normalisedUri); if (!projectDocument) { Services.logger.warn(`Failed to get document to close: ${document.uri}`); return; @@ -268,33 +302,33 @@ class WorkspaceEvents { const cancellableOnDocSymbol = returnDefaultOnCancelClientRequest( (p: DocumentSymbolParams, t) => this.onDocumentSymbolAsync(p, t), [], 'Document Symbols'); - const cancellableOnFoldingRanges = returnDefaultOnCancelClientRequest( - (p: FoldingRangeParams, t) => this.onFoldingRangesAsync(p, t), [], 'Folding Range'); - - connection.onInitialized(() => this.onInitialized()); + connection.onCodeAction(async (params, token) => this.onCodeActionRequest(params, token)); connection.onCompletion(params => this.onCompletion(params)); connection.onCompletionResolve(item => this.onCompletionResolve(item)); + connection.onDefinition(async (params, token) => await this.onDefinition(params, token)); connection.onDidChangeConfiguration(() => Services.workspace.clearDocumentsConfiguration()); connection.onDidChangeWatchedFiles(params => this.onDidChangeWatchedFiles(params)); + connection.onDidCloseTextDocument(params => { Services.logger.debug('[event] onDidCloseTextDocument'); Services.logger.debug(JSON.stringify(params), 1); }); + connection.onDocumentFormatting(async (params, token) => await this.onDocumentFormatting(params, token)); connection.onDocumentSymbol(async (params, token) => await cancellableOnDocSymbol(params, token)); connection.onHover(params => this.onHover(params)); - connection.onDocumentFormatting(async (params, token) => await this.onDocumentFormatting(params, token)); - connection.onDidCloseTextDocument(params => { Services.logger.debug('[event] onDidCloseTextDocument'); Services.logger.debug(JSON.stringify(params), 1); }); - connection.onCodeAction((params, token) => this.onCodeActionRequest(params, token)); + connection.onInitialized(() => this.onInitialized()); + connection.onRenameRequest((params, token) => this.onRenameRequest(params, token)); if (hasWorkspaceConfigurationCapability(Services.server)) { - connection.onFoldingRanges(async (params, token) => await cancellableOnFoldingRanges(params, token)); + connection.onFoldingRanges(async (params, token) => await this.onFoldingRangesAsync(params, token)); } connection.onRequest((method: string, params: object | object[] | any) => { switch (method) { case 'textDocument/semanticTokens/full': { - const uri: string = params.textDocument.uri; - return this.activeDocument?.languageServerSemanticTokens(); + const uri: string = params.textDocument.uri.toFilePath(); + return this.projectDocuments.get(uri)?.languageServerSemanticTokens(); } case 'textDocument/semanticTokens/range': { const rangeParams = params as SemanticTokensRangeParams; - return this.activeDocument?.languageServerSemanticTokens(rangeParams.range); + const uri: string = params.textDocument.uri.toFilePath(); + return this.projectDocuments.get(uri)?.languageServerSemanticTokens(rangeParams.range); } default: Services.logger.error(`Unresolved request path: ${method}`); @@ -323,6 +357,23 @@ class WorkspaceEvents { return item; } + private async onDefinition(params: DefinitionParams, token: CancellationToken): Promise { + Services.logger.debug('[event] onDefinition'); + Services.logger.debug(JSON.stringify(params), 1); + if (token.isCancellationRequested) { + return; + } + + const results = Services.projectScope.getDeclarationLocation(params.textDocument.uri, params.position); + Services.logger.debug(`Processed onDefinition: returning ${JSON.stringify(results)}`); + + if (results === undefined) { + return null; + } else { + return results; + } + } + private onDidChangeWatchedFiles(params: DidChangeWatchedFilesParams) { Services.logger.debug('[event] onDidChangeWatchedFiles'); Services.logger.debug(JSON.stringify(params), 1); @@ -341,18 +392,33 @@ class WorkspaceEvents { return document?.languageServerSymbolInformation() ?? []; } - private async onFoldingRangesAsync(params: FoldingRangeParams, token: CancellationToken): Promise { + private async onFoldingRangesAsync(params: FoldingRangeParams, token: CancellationToken): Promise { const logger = Services.logger; logger.debug('[Event] onFoldingRanges'); + + // Don't do any work if we don't have to. + if (token.isCancellationRequested) { + logger.debug('Cancellation requested before start for Folding Ranges.'); + return; + } + let document: BaseProjectDocument | undefined; try { - document = await this.getParsedProjectDocument(params.textDocument.uri, 0, token); + const normalisedUri = params.textDocument.uri.toFilePath().toFileUri(); + document = await this.getParsedProjectDocument(normalisedUri, 0, token); } catch (error) { // Swallow parser cancellations and rethrow anything else. if (error instanceof ParseCancellationException) { throw error; } } + + // Check again if we're cancelled. + if (token.isCancellationRequested) { + logger.debug('Cancellation requested before start for Folding Ranges.'); + return; + } + const result = document?.languageServerFoldingRanges(); for (const foldingRange of result ?? []) { logger.debug(`${JSON.stringify(foldingRange.range)} '${foldingRange.openWord}..${foldingRange.closeWord}'`, 1); @@ -379,6 +445,11 @@ class WorkspaceEvents { ); connection.client.register(DidChangeConfigurationNotification.type, undefined); } + + // Read workspace folders if we have them. + Services.server.configuration?.params + .workspaceFolders?.forEach(folder => setImmediate(() => + Services.workspace.addWorkspaceFolder(folder))); } private async onDocumentFormatting(params: DocumentFormattingParams, token: CancellationToken): Promise { @@ -412,8 +483,6 @@ class WorkspaceEvents { } try { - // We don't actually need the document but await to ensure it's parsed. - await this.getParsedProjectDocument(params.textDocument.uri, 0, token); const uri = params.textDocument.uri; const result: (Command | CodeAction)[] = []; const codeActionRegistry = Services.codeActionsRegistry; @@ -433,6 +502,44 @@ class WorkspaceEvents { } } + private async onRenameRequest(params: RenameParams, token: CancellationToken): Promise { + Services.logger.debug(`[event] onRenameRequest: ${JSON.stringify(params)}`); + if (token.isCancellationRequested) { + Services.logger.debug(`onRenameRequest cancelled before start.`); + return; + } + + const renameItems = Services.projectScope.getRenameItems(params.textDocument.uri, params.position); + const workspaceEdit: { changes: { [uri: string]: TextEdit[] }; } = { changes: {} }; + + for (const renameItem of renameItems) { + const uri = renameItem.locationUri; + if (!uri) { + Services.logger.warn('Scope item has no element to rename'); + continue; + } + + const range = renameItem.element?.identifierCapability?.range; + if (!range) { + Services.logger.warn('Scope item has no identifier to rename'); + continue; + } + + workspaceEdit.changes[uri] ??= []; + workspaceEdit.changes[uri].push(TextEdit.replace(range, params.newName)); + } + + Services.logger.debug(`resolved onRenameRequest: returning\n${JSON.stringify(workspaceEdit)}`); + + // Allow a cancellation to be processed if we have one. + await new Promise(resolve => setTimeout(resolve, 0)); + if (token.isCancellationRequested) { + Services.logger.debug(`onRenameRequest cancelled during run.`); + return; + } + return workspaceEdit; + } + /** Documents event handlers */ /** @@ -440,13 +547,10 @@ class WorkspaceEvents { * @param document The document being opened. */ onDidOpen(document: TextDocument) { - const logger = Services.logger; - logger.debug('[event] onDidOpen'); - logger.debug(`uri: ${document.uri}`, 1); - logger.debug(`languageId: ${document.languageId}`, 1); - logger.debug(`version: ${document.version}`, 1); - const projectDocument = this.projectDocuments.get(document.uri); - if (projectDocument) { + Services.logger.debug('[event] onDidOpen'); + this.printDocumentInformation(document); + const normalisedUri = document.uri.toFilePath().toFileUri(); + if (this.projectDocuments.has(normalisedUri)) { Services.workspace.openDocument(document); } } @@ -456,23 +560,22 @@ class WorkspaceEvents { * @param document The document that was changed. */ onDidChangeContent(document: TextDocument): void { - const logger = Services.logger; - logger.debug('[event] onDidChangeContentAsync'); - logger.debug(`uri: ${document.uri}`, 1); - logger.debug(`languageId: ${document.languageId}`, 1); - logger.debug(`version: ${document.version}`, 1); + Services.logger.debug('[event] onDidChangeContent'); + this.printDocumentInformation(document); // If the event is fired for the same version of the document, don't reparse. - const existingDocument = this.projectDocuments.get(document.uri); - if ((existingDocument?.version ?? -1) >= document.version) { - logger.debug('Document already parsed.'); + const normalisedUri = document.uri.toFilePath().toFileUri(); + const existingDocument = this.projectDocuments.get(normalisedUri); + const existingVersion = existingDocument?.version ?? -1; + Services.logger.debug(`existing: ${existingVersion}`, 1); + if (existingVersion >= document.version) { return; } // The document is new or a new version that we should parse. const projectDocument = BaseProjectDocument.create(document); - this.projectDocuments.set(document.uri, projectDocument); - Services.projectScope.invalidate(document.uri); + this.projectDocuments.set(normalisedUri, projectDocument); + Services.projectScope.invalidateModule(normalisedUri); Services.workspace.parseDocument(projectDocument); } @@ -481,14 +584,21 @@ class WorkspaceEvents { * @param document The document being closed. */ onDidClose(document: TextDocument) { + Services.logger.debug('[event] onDidClose'); + this.printDocumentInformation(document); + + const normalisedUri = document.uri.toFilePath().toFileUri(); + if (this.projectDocuments.has(normalisedUri)) { + Services.workspace.closeDocument(document); + } + } + + private printDocumentInformation(document: TextDocument) { const logger = Services.logger; - logger.debug('[event] onDidClose'); - logger.debug(`uri: ${document.uri}`, 1); + const normalisedUri = document.uri.toFilePath().toFileUri(); + logger.debug(`doc uri: ${document.uri}`, 1); + logger.debug(`norm uri: ${normalisedUri}`, 1); logger.debug(`languageId: ${document.languageId}`, 1); logger.debug(`version: ${document.version}`, 1); - const projectDocument = this.projectDocuments.get(document.uri); - if (projectDocument) { - Services.workspace.closeDocument(document); - } } } diff --git a/server/src/server.ts b/server/src/server.ts index f42fff0..c527289 100644 --- a/server/src/server.ts +++ b/server/src/server.ts @@ -16,8 +16,10 @@ import { Services } from './injection/services'; // Ensures globally available type extensions. import './extensions/stringExtensions'; -import './extensions/parserExtensions'; import './extensions/numberExtensions'; +import './extensions/antlrCoreExtensions'; +import './extensions/antlrVbaParserExtensions'; +import './extensions/antlrVbaPreParserExtensions'; import { Workspace } from './project/workspace'; import { activateSemanticTokenProvider } from './capabilities/semanticTokens'; import { activateWorkspaceFolderCapability } from './capabilities/workspaceFolder'; @@ -45,6 +47,10 @@ export class LanguageServer implements ILanguageServer { activateSemanticTokenProvider(result); return result; }); + // Register shutdown actions + this.connection.onShutdown(() => { }); + this.connection.onExit(() => process.exit(0)); + // Register for client configuration notification changes. this.connection.onInitialized(() => { this.connection.client.register(DidChangeConfigurationNotification.type, undefined); }); this.connection.onDidChangeConfiguration(() => this._clientConfiguration = undefined); @@ -70,26 +76,21 @@ export class LanguageServer implements ILanguageServer { export class LanguageServerConfiguration { - params: InitializeParams; capabilities: ServerCapabilities = { // Implemented - documentSymbolProvider: true, + codeActionProvider: true, + definitionProvider: true, foldingRangeProvider: true, + documentSymbolProvider: true, textDocumentSync: TextDocumentSyncKind.Incremental, - // diagnosticProvider: { - // interFileDependencies: false, - // workspaceDiagnostics: false - // }, // Implement soon. - codeActionProvider: true, - completionProvider: undefined, hoverProvider: false, + completionProvider: undefined, // Not implemented. signatureHelpProvider: undefined, declarationProvider: false, - definitionProvider: false, typeDefinitionProvider: false, implementationProvider: false, referencesProvider: false, @@ -101,7 +102,7 @@ export class LanguageServerConfiguration { documentFormattingProvider: true, documentRangeFormattingProvider: false, documentOnTypeFormattingProvider: undefined, - renameProvider: false, + renameProvider: true, selectionRangeProvider: false, executeCommandProvider: undefined, callHierarchyProvider: false, @@ -111,9 +112,7 @@ export class LanguageServerConfiguration { experimental: undefined, }; - constructor(params: InitializeParams) { - this.params = params; - } + constructor(public params: InitializeParams) { } } diff --git a/server/src/utils/helpers.ts b/server/src/utils/helpers.ts index cea83e3..40e69da 100644 --- a/server/src/utils/helpers.ts +++ b/server/src/utils/helpers.ts @@ -1,3 +1,10 @@ +// Core +import * as fs from 'fs'; +import * as path from 'path'; +import { Services } from '../injection/services'; +import { pathToFileURL } from 'url'; +import { Position, Range } from 'vscode-languageserver'; + export class Dictionary extends Map { private defaultFactory: (...args: any) => V; @@ -34,4 +41,50 @@ export function ioEvents(): Promise { export function sleep(ms: number): Promise { return new Promise(resolve => setTimeout(resolve, ms)); +} + +/** + * Recursively walks a directory structure and returns a map of file path to string content. + * @param dirOrUri A directory as a path or 'file://' uri. + * @param pattern A predicate to filter results. + * @param files Used for internal recursive calls. + */ +export function walk(dirOrUri: string, pattern?: RegExp, files: Map = new Map()): Map { + const logger = Services.logger; + logger.debug(`Walking ${dirOrUri}`); + + // Walk the contents of the directory. + const dir = dirOrUri.toFilePath(); + for (const name of fs.readdirSync(dir)) { + const p = path.join(dir, name); + + // Check if we have a directory. This can occasionally throw at an OS level. + let pIsDirectory: boolean | undefined; + try { pIsDirectory = fs.statSync(p).isDirectory(); } + catch (e) { logger.warn(`The OS threw an exception checking whether ${p} is a directory.`, 0, e); } + if (pIsDirectory) { + // Recursive call for directories. + walk(p, pattern, files); + } else if (pattern?.test(name) ?? true) { + // Track files that match the pattern. + logger.debug(`Found ${p}`, 1); + logger.debug(`href: ${pathToFileURL(p).href}`); + let fileContent = ''; + try { fileContent = fs.readFileSync(p, 'utf-8'); } + catch (e) { logger.error(`The OS threw an exception reading ${p}.`, 0, e); } + files.set(p, fileContent); + } + } + return files; +} + +export function isPositionInsideRange(position: Position, range: Range): boolean { + if (range.start.line !== range.end.line) { + return position.line >= range.start.line + && position.line <= range.end.line; + } + + return position.line === range.start.line + && position.character >= range.start.character + && position.character <= range.end.character; } \ No newline at end of file diff --git a/server/src/utils/logger.ts b/server/src/utils/logger.ts index 2a79dd9..f7d4311 100644 --- a/server/src/utils/logger.ts +++ b/server/src/utils/logger.ts @@ -1,7 +1,10 @@ +// Core import { inject, injectable } from 'tsyringe'; -import { Logger, ILanguageServer } from '../injection/interface'; import { _Connection } from 'vscode-languageserver'; +// Project +import { Logger, ILanguageServer } from '../injection/interface'; + enum LogLevel { error = 1, @@ -23,14 +26,14 @@ export class LspLogger implements Logger { @inject("_Connection") public connection: _Connection, @inject("ILanguageServer") private server: ILanguageServer) { } - error = (msg: string, lvl?: number) => this.emit(LogLevel.error, msg, lvl); - warn = (msg: string, lvl?: number) => this.emit(LogLevel.warn, msg, lvl); - info = (msg: string, lvl?: number) => this.emit(LogLevel.info, msg, lvl); - log = (msg: string, lvl?: number) => this.emit(LogLevel.log, msg, lvl); - debug = (msg: string, lvl?: number) => this.emit(LogLevel.debug, msg, lvl); - stack = (e: Error) => this.emit(LogLevel.error, `${e.name}: ${e.message}\n${e.stack}`); + error = (msg: string, lvl?: number, e?: unknown) => this.emit(LogLevel.error, msg, lvl, e); + warn = (msg: string, lvl?: number, e?: unknown) => this.emit(LogLevel.warn, msg, lvl, e); + info = (msg: string, lvl?: number, e?: unknown) => this.emit(LogLevel.info, msg, lvl, e); + log = (msg: string, lvl?: number, e?: unknown) => this.emit(LogLevel.log, msg, lvl, e); + debug = (msg: string, lvl?: number, e?: unknown) => this.emit(LogLevel.debug, msg, lvl, e); + stack = (e: Error, logLevel?: LogLevel) => this.emit(logLevel ?? LogLevel.error, `${e.name}: ${e.message}\n${e.stack}`); - private emit(logLevel: LogLevel, msgText: string, msgLevel?: number): void { + private emit(logLevel: LogLevel, msgText: string, msgLevel?: number, e?: unknown): void { // Async get the configuration and then emit. this.server.clientConfiguration.then(config => { try { @@ -54,6 +57,11 @@ export class LspLogger implements Logger { message: msgText, level: msgLevel ?? 0 }); + + // If we have an error, then log stack trace too. + if (e instanceof Error) { + this.stack(e, logLevel); + } }); } diff --git a/server/src/utils/wrappers.ts b/server/src/utils/wrappers.ts index 2c04a32..3476144 100644 --- a/server/src/utils/wrappers.ts +++ b/server/src/utils/wrappers.ts @@ -1,36 +1,11 @@ +// Core import { CancellationToken } from 'vscode-languageserver'; -import { LspLogger } from './logger'; -import { Logger } from '../injection/interface'; + +// Project import { Services } from '../injection/services'; -type signature = (token: CancellationToken, ...args: A) => Promise; type paramsSignature = (params: P, token: CancellationToken) => Promise; -/** - * A wrapper to give cancellation token handling to an async function. - * @param fn An async function that requires cancellation token handling. - * @param defaultValue The value to return when cancelled. - */ -function returnDefaultOnCancel(fn: signature, logger?: Logger, name?: string, defaultValue?: T, cancelError?: Error): signature { - return async (token: CancellationToken, ...args: A): Promise => { - if (token.isCancellationRequested) { - if (logger) logger.debug(`Cancellation requested before start for ${name ?? 'unknown'}. Returning default.`); - if (cancelError) throw cancelError; - return defaultValue; - } - - return new Promise((resolve) => { - const onCancel = () => { - if (logger) logger.debug(`Cancellation requested during processing for ${name ?? 'unknown'}. Returning default.`); - if (cancelError) throw cancelError; - resolve(defaultValue); - }; - token.onCancellationRequested(onCancel); - fn(token, ...args).then(resolve).catch(() => resolve(defaultValue)); - }); - }; -} - /** * A wrapper to give cancellation token handling to an async client request. * @param fn An async function that requires cancellation token handling. diff --git a/vba.language-configuration.json b/vba.language-configuration.json index 91918b0..9d5867f 100644 --- a/vba.language-configuration.json +++ b/vba.language-configuration.json @@ -8,12 +8,20 @@ ["(", ")"] ], "autoClosingPairs": [ - { "open": "\"", "close": "\""} + { "open": "\"", "close": "\""}, + { "open": "(", "close": ")"}, + { "open": "[", "close": "]"} ], + "autoCloseBefore": ": \n\r\t)}]\n", "surroundingPairs": [ [ "(", ")" ], [ "[", "]" ], [ "\"", "\"" ] ], "wordPattern": "(-?\\d*\\.\\d\\w*)|([^\\`\\~\\!\\@\\#\\%\\^\\&\\*\\(\\)\\-\\=\\+\\[\\{\\]\\}\\\\\\|\\;\\:\\'\\\"\\,\\.\\<\\>\\/\\?\\s]+)", + // Placeholder - possibly won't use as it's a little awkward. Prefer to implement with LSP. + // "indentationRules": { + // "increaseIndentPattern": "^\\s*(?:Sub|Function|Property\\s+(?:Get|Let|Set)|Else|(Else)?If.*?Then|For|While|Do|Select\\s+Case|Type|With|Enum)\\b", + // "decreaseIndentPattern": "^\\s*(?:End\\s+(?:Sub|Function|Property|If|Select|Type|With|Enum)|Next|Loop|Else|ElseIf.*?Then)\\b" + // } } \ No newline at end of file