diff --git a/.vscode/launch.json b/.vscode/launch.json index 8532828..bbb5424 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -6,7 +6,7 @@ "version": "0.2.0", "configurations": [ { - "name": "Extension", + "name": "Extension - Test Suite 1 - Language Registration", "type": "extensionHost", "request": "launch", "preLaunchTask": "compile", @@ -14,10 +14,38 @@ "args": [ "--disable-extensions", "--extensionDevelopmentPath=${workspaceRoot}", - "--extensionTestsPath=${workspaceRoot}/out/tests/suite", - "${workspaceRoot}/test-fixture", + "--extensionTestsPath=${workspaceRoot}/out/tests/suite1", + "${workspaceRoot}/test-fixtures/suite1", ], - "outFiles": ["${workspaceRoot}/out/tests/suite/*.js"] + "outFiles": ["${workspaceRoot}/out/tests/suite1/*.js"] + }, + { + "name": "Extension - Test Suite 2 - Diagnostics", + "type": "extensionHost", + "request": "launch", + "preLaunchTask": "compile", + "runtimeExecutable": "${execPath}", + "args": [ + "--disable-extensions", + "--extensionDevelopmentPath=${workspaceRoot}", + "--extensionTestsPath=${workspaceRoot}/out/tests/suite2", + "${workspaceRoot}/test-fixtures/suite2", + ], + "outFiles": ["${workspaceRoot}/out/tests/suite2/*.js"] + }, + { + "name": "Extension - Test Suite 3 - Selectors", + "type": "extensionHost", + "request": "launch", + "preLaunchTask": "compile", + "runtimeExecutable": "${execPath}", + "args": [ + "--disable-extensions", + "--extensionDevelopmentPath=${workspaceRoot}", + "--extensionTestsPath=${workspaceRoot}/out/tests/suite3", + "${workspaceRoot}/test-fixtures/suite3", + ], + "outFiles": ["${workspaceRoot}/out/tests/suite3/*.js"] } ] } diff --git a/package-lock.json b/package-lock.json index 7c96f67..cf7f1a3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -20,11 +20,13 @@ "@types/glob": "^7.2.0", "@types/mocha": "^9.1.0", "@types/node": "^17.0.23", + "@types/sinon": "^10.0.11", "@types/vscode": "^1.65.0", "@vscode/test-electron": "^2.1.3", "glob": "^7.1.6", "mocha": "^9.2.2", "prettier": "^2.6.2", + "sinon": "^13.0.2", "typescript": "^4.6.3", "vsce": "^2.7.0", "vscode-nls-dev": "^4.0.0" @@ -33,6 +35,41 @@ "vscode": "^1.65.0" } }, + "node_modules/@sinonjs/commons": { + "version": "1.8.3", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.3.tgz", + "integrity": "sha512-xkNcLAn/wZaX14RPlwizcKicDk9G3F8m2nU3L7Ukm5zBgTwiT0wsoFAHx9Jq56fJA1z/7uKGtCRu16sOUCLIHQ==", + "dev": true, + "dependencies": { + "type-detect": "4.0.8" + } + }, + "node_modules/@sinonjs/fake-timers": { + "version": "9.1.2", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-9.1.2.tgz", + "integrity": "sha512-BPS4ynJW/o92PUR4wgriz2Ud5gpST5vz6GQfMixEDK0Z8ZCUv2M7SkBLykH56T++Xs+8ln9zTGbOvNGIe02/jw==", + "dev": true, + "dependencies": { + "@sinonjs/commons": "^1.7.0" + } + }, + "node_modules/@sinonjs/samsam": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/@sinonjs/samsam/-/samsam-6.1.1.tgz", + "integrity": "sha512-cZ7rKJTLiE7u7Wi/v9Hc2fs3Ucc3jrWeMgPHbbTCeVAB2S0wOBbYlkJVeNSL04i7fdhT8wIbDq1zhC/PXTD2SA==", + "dev": true, + "dependencies": { + "@sinonjs/commons": "^1.6.0", + "lodash.get": "^4.4.2", + "type-detect": "^4.0.8" + } + }, + "node_modules/@sinonjs/text-encoding": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/@sinonjs/text-encoding/-/text-encoding-0.7.1.tgz", + "integrity": "sha512-+iTbntw2IZPb/anVDbypzfQa+ay64MW0Zo8aJ8gZPWMMK6/OubMVb6lUPMagqjOPnmtauXnFCACVl3O7ogjeqQ==", + "dev": true + }, "node_modules/@tootallnate/once": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-1.1.2.tgz", @@ -79,6 +116,21 @@ "integrity": "sha512-UxDxWn7dl97rKVeVS61vErvw086aCYhDLyvRQZ5Rk65rZKepaFdm53GeqXaKBuOhED4e9uWq34IC3TdSdJJ2Gw==", "dev": true }, + "node_modules/@types/sinon": { + "version": "10.0.11", + "resolved": "https://registry.npmjs.org/@types/sinon/-/sinon-10.0.11.tgz", + "integrity": "sha512-dmZsHlBsKUtBpHriNjlK0ndlvEh8dcb9uV9Afsbt89QIyydpC7NcR+nWlAhASfy3GHnxTl4FX/aKE7XZUt/B4g==", + "dev": true, + "dependencies": { + "@types/sinonjs__fake-timers": "*" + } + }, + "node_modules/@types/sinonjs__fake-timers": { + "version": "8.1.2", + "resolved": "https://registry.npmjs.org/@types/sinonjs__fake-timers/-/sinonjs__fake-timers-8.1.2.tgz", + "integrity": "sha512-9GcLXF0/v3t80caGs5p2rRfkB+a8VBGLJZVih6CNFkx8IZ994wiKKLSRs9nuFwk1HevWs/1mnUmkApGrSGsShA==", + "dev": true + }, "node_modules/@types/vscode": { "version": "1.65.0", "resolved": "https://registry.npmjs.org/@types/vscode/-/vscode-1.65.0.tgz", @@ -1384,6 +1436,12 @@ "js-yaml": "bin/js-yaml.js" } }, + "node_modules/just-extend": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/just-extend/-/just-extend-4.2.1.tgz", + "integrity": "sha512-g3UB796vUFIY90VIv/WX3L2c8CS2MdWUww3CNrYmqza1Fg0DURc2K/O4YrnklBdQarSJ/y8JnJYDGc+1iumQjg==", + "dev": true + }, "node_modules/keytar": { "version": "7.9.0", "resolved": "https://registry.npmjs.org/keytar/-/keytar-7.9.0.tgz", @@ -1434,6 +1492,12 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/lodash.get": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz", + "integrity": "sha1-LRd/ZS+jHpObRDjVNBSZ36OCXpk=", + "dev": true + }, "node_modules/log-symbols": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", @@ -1847,6 +1911,19 @@ "integrity": "sha512-ONmRUqK7zj7DWX0D9ADe03wbwOBZxNAfF20PlGfCWQcD3+/MakShIHrMqx9YwPTfxDdF1zLeL+RGZiR9kGMLdg==", "dev": true }, + "node_modules/nise": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/nise/-/nise-5.1.1.tgz", + "integrity": "sha512-yr5kW2THW1AkxVmCnKEh4nbYkJdB3I7LUkiUgOvEkOp414mc2UMaHMA7pjq1nYowhdoJZGwEKGaQVbxfpWj10A==", + "dev": true, + "dependencies": { + "@sinonjs/commons": "^1.8.3", + "@sinonjs/fake-timers": ">=5", + "@sinonjs/text-encoding": "^0.7.1", + "just-extend": "^4.0.2", + "path-to-regexp": "^1.7.0" + } + }, "node_modules/node-abi": { "version": "3.8.0", "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.8.0.tgz", @@ -2028,6 +2105,21 @@ "node": ">=0.10.0" } }, + "node_modules/path-to-regexp": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.8.0.tgz", + "integrity": "sha512-n43JRhlUKUAlibEJhPeir1ncUID16QnEjNpwzNdO3Lm4ywrBpBZ5oLD0I6br9evr1Y9JTqwRtAh7JLoOzAQdVA==", + "dev": true, + "dependencies": { + "isarray": "0.0.1" + } + }, + "node_modules/path-to-regexp/node_modules/isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", + "dev": true + }, "node_modules/pause-stream": { "version": "0.0.11", "resolved": "https://registry.npmjs.org/pause-stream/-/pause-stream-0.0.11.tgz", @@ -2343,6 +2435,54 @@ "simple-concat": "^1.0.0" } }, + "node_modules/sinon": { + "version": "13.0.2", + "resolved": "https://registry.npmjs.org/sinon/-/sinon-13.0.2.tgz", + "integrity": "sha512-KvOrztAVqzSJWMDoxM4vM+GPys1df2VBoXm+YciyB/OLMamfS3VXh3oGh5WtrAGSzrgczNWFFY22oKb7Fi5eeA==", + "dev": true, + "dependencies": { + "@sinonjs/commons": "^1.8.3", + "@sinonjs/fake-timers": "^9.1.2", + "@sinonjs/samsam": "^6.1.1", + "diff": "^5.0.0", + "nise": "^5.1.1", + "supports-color": "^7.2.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/sinon" + } + }, + "node_modules/sinon/node_modules/diff": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-5.0.0.tgz", + "integrity": "sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w==", + "dev": true, + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/sinon/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/sinon/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", @@ -2551,6 +2691,15 @@ "node": "*" } }, + "node_modules/type-detect": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", + "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", + "dev": true, + "engines": { + "node": ">=4" + } + }, "node_modules/typed-rest-client": { "version": "1.8.6", "resolved": "https://registry.npmjs.org/typed-rest-client/-/typed-rest-client-1.8.6.tgz", @@ -3070,6 +3219,41 @@ } }, "dependencies": { + "@sinonjs/commons": { + "version": "1.8.3", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.3.tgz", + "integrity": "sha512-xkNcLAn/wZaX14RPlwizcKicDk9G3F8m2nU3L7Ukm5zBgTwiT0wsoFAHx9Jq56fJA1z/7uKGtCRu16sOUCLIHQ==", + "dev": true, + "requires": { + "type-detect": "4.0.8" + } + }, + "@sinonjs/fake-timers": { + "version": "9.1.2", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-9.1.2.tgz", + "integrity": "sha512-BPS4ynJW/o92PUR4wgriz2Ud5gpST5vz6GQfMixEDK0Z8ZCUv2M7SkBLykH56T++Xs+8ln9zTGbOvNGIe02/jw==", + "dev": true, + "requires": { + "@sinonjs/commons": "^1.7.0" + } + }, + "@sinonjs/samsam": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/@sinonjs/samsam/-/samsam-6.1.1.tgz", + "integrity": "sha512-cZ7rKJTLiE7u7Wi/v9Hc2fs3Ucc3jrWeMgPHbbTCeVAB2S0wOBbYlkJVeNSL04i7fdhT8wIbDq1zhC/PXTD2SA==", + "dev": true, + "requires": { + "@sinonjs/commons": "^1.6.0", + "lodash.get": "^4.4.2", + "type-detect": "^4.0.8" + } + }, + "@sinonjs/text-encoding": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/@sinonjs/text-encoding/-/text-encoding-0.7.1.tgz", + "integrity": "sha512-+iTbntw2IZPb/anVDbypzfQa+ay64MW0Zo8aJ8gZPWMMK6/OubMVb6lUPMagqjOPnmtauXnFCACVl3O7ogjeqQ==", + "dev": true + }, "@tootallnate/once": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-1.1.2.tgz", @@ -3113,6 +3297,21 @@ "integrity": "sha512-UxDxWn7dl97rKVeVS61vErvw086aCYhDLyvRQZ5Rk65rZKepaFdm53GeqXaKBuOhED4e9uWq34IC3TdSdJJ2Gw==", "dev": true }, + "@types/sinon": { + "version": "10.0.11", + "resolved": "https://registry.npmjs.org/@types/sinon/-/sinon-10.0.11.tgz", + "integrity": "sha512-dmZsHlBsKUtBpHriNjlK0ndlvEh8dcb9uV9Afsbt89QIyydpC7NcR+nWlAhASfy3GHnxTl4FX/aKE7XZUt/B4g==", + "dev": true, + "requires": { + "@types/sinonjs__fake-timers": "*" + } + }, + "@types/sinonjs__fake-timers": { + "version": "8.1.2", + "resolved": "https://registry.npmjs.org/@types/sinonjs__fake-timers/-/sinonjs__fake-timers-8.1.2.tgz", + "integrity": "sha512-9GcLXF0/v3t80caGs5p2rRfkB+a8VBGLJZVih6CNFkx8IZ994wiKKLSRs9nuFwk1HevWs/1mnUmkApGrSGsShA==", + "dev": true + }, "@types/vscode": { "version": "1.65.0", "resolved": "https://registry.npmjs.org/@types/vscode/-/vscode-1.65.0.tgz", @@ -4086,6 +4285,12 @@ "argparse": "^2.0.1" } }, + "just-extend": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/just-extend/-/just-extend-4.2.1.tgz", + "integrity": "sha512-g3UB796vUFIY90VIv/WX3L2c8CS2MdWUww3CNrYmqza1Fg0DURc2K/O4YrnklBdQarSJ/y8JnJYDGc+1iumQjg==", + "dev": true + }, "keytar": { "version": "7.9.0", "resolved": "https://registry.npmjs.org/keytar/-/keytar-7.9.0.tgz", @@ -4126,6 +4331,12 @@ "p-locate": "^5.0.0" } }, + "lodash.get": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz", + "integrity": "sha1-LRd/ZS+jHpObRDjVNBSZ36OCXpk=", + "dev": true + }, "log-symbols": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", @@ -4431,6 +4642,19 @@ "integrity": "sha512-ONmRUqK7zj7DWX0D9ADe03wbwOBZxNAfF20PlGfCWQcD3+/MakShIHrMqx9YwPTfxDdF1zLeL+RGZiR9kGMLdg==", "dev": true }, + "nise": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/nise/-/nise-5.1.1.tgz", + "integrity": "sha512-yr5kW2THW1AkxVmCnKEh4nbYkJdB3I7LUkiUgOvEkOp414mc2UMaHMA7pjq1nYowhdoJZGwEKGaQVbxfpWj10A==", + "dev": true, + "requires": { + "@sinonjs/commons": "^1.8.3", + "@sinonjs/fake-timers": ">=5", + "@sinonjs/text-encoding": "^0.7.1", + "just-extend": "^4.0.2", + "path-to-regexp": "^1.7.0" + } + }, "node-abi": { "version": "3.8.0", "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.8.0.tgz", @@ -4569,6 +4793,23 @@ "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=" }, + "path-to-regexp": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.8.0.tgz", + "integrity": "sha512-n43JRhlUKUAlibEJhPeir1ncUID16QnEjNpwzNdO3Lm4ywrBpBZ5oLD0I6br9evr1Y9JTqwRtAh7JLoOzAQdVA==", + "dev": true, + "requires": { + "isarray": "0.0.1" + }, + "dependencies": { + "isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", + "dev": true + } + } + }, "pause-stream": { "version": "0.0.11", "resolved": "https://registry.npmjs.org/pause-stream/-/pause-stream-0.0.11.tgz", @@ -4802,6 +5043,43 @@ "simple-concat": "^1.0.0" } }, + "sinon": { + "version": "13.0.2", + "resolved": "https://registry.npmjs.org/sinon/-/sinon-13.0.2.tgz", + "integrity": "sha512-KvOrztAVqzSJWMDoxM4vM+GPys1df2VBoXm+YciyB/OLMamfS3VXh3oGh5WtrAGSzrgczNWFFY22oKb7Fi5eeA==", + "dev": true, + "requires": { + "@sinonjs/commons": "^1.8.3", + "@sinonjs/fake-timers": "^9.1.2", + "@sinonjs/samsam": "^6.1.1", + "diff": "^5.0.0", + "nise": "^5.1.1", + "supports-color": "^7.2.0" + }, + "dependencies": { + "diff": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-5.0.0.tgz", + "integrity": "sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, "source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", @@ -4970,6 +5248,12 @@ "safe-buffer": "^5.0.1" } }, + "type-detect": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", + "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", + "dev": true + }, "typed-rest-client": { "version": "1.8.6", "resolved": "https://registry.npmjs.org/typed-rest-client/-/typed-rest-client-1.8.6.tgz", diff --git a/package.json b/package.json index 7a5ed1c..46a7d83 100644 --- a/package.json +++ b/package.json @@ -21,7 +21,8 @@ "Snippets" ], "activationEvents": [ - "onLanguage:smithy" + "onLanguage:smithy", + "onCommand:smithy.runSelector" ], "main": "./out/src/extension", "preview": true, @@ -52,6 +53,28 @@ "path": "./snippets.json" } ], + "commands": [ + { + "command": "smithy.runSelector", + "title": "Smithy:Selector:Run" + }, + { + "command": "smithy.clearSelector", + "title": "Smithy:Selector:Clear" + } + ], + "menus": { + "commandPalette": [ + { + "command": "smithy.runSelector", + "when": "editorLangId == smithy" + }, + { + "command": "smithy.clearSelector", + "when": "editorLangId == smithy" + } + ] + }, "configuration": { "type": "object", "title": "vscode-smithy configuration", @@ -97,11 +120,13 @@ "@types/glob": "^7.2.0", "@types/mocha": "^9.1.0", "@types/node": "^17.0.23", + "@types/sinon": "^10.0.11", "@types/vscode": "^1.65.0", "@vscode/test-electron": "^2.1.3", "glob": "^7.1.6", "mocha": "^9.2.2", "prettier": "^2.6.2", + "sinon": "^13.0.2", "typescript": "^4.6.3", "vsce": "^2.7.0", "vscode-nls-dev": "^4.0.0" diff --git a/src/extension.ts b/src/extension.ts index acaf7c7..db5bea7 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -3,6 +3,8 @@ import * as fs from "fs"; import * as child_process from "child_process"; import { workspace, ExtensionContext } from "vscode"; import * as vscode from "vscode"; +import { SelectorDecorator } from "./selector/selector-decorator"; +import { selectorRunCommandHandler, selectorClearCommandHandler } from "./selector/selector-command-handlers"; import { CancellationToken, @@ -122,9 +124,23 @@ export function activate(context: ExtensionContext) { // Create the language client and start the client. client = new LanguageClient("smithyLsp", "Smithy LSP", createServer, clientOptions); + + // Set client on `this` context to use with command handlers. + this.client = client; + const smithyContentProvider = createSmithyContentProvider(client); context.subscriptions.push(workspace.registerTextDocumentContentProvider("smithyjar", smithyContentProvider)); + // Set default expression input, and use context to hold state between command invocations. + this.expression = "Enter Selector Expression"; + this.selectorDecorator = new SelectorDecorator(); + + // Register selector commands. + context.subscriptions.push(vscode.commands.registerCommand("smithy.runSelector", selectorRunCommandHandler, this)); + context.subscriptions.push( + vscode.commands.registerCommand("smithy.clearSelector", selectorClearCommandHandler, this) + ); + // Start the client. This will also launch the server client.start(); } diff --git a/src/selector/selector-command-handlers.ts b/src/selector/selector-command-handlers.ts new file mode 100644 index 0000000..5f5c9a4 --- /dev/null +++ b/src/selector/selector-command-handlers.ts @@ -0,0 +1,31 @@ +import { Position, Range, window } from "vscode"; +import { SelectorCommandRequest } from "./selector-command-request"; + +export async function selectorClearCommandHandler() { + this.selectorDecorator.clear(); +} + +export async function selectorRunCommandHandler() { + const expression = await window.showInputBox({ + title: "Run a selector", + value: this.expression, + }); + const decorator = this.selectorDecorator; + let response = []; + // Don't do anything if expression was not populated. + if (expression) { + decorator.clear(); + this.expression = expression; + response = await this.client.sendRequest(SelectorCommandRequest.type, { expression: expression }); + const activeEditor = window.activeTextEditor; + const ranges = []; + for (const location of response) { + if (location["uri"].endsWith(activeEditor.document.fileName)) { + const startPosition = new Position(location["range"]["start"]["line"], location["range"]["start"]["character"]); + const endPosition = new Position(location["range"]["end"]["line"], location["range"]["end"]["character"]); + ranges.push(new Range(startPosition, endPosition)); + } + } + decorator.set(activeEditor, ranges); + } +} diff --git a/src/selector/selector-command-request.ts b/src/selector/selector-command-request.ts new file mode 100644 index 0000000..757d2c5 --- /dev/null +++ b/src/selector/selector-command-request.ts @@ -0,0 +1,9 @@ +import { RequestType } from "vscode-languageclient"; + +interface SelectorParams { + expression: String; +} + +export namespace SelectorCommandRequest { + export const type = new RequestType("smithy/selectorCommand"); +} diff --git a/src/selector/selector-decorator.ts b/src/selector/selector-decorator.ts new file mode 100644 index 0000000..449195f --- /dev/null +++ b/src/selector/selector-decorator.ts @@ -0,0 +1,35 @@ +import { Range, TextEditor, TextEditorDecorationType, window, workspace } from "vscode"; + +export interface ISelectorDecorator { + getDecorationType(): TextEditorDecorationType; + clear(): void; + set(textEditor: TextEditor, ranges: readonly Range[]): void; +} + +export class SelectorDecorator { + private decorationType: TextEditorDecorationType; + + constructor() { + this.decorationType = this.createDecorationType(); + } + + getDecorationType(): TextEditorDecorationType { + return this.decorationType; + } + + clear(): void { + this.decorationType.dispose(); + this.decorationType = this.createDecorationType(); + } + + createDecorationType(): TextEditorDecorationType { + return window.createTextEditorDecorationType({ + border: "dotted", + borderColor: "#C44536", + }); + } + + set(textEditor: TextEditor, ranges: readonly Range[]): void { + textEditor.setDecorations(this.decorationType, ranges); + } +} diff --git a/test-fixtures/suite1/main.smithy b/test-fixtures/suite1/main.smithy new file mode 100644 index 0000000..7c2b0c4 --- /dev/null +++ b/test-fixtures/suite1/main.smithy @@ -0,0 +1,24 @@ +$version: "1.0" + +namespace example.weather + +/// Provides weather forecasts. +service Weather { + version: "2006-03-01", + operations: [GetCurrentTime] +} + +@readonly +operation GetCurrentTime { + input: GetCurrentTimeInput, + output: GetCurrentTimeOutput +} + +@input +structure GetCurrentTimeInput {} + +@output +structure GetCurrentTimeOutput { + @required + time: Timestamp +} diff --git a/test-fixture/smithy-build.json b/test-fixtures/suite1/smithy-build.json similarity index 100% rename from test-fixture/smithy-build.json rename to test-fixtures/suite1/smithy-build.json diff --git a/test-fixtures/suite2/main.smithy b/test-fixtures/suite2/main.smithy new file mode 100644 index 0000000..ebaae02 --- /dev/null +++ b/test-fixtures/suite2/main.smithy @@ -0,0 +1,9 @@ +$version: "1.0" + +namespace example.weather + +/// Provides weather forecasts. +service Weather { + version: "2006-03-01", + operations: [GetCurrentTime] +} diff --git a/test-fixtures/suite2/smithy-build.json b/test-fixtures/suite2/smithy-build.json new file mode 100644 index 0000000..53f0363 --- /dev/null +++ b/test-fixtures/suite2/smithy-build.json @@ -0,0 +1,6 @@ +{ + "maven": { + "dependencies": ["software.amazon.smithy:smithy-aws-traits:1.19.0"], + "repositories": [{ "url": "" }] + } +} diff --git a/test-fixture/main.smithy b/test-fixtures/suite3/main.smithy similarity index 99% rename from test-fixture/main.smithy rename to test-fixtures/suite3/main.smithy index d9155bc..5c28adb 100644 --- a/test-fixture/main.smithy +++ b/test-fixtures/suite3/main.smithy @@ -15,7 +15,6 @@ resource City { identifiers: { cityId: CityId }, read: GetCity, list: ListCities, - delete: DeleteCity, resources: [Forecast], } diff --git a/test-fixtures/suite3/smithy-build.json b/test-fixtures/suite3/smithy-build.json new file mode 100644 index 0000000..53f0363 --- /dev/null +++ b/test-fixtures/suite3/smithy-build.json @@ -0,0 +1,6 @@ +{ + "maven": { + "dependencies": ["software.amazon.smithy:smithy-aws-traits:1.19.0"], + "repositories": [{ "url": "" }] + } +} diff --git a/tests/helper.ts b/tests/helper.ts new file mode 100644 index 0000000..21e96d7 --- /dev/null +++ b/tests/helper.ts @@ -0,0 +1,49 @@ +import { TextDocument, TextEditor, Uri, workspace } from "vscode"; +import { resolve } from "path"; +import { glob } from "glob"; +import * as Mocha from "mocha"; + +export let doc: TextDocument; +export let editor: TextEditor; + +export const getDocPath = (p: string) => { + return resolve(__dirname, "../../test-fixtures", p); +}; + +export const getDocUri = (p: string) => { + return Uri.file(getDocPath(p)); +}; + +export async function waitForServerStartup() { + // Wait for Smithy Language Server to start + await new Promise((resolve) => setTimeout(resolve, 6000)); +} + +export async function getLangServerLogs(p: string): Promise { + const smithyLogUri = getDocUri(p + "/.smithy.lsp.log"); + const smithyLog = await workspace.openTextDocument(smithyLogUri); + return smithyLog.getText(); +} + +export function runTests(testsRoot: string, cb: (error: any, failures?: number) => void): void { + const mocha = new Mocha({ + ui: "tdd", + }); + + glob("**/**.test.js", { cwd: testsRoot }, (err, files) => { + if (err) { + return cb(err); + } + + files.forEach((f) => mocha.addFile(resolve(testsRoot, f))); + + try { + mocha.run((failures) => { + cb(null, failures); + }); + } catch (err) { + console.error(err); + cb(err); + } + }); +} diff --git a/tests/runTest.ts b/tests/runTest.ts index da5495a..80b8d9b 100644 --- a/tests/runTest.ts +++ b/tests/runTest.ts @@ -1,17 +1,29 @@ -import * as path from "path"; +import { resolve } from "path"; import { runTests } from "@vscode/test-electron"; async function go() { try { - const extensionDevelopmentPath = path.resolve(__dirname, "../../"); - const extensionTestsPath = path.resolve(__dirname, "./suite"); - const testWorkspace = path.resolve(__dirname, "../../test-fixture"); + const extensionDevelopmentPath = resolve(__dirname, "../../"); + // Suite 1 - Extension registration and launching language server await runTests({ extensionDevelopmentPath, - extensionTestsPath, - launchArgs: [testWorkspace], + extensionTestsPath: resolve(__dirname, "./suite1"), + launchArgs: [resolve(__dirname, "../../test-fixtures/suite1")], + }); + + // Suite 2 - Diagnostics from broken model + await runTests({ + extensionDevelopmentPath, + extensionTestsPath: resolve(__dirname, "./suite2"), + launchArgs: [resolve(__dirname, "../../test-fixtures/suite2")], + }); + + await runTests({ + extensionDevelopmentPath, + extensionTestsPath: resolve(__dirname, "./suite3"), + launchArgs: [resolve(__dirname, "../../test-fixtures/suite3")], }); } catch (err) { console.error("Failed to run tests"); diff --git a/tests/suite1/extension.test.ts b/tests/suite1/extension.test.ts new file mode 100644 index 0000000..31222e1 --- /dev/null +++ b/tests/suite1/extension.test.ts @@ -0,0 +1,27 @@ +import * as assert from "assert"; +import * as vscode from "vscode"; +import { getDocUri, getLangServerLogs, waitForServerStartup } from "./../helper"; + +suite("Extension tests", () => { + test("Should start extension and Language Server", async () => { + const smithyMainUri = getDocUri("suite1/main.smithy"); + const doc = await vscode.workspace.openTextDocument(smithyMainUri); + const editor = await vscode.window.showTextDocument(doc); + const ext = vscode.extensions.getExtension("aws-smithy.smithy-vscode"); + await waitForServerStartup(); + + // Grab Language Server logs + const logText = await getLangServerLogs("suite1"); + + assert.notEqual(doc, undefined); + assert.notEqual(editor, undefined); + assert.equal(ext.isActive, true); + assert.match(logText, /Downloaded external jars.*smithy-aws-traits-1\.19\.0\.jar/); + assert.match(logText, /Discovered smithy files.*\/main.smithy]/); + }).timeout(7000); + + test("Should register language", async () => { + const languages = await vscode.languages.getLanguages(); + assert.equal(languages.includes("smithy"), true); + }).timeout(1000); +}); diff --git a/tests/suite1/index.ts b/tests/suite1/index.ts new file mode 100644 index 0000000..7b4a869 --- /dev/null +++ b/tests/suite1/index.ts @@ -0,0 +1,5 @@ +import { runTests } from "../helper"; + +export function run(testsRoot: string, cb: (error: any, failures?: number) => void): void { + runTests(testsRoot, cb); +} diff --git a/tests/suite2/extension.test.ts b/tests/suite2/extension.test.ts new file mode 100644 index 0000000..d8bf165 --- /dev/null +++ b/tests/suite2/extension.test.ts @@ -0,0 +1,17 @@ +import * as assert from "assert"; +import * as vscode from "vscode"; +import { getDocUri, waitForServerStartup } from "./../helper"; + +suite("broken model tests", () => { + test("Should provide diagnostics", async () => { + const smithyMainUri = getDocUri("suite2/main.smithy"); + const doc = await vscode.workspace.openTextDocument(smithyMainUri); + const editor = await vscode.window.showTextDocument(doc); + await waitForServerStartup(); + const diagnostics = vscode.languages.getDiagnostics(smithyMainUri); + + assert.match(diagnostics[0].message, /relationship to an unresolved shape `example.weather#GetCurrentTime`/); + assert.equal(diagnostics[0].range.start.line, 5); + assert.equal(diagnostics[0].range.start.character, 0); + }).timeout(7000); +}); diff --git a/tests/suite2/index.ts b/tests/suite2/index.ts new file mode 100644 index 0000000..7b4a869 --- /dev/null +++ b/tests/suite2/index.ts @@ -0,0 +1,5 @@ +import { runTests } from "../helper"; + +export function run(testsRoot: string, cb: (error: any, failures?: number) => void): void { + runTests(testsRoot, cb); +} diff --git a/tests/suite3/extension.test.ts b/tests/suite3/extension.test.ts new file mode 100644 index 0000000..f6ea2d4 --- /dev/null +++ b/tests/suite3/extension.test.ts @@ -0,0 +1,20 @@ +import * as assert from "assert"; +import * as vscode from "vscode"; +import { getDocUri, getLangServerLogs, waitForServerStartup } from "./../helper"; +import * as sinon from "sinon"; + +suite("Selector tests", () => { + test("Can run selectors", async () => { + const smithyMainUri = getDocUri("suite3/main.smithy"); + const doc = await vscode.workspace.openTextDocument(smithyMainUri); + await vscode.window.showTextDocument(doc); + await waitForServerStartup(); + + const showInputBox = sinon.stub(vscode.window, "showInputBox"); + showInputBox.resolves("operation [id|namespace=example.weather]"); + await vscode.commands.executeCommand("smithy.runSelector"); + const logText = await getLangServerLogs("suite3"); + + assert.match(logText, /Selector command found 4 matching shapes/); + }).timeout(10000); +}); diff --git a/tests/suite3/index.ts b/tests/suite3/index.ts new file mode 100644 index 0000000..7b4a869 --- /dev/null +++ b/tests/suite3/index.ts @@ -0,0 +1,5 @@ +import { runTests } from "../helper"; + +export function run(testsRoot: string, cb: (error: any, failures?: number) => void): void { + runTests(testsRoot, cb); +}