diff --git a/.editorconfig b/.editorconfig index 0c75543..d37bd4b 100644 --- a/.editorconfig +++ b/.editorconfig @@ -7,6 +7,9 @@ indent_style = tab insert_final_newline = true trim_trailing_whitespace = true +[test/workspace/**/*.txt] +trim_trailing_whitespace = false + [*.{yml,yaml}] indent_size = 2 indent_style = space diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 4c48deb..6fd9ec5 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -12,7 +12,7 @@ on: jobs: lint-and-compile-code: - runs-on: ubuntu-latest + runs-on: windows-latest steps: - name: Checkout Code uses: actions/checkout@v3 @@ -32,3 +32,5 @@ jobs: run: npm run lint - name: Compile Code run: npm run compile + - name: Test Code + run: npm run test diff --git a/.gitignore b/.gitignore index 49cdee3..ff98f55 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ +.vscode-test/ dist/ node_modules/ *.vsix diff --git a/.vscodeignore b/.vscodeignore index 315c8d9..9c9950e 100644 --- a/.vscodeignore +++ b/.vscodeignore @@ -1,6 +1,7 @@ +.vscode/ +.vscode-test/ .github/** .husky/** -.vscode/** **/node_modules **/.eslintignore **/.eslintrc.json diff --git a/package-lock.json b/package-lock.json index b8be7d5..dcac607 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,15 +13,18 @@ }, "devDependencies": { "@types/glob": "^8.1.0", - "@types/node": "20.2.5", + "@types/mocha": "^10.0.6", + "@types/node": "^20.2.5", "@types/node-fetch": "^2.6.6", + "@types/vscode": "^1.86.0", "@typescript-eslint/eslint-plugin": "^5.59.8", "@typescript-eslint/parser": "^5.59.8", - "@vscode/test-electron": "^2.3.2", + "@vscode/test-electron": "^2.3.9", "eslint": "^8.41.0", "glob": "^8.1.0", "husky": "^8.0.3", "merge-options": "^3.0.4", + "mocha": "^10.3.0", "rimraf": "^5.0.5", "ts-loader": "^9.4.3", "typescript": "^5.1.3", @@ -395,6 +398,12 @@ "integrity": "sha512-K0VQKziLUWkVKiRVrx4a40iPaxTUefQmjtkQofBkYRcoaaL/8rhwDWww9qWbrgicNOgnpIsMxyNIUM4+n6dUIA==", "dev": true }, + "node_modules/@types/mocha": { + "version": "10.0.6", + "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-10.0.6.tgz", + "integrity": "sha512-dJvrYWxP/UcXm36Qn36fxhUKu8A/xMRXVT2cliFF1Z7UA9liG5Psj3ezNSZw+5puH2czDXRLcXQxf8JbJt0ejg==", + "dev": true + }, "node_modules/@types/node": { "version": "20.2.5", "resolved": "https://registry.npmjs.org/@types/node/-/node-20.2.5.tgz", @@ -417,6 +426,12 @@ "integrity": "sha512-G8hZ6XJiHnuhQKR7ZmysCeJWE08o8T0AXtk5darsCaTVsYZhhgUrq53jizaR2FvsoeCwJhlmwTjkXBY5Pn/ZHw==", "dev": true }, + "node_modules/@types/vscode": { + "version": "1.86.0", + "resolved": "https://registry.npmjs.org/@types/vscode/-/vscode-1.86.0.tgz", + "integrity": "sha512-DnIXf2ftWv+9LWOB5OJeIeaLigLHF7fdXF6atfc7X5g2w/wVZBgk0amP7b+ub5xAuW1q7qP5YcFvOcit/DtyCQ==", + "dev": true + }, "node_modules/@typescript-eslint/eslint-plugin": { "version": "5.62.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.62.0.tgz", @@ -606,9 +621,9 @@ } }, "node_modules/@vscode/test-electron": { - "version": "2.3.4", - "resolved": "https://registry.npmjs.org/@vscode/test-electron/-/test-electron-2.3.4.tgz", - "integrity": "sha512-eWzIqXMhvlcoXfEFNWrVu/yYT5w6De+WZXR/bafUQhAp8+8GkQo95Oe14phwiRUPv8L+geAKl/QM2+PoT3YW3g==", + "version": "2.3.9", + "resolved": "https://registry.npmjs.org/@vscode/test-electron/-/test-electron-2.3.9.tgz", + "integrity": "sha512-z3eiChaCQXMqBnk2aHHSEkobmC2VRalFQN0ApOAtydL172zXGxTwGrRtviT5HnUB+Q+G3vtEYFtuQkYqBzYgMA==", "dev": true, "dependencies": { "http-proxy-agent": "^4.0.1", @@ -889,6 +904,15 @@ "ajv": "^6.9.1" } }, + "node_modules/ansi-colors": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", + "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, "node_modules/ansi-regex": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", @@ -913,6 +937,19 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, "node_modules/argparse": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", @@ -940,6 +977,15 @@ "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", "dev": true }, + "node_modules/binary-extensions": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", + "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, "node_modules/brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", @@ -962,6 +1008,12 @@ "node": ">=8" } }, + "node_modules/browser-stdout": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", + "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", + "dev": true + }, "node_modules/browserslist": { "version": "4.21.10", "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.10.tgz", @@ -1009,6 +1061,18 @@ "node": ">=6" } }, + "node_modules/camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/caniuse-lite": { "version": "1.0.30001519", "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001519.tgz", @@ -1045,6 +1109,45 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, + "node_modules/chokidar": { + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", + "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + ], + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/chokidar/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/chrome-trace-event": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.3.tgz", @@ -1054,6 +1157,17 @@ "node": ">=6.0" } }, + "node_modules/cliui": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", + "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", + "dev": true, + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" + } + }, "node_modules/clone-deep": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-4.0.1.tgz", @@ -1161,6 +1275,18 @@ } } }, + "node_modules/decamelize": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-4.0.0.tgz", + "integrity": "sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/deep-is": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", @@ -1176,6 +1302,15 @@ "node": ">=0.4.0" } }, + "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/dir-glob": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", @@ -1696,6 +1831,20 @@ "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", "dev": true }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, "node_modules/function-bind": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", @@ -1705,6 +1854,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true, + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, "node_modules/glob": { "version": "8.1.0", "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", @@ -1831,6 +1989,15 @@ "node": ">= 0.4" } }, + "node_modules/he": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", + "dev": true, + "bin": { + "he": "bin/he" + } + }, "node_modules/http-proxy-agent": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-4.0.1.tgz", @@ -1957,6 +2124,18 @@ "node": ">=10.13.0" } }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/is-core-module": { "version": "2.13.1", "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.1.tgz", @@ -2038,6 +2217,18 @@ "node": ">=0.10.0" } }, + "node_modules/is-unicode-supported": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", + "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/isarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", @@ -2209,6 +2400,22 @@ "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", "dev": true }, + "node_modules/log-symbols": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", + "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", + "dev": true, + "dependencies": { + "chalk": "^4.1.0", + "is-unicode-supported": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/lru-cache": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", @@ -2303,6 +2510,83 @@ "node": ">=16 || 14 >=14.17" } }, + "node_modules/mocha": { + "version": "10.3.0", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-10.3.0.tgz", + "integrity": "sha512-uF2XJs+7xSLsrmIvn37i/wnc91nw7XjOQB8ccyx5aEgdnohr7n+rEiZP23WkCYHjilR6+EboEnbq/ZQDz4LSbg==", + "dev": true, + "dependencies": { + "ansi-colors": "4.1.1", + "browser-stdout": "1.3.1", + "chokidar": "3.5.3", + "debug": "4.3.4", + "diff": "5.0.0", + "escape-string-regexp": "4.0.0", + "find-up": "5.0.0", + "glob": "8.1.0", + "he": "1.2.0", + "js-yaml": "4.1.0", + "log-symbols": "4.1.0", + "minimatch": "5.0.1", + "ms": "2.1.3", + "serialize-javascript": "6.0.0", + "strip-json-comments": "3.1.1", + "supports-color": "8.1.1", + "workerpool": "6.2.1", + "yargs": "16.2.0", + "yargs-parser": "20.2.4", + "yargs-unparser": "2.0.0" + }, + "bin": { + "_mocha": "bin/_mocha", + "mocha": "bin/mocha.js" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/mocha/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/mocha/node_modules/minimatch": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.0.1.tgz", + "integrity": "sha512-nLDxIFRyhDblz3qMuq+SoRZED4+miJ/G+tdDrjkkkRnjAsBexeGpgjLEQ0blJy7rHhR2b93rhQY4SvyWu9v03g==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/mocha/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true + }, + "node_modules/mocha/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, "node_modules/ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", @@ -2368,6 +2652,15 @@ "integrity": "sha512-uYr7J37ae/ORWdZeQ1xxMJe3NtdmqMC/JZK+geofDrkLUApKRHPd18/TxtBOJ4A0/+uUIliorNrfYV6s1b02eQ==", "dev": true }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", @@ -2668,6 +2961,18 @@ "util-deprecate": "~1.0.1" } }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, "node_modules/rechoir": { "version": "0.8.0", "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.8.0.tgz", @@ -2680,6 +2985,15 @@ "node": ">= 10.13.0" } }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/resolve": { "version": "1.22.8", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", @@ -2863,6 +3177,15 @@ "node": ">=10" } }, + "node_modules/serialize-javascript": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.0.tgz", + "integrity": "sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag==", + "dev": true, + "dependencies": { + "randombytes": "^2.1.0" + } + }, "node_modules/setimmediate": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", @@ -3417,6 +3740,29 @@ "integrity": "sha512-CC1bOL87PIWSBhDcTrdeLo6eGT7mCFtrg0uIJtqJUFyK+eJnzl8A1niH56uu7KMa5XFrtiV+AQuHO3n7DsHnLQ==", "dev": true }, + "node_modules/workerpool": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.2.1.tgz", + "integrity": "sha512-ILEIE97kDZvF9Wb9f6h5aXK4swSlKGUcOEGiIYb2OOu/IrDU9iwj0fD//SsA6E5ibwJxpEvhullJY4Sl4GcpAw==", + "dev": true + }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, "node_modules/wrap-ansi-cjs": { "name": "wrap-ansi", "version": "7.0.0", @@ -3441,12 +3787,63 @@ "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", "dev": true }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, + "engines": { + "node": ">=10" + } + }, "node_modules/yallist": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", "dev": true }, + "node_modules/yargs": { + "version": "16.2.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", + "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", + "dev": true, + "dependencies": { + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^20.2.2" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs-parser": { + "version": "20.2.4", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.4.tgz", + "integrity": "sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs-unparser": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-2.0.0.tgz", + "integrity": "sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA==", + "dev": true, + "dependencies": { + "camelcase": "^6.0.0", + "decamelize": "^4.0.0", + "flat": "^5.0.2", + "is-plain-obj": "^2.1.0" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/yocto-queue": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", diff --git a/package.json b/package.json index ed3e6a2..523f7be 100644 --- a/package.json +++ b/package.json @@ -87,20 +87,24 @@ "lint:client": "eslint --config ./client/.eslintrc.json ./client/src/*.ts", "lint:server": "eslint --config ./server/.eslintrc.json ./server/src/*.ts", "lint:fix": "npm run lint:client --fix && npm run lint:server --fix", + "test": "tsc -p ./test && node ./test/dist/run-tests.js", "webpack": "npm run clean && webpack --mode production --config ./client/webpack.config.js && webpack --mode production --config ./server/webpack.config.js", "webpack:dev": "npm run clean && webpack --mode development --config ./client/webpack.config.js && webpack --mode development --config ./server/webpack.config.js" }, "devDependencies": { "@types/glob": "^8.1.0", - "@types/node": "20.2.5", + "@types/mocha": "^10.0.6", + "@types/node": "^20.2.5", "@types/node-fetch": "^2.6.6", + "@types/vscode": "^1.86.0", "@typescript-eslint/eslint-plugin": "^5.59.8", "@typescript-eslint/parser": "^5.59.8", - "@vscode/test-electron": "^2.3.2", + "@vscode/test-electron": "^2.3.9", "eslint": "^8.41.0", "glob": "^8.1.0", "husky": "^8.0.3", "merge-options": "^3.0.4", + "mocha": "^10.3.0", "rimraf": "^5.0.5", "ts-loader": "^9.4.3", "typescript": "^5.1.3", diff --git a/test/.eslintrc.json b/test/.eslintrc.json new file mode 100644 index 0000000..282f020 --- /dev/null +++ b/test/.eslintrc.json @@ -0,0 +1,4 @@ +{ + "extends": "../.eslintrc.base.json", + "rules": {} +} diff --git a/test/src/run-tests.ts b/test/src/run-tests.ts new file mode 100644 index 0000000..87ce1b5 --- /dev/null +++ b/test/src/run-tests.ts @@ -0,0 +1,23 @@ +import { runTests } from "@vscode/test-electron"; +import * as path from "path"; + +async function main(): Promise { + try { + // The folder containing the Extension Manifest `package.json` file which is passed to `--extensionDevelopmentPath` + const extensionDevelopmentPath: string = path.resolve(__dirname, "../../"); + + // The path to test runner which is passed to `--extensionTestsPath` + const extensionTestsPath: string = path.resolve(__dirname, "./suite/index"); + + // Download Visual Studio Code, unzip it, and run the integration tests + await runTests({ + extensionDevelopmentPath, + extensionTestsPath + }); + } catch (error: unknown) { + console.error("Failed to run tests", error); + process.exit(1); + } +} + +main(); diff --git a/test/src/suite/autocompletion.test.ts b/test/src/suite/autocompletion.test.ts new file mode 100644 index 0000000..f613ba6 --- /dev/null +++ b/test/src/suite/autocompletion.test.ts @@ -0,0 +1,112 @@ +import * as assert from "assert"; +import * as vscode from "vscode"; +import { + activateRobotsDotTextExtension, + openDocumentByRelativeUri +} from "../utils/extension"; + +async function getAutocompletionItemsAtPosition( + textDocument: vscode.TextDocument, + cursorPosition: vscode.Position +): Promise { + return ( + await vscode.commands.executeCommand( + "vscode.executeCompletionItemProvider", + textDocument.uri, + cursorPosition + ) + ) as vscode.CompletionList; +} + +async function assertAutocompletionListContainsItems( + textDocument: vscode.TextDocument, + cursorPosition: vscode.Position, + expectedAutocompletionList: vscode.CompletionList +): Promise { + const autocompletionList: vscode.CompletionList = await getAutocompletionItemsAtPosition( + textDocument, + cursorPosition + ); + + if (expectedAutocompletionList.items.length > 0) { + assert.ok( + autocompletionList.items.length > 0, + "Autocompletion list was empty" + ); + } + + for (const expectedAutocompletionItem of expectedAutocompletionList.items) { + let equivalentAutocompletionItem: vscode.CompletionItem | undefined = undefined; + + for (const autocompletionItem of autocompletionList.items) { + if (autocompletionItem.label === expectedAutocompletionItem.label) { + equivalentAutocompletionItem = autocompletionItem; + break; + } + } + + if (equivalentAutocompletionItem !== undefined) { + assert.equal( + equivalentAutocompletionItem.kind, + expectedAutocompletionItem.kind, + `Autocompletion kind for '${equivalentAutocompletionItem.label}' does not match` + ); + } else { + throw new Error(`'${expectedAutocompletionItem.label}' was not found in the autocompletion list`); + } + } +} + +suite("Robots.txt Language Support for Visual Studio Code Autocompletion Test Suite", () => { + setup(async () => { + await activateRobotsDotTextExtension(); + }); + + teardown(async () => { + await vscode.commands.executeCommand("workbench.action.closeActiveEditor"); + }); + + test("'User-agent' Directive Has Autocompletion Items", async () => { + const textEditor: vscode.TextEditor = await openDocumentByRelativeUri("autocompletion/directive.txt"); + + await assertAutocompletionListContainsItems( + textEditor.document, + new vscode.Position(0, 12), + { + items: [ + { label: "*", kind: vscode.CompletionItemKind.Value }, + { label: "Bingbot", kind: vscode.CompletionItemKind.Value }, + { label: "Googlebot", kind: vscode.CompletionItemKind.Value } + ] + } + ); + }); + + test("'Allow' Directive Has '/' in Autocompletion List", async () => { + const textEditor: vscode.TextEditor = await openDocumentByRelativeUri("autocompletion/directive.txt"); + + await assertAutocompletionListContainsItems( + textEditor.document, + new vscode.Position(1, 7), + { + items: [ + { label: "/", kind: vscode.CompletionItemKind.Value } + ] + } + ); + }); + + test("'Disallow' Directive Has '/' in Autocompletion List", async () => { + const textEditor: vscode.TextEditor = await openDocumentByRelativeUri("autocompletion/directive.txt"); + + await assertAutocompletionListContainsItems( + textEditor.document, + new vscode.Position(2, 10), + { + items: [ + { label: "/", kind: vscode.CompletionItemKind.Value } + ] + } + ); + }); +}); diff --git a/test/src/suite/diagnostics.test.ts b/test/src/suite/diagnostics.test.ts new file mode 100644 index 0000000..75a6bf5 --- /dev/null +++ b/test/src/suite/diagnostics.test.ts @@ -0,0 +1,208 @@ +import * as assert from "assert"; +import * as vscode from "vscode"; +import { + activateRobotsDotTextExtension, + openDocumentByRelativeUri +} from "../utils/extension"; + +const NO_USER_AGENT_SPECIFIED_MESSAGE: string = "No user-agent specified."; + +const CRAWL_DELAY_GREATER_EQUAL_ZERO_MESSAGE: string = "`Crawl-delay` must be a number greater than or equal to zero."; + +const SITEMAP_ABSOLUTE_URL_MESSAGE: string = "`Sitemap` must be an absolute URL."; + +function assertDiagnosticArrayLengthEqual( + diagnostics: vscode.Diagnostic[], + expectedLength: number +): void { + assert.equal(diagnostics.length, expectedLength, "Diagnostics array length does not match"); +} + +function assertDiagnosticMatch( + diagnostic: vscode.Diagnostic, + range: vscode.Range, + expectedMessage: string +): void { + assert.equal( + diagnostic.message, + expectedMessage, + "Diagnostic message does not match" + ); + + assert.equal( + range.start.line, + diagnostic.range.start.line, + "Start range line does not match" + ); + + assert.equal( + range.start.character, + diagnostic.range.start.character, + "Start range character does not match" + ); + + assert.equal( + range.end.line, + diagnostic.range.end.line, + "End range line does not match" + ); + + assert.equal( + range.end.character, + diagnostic.range.end.character, + "End range character does not match" + ); +} + +suite("Robots.txt Language Support for Visual Studio Code Diagnostics Test Suite", () => { + setup(async () => { + await activateRobotsDotTextExtension(); + }); + + teardown(async () => { + await vscode.commands.executeCommand("workbench.action.closeActiveEditor"); + }); + + test("'No user-agent specified' Diagnostics Works", async () => { + const textEditor: vscode.TextEditor = await openDocumentByRelativeUri("diagnostics/no-user-agent.txt"); + const diagnostics: vscode.Diagnostic[] = vscode.languages.getDiagnostics(textEditor.document.uri); + + assertDiagnosticArrayLengthEqual(diagnostics, 4); + + assertDiagnosticMatch( + diagnostics[0], + new vscode.Range( + new vscode.Position(0, 0), + new vscode.Position(0, 8) + ), + NO_USER_AGENT_SPECIFIED_MESSAGE + ); + + assertDiagnosticMatch( + diagnostics[1], + new vscode.Range( + new vscode.Position(1, 0), + new vscode.Position(1, 11) + ), + NO_USER_AGENT_SPECIFIED_MESSAGE + ); + + assertDiagnosticMatch( + diagnostics[2], + new vscode.Range( + new vscode.Position(2, 0), + new vscode.Position(2, 15) + ), + NO_USER_AGENT_SPECIFIED_MESSAGE + ); + + assertDiagnosticMatch( + diagnostics[3], + new vscode.Range( + new vscode.Position(3, 0), + new vscode.Position(3, 33) + ), + NO_USER_AGENT_SPECIFIED_MESSAGE + ); + }); + + test("'User-agent' Directive Solves 'No user-agent specified' Diagnostic", async () => { + const textEditor: vscode.TextEditor = await openDocumentByRelativeUri("diagnostics/user-agent.txt"); + const diagnostics: vscode.Diagnostic[] = vscode.languages.getDiagnostics(textEditor.document.uri); + + assertDiagnosticArrayLengthEqual(diagnostics, 0); + }); + + test("'Crawl-delay' Directive Diagnostics Work", async () => { + const textEditor: vscode.TextEditor = await openDocumentByRelativeUri("diagnostics/crawl-delay.txt"); + const diagnostics: vscode.Diagnostic[] = vscode.languages.getDiagnostics(textEditor.document.uri); + + assertDiagnosticArrayLengthEqual(diagnostics, 3); + + assertDiagnosticMatch( + diagnostics[0], + new vscode.Range( + new vscode.Position(1, 11), + new vscode.Position(1, 11) + ), + CRAWL_DELAY_GREATER_EQUAL_ZERO_MESSAGE + ); + + assertDiagnosticMatch( + diagnostics[1], + new vscode.Range( + new vscode.Position(2, 13), + new vscode.Position(2, 14) + ), + CRAWL_DELAY_GREATER_EQUAL_ZERO_MESSAGE + ); + + assertDiagnosticMatch( + diagnostics[2], + new vscode.Range( + new vscode.Position(3, 13), + new vscode.Position(3, 15) + ), + CRAWL_DELAY_GREATER_EQUAL_ZERO_MESSAGE + ); + }); + + test("'Sitemap' Directive Diagnostics Work", async () => { + const textEditor: vscode.TextEditor = await openDocumentByRelativeUri("diagnostics/sitemap.txt"); + const diagnostics: vscode.Diagnostic[] = vscode.languages.getDiagnostics(textEditor.document.uri); + + assertDiagnosticArrayLengthEqual(diagnostics, 2); + + assertDiagnosticMatch( + diagnostics[0], + new vscode.Range( + new vscode.Position(1, 7), + new vscode.Position(1, 7) + ), + SITEMAP_ABSOLUTE_URL_MESSAGE + ); + + assertDiagnosticMatch( + diagnostics[1], + new vscode.Range( + new vscode.Position(2, 9), + new vscode.Position(2, 21) + ), + SITEMAP_ABSOLUTE_URL_MESSAGE + ); + }); + + test("Unknown Directive Diagnostics Work", async () => { + const textEditor: vscode.TextEditor = await openDocumentByRelativeUri("diagnostics/unknown-directive.txt"); + const diagnostics: vscode.Diagnostic[] = vscode.languages.getDiagnostics(textEditor.document.uri); + + assertDiagnosticArrayLengthEqual(diagnostics, 3); + + assertDiagnosticMatch( + diagnostics[0], + new vscode.Range( + new vscode.Position(3, 0), + new vscode.Position(3, 10) + ), + "Unknown Robots.txt directive: `Directive`" + ); + + assertDiagnosticMatch( + diagnostics[1], + new vscode.Range( + new vscode.Position(4, 0), + new vscode.Position(4, 16) + ), + "Unknown Robots.txt directive: `Directive`" + ); + + assertDiagnosticMatch( + diagnostics[2], + new vscode.Range( + new vscode.Position(9, 0), + new vscode.Position(9, 12) + ), + "Unknown Robots.txt directive: `Invalid`" + ); + }); +}); diff --git a/test/src/suite/index.ts b/test/src/suite/index.ts new file mode 100644 index 0000000..67f9ace --- /dev/null +++ b/test/src/suite/index.ts @@ -0,0 +1,42 @@ +import * as glob from "glob"; +import * as Mocha from "mocha"; +import * as path from "path"; + +const MOCHA_TEST_TIMEOUT_MS: number = 60000; + +export function run(): Promise { + const mocha: Mocha = new Mocha({ + ui: "tdd", + color: true, + timeout: MOCHA_TEST_TIMEOUT_MS + }); + + const testsRoot: string = path.resolve(__dirname, ".."); + + return new Promise((resolve, reject) => { + glob("**/**.test.js", { cwd: testsRoot }, (error: Error | null, files: string[]) => { + if (error !== null) { + return reject(error); + } + + // Add files to the test suite + for (const file of files) { + mocha.addFile(path.resolve(testsRoot, file)); + } + + try { + // Run the Mocha tests + mocha.run(failures => { + if (failures > 0) { + reject(`${failures} tests failed.`); + } else { + resolve(); + } + }); + } catch (error: unknown) { + console.error(error); + reject(error); + } + }); + }); +} diff --git a/test/src/utils/extension.ts b/test/src/utils/extension.ts new file mode 100644 index 0000000..95b6384 --- /dev/null +++ b/test/src/utils/extension.ts @@ -0,0 +1,47 @@ +import * as path from "path"; +import * as vscode from "vscode"; +import { sleep } from "./misc"; + +const TEST_CONFIGURATION_WORKSPACE: string = path.resolve(__dirname, "../../workspace/"); + +export async function activateRobotsDotTextExtension( + sourceCodeDirectory: string | undefined = undefined +): Promise { + const uri: vscode.Uri = vscode.Uri.file(sourceCodeDirectory || TEST_CONFIGURATION_WORKSPACE); + + await vscode.commands.executeCommand("vscode.openFolder", uri); + + const extension: vscode.Extension | undefined = vscode.extensions.getExtension("darian-benam.vscode-robots-dot-txt-support"); + + if (extension === undefined) { + return; + } + + await extension.activate(); + + // Wait for the Robots.txt language server process to initialize + await sleep(2000); +} + +/** + * Opens a document by URI in Visual Studio Code. + * + * @param uri The URI to open in Visual Studio Code. + * @param languageId The langauge ID of the document that Visual Studio Code will display it as. + * @param delayMilliseconds The delay in milliseconds to allow the document to initialize (mainly used to wait for diagnostics). + * @returns A promise to the Visual Studio Code text editor that was opened. + */ +export async function openDocumentByRelativeUri( + uri: string, + languageId: string = "robots-txt", + delayMilliseconds: number = 1000 +): Promise { + const textDocument: vscode.TextDocument = await vscode.workspace.openTextDocument(path.resolve(TEST_CONFIGURATION_WORKSPACE, uri)); + const textEditor: vscode.TextEditor = await vscode.window.showTextDocument(textDocument); + + vscode.languages.setTextDocumentLanguage(textDocument, languageId); + + await sleep(delayMilliseconds); + + return textEditor; +} diff --git a/test/src/utils/misc.ts b/test/src/utils/misc.ts new file mode 100644 index 0000000..c1c3135 --- /dev/null +++ b/test/src/utils/misc.ts @@ -0,0 +1,3 @@ +export async function sleep(milliseconds: number): Promise { + return new Promise(resolve => setTimeout(resolve, milliseconds)); +} diff --git a/test/tsconfig.json b/test/tsconfig.json new file mode 100644 index 0000000..8a9fbf5 --- /dev/null +++ b/test/tsconfig.json @@ -0,0 +1,21 @@ +{ + "extends": "../tsconfig.base.json", + "compilerOptions": { + "module": "CommonJS", + "target": "ES2020", + "outDir": "dist", + "rootDir": "src", + "lib": [ + "ES2020" + ], + "sourceMap": true, + "paths": { + "@test/*": [ + "./src/*" + ] + } + }, + "include": [ + "src" + ] +} diff --git a/test/workspace/.vsccode/settings.json b/test/workspace/.vsccode/settings.json new file mode 100644 index 0000000..a150573 --- /dev/null +++ b/test/workspace/.vsccode/settings.json @@ -0,0 +1,3 @@ +{ + "editor.wordBasedSuggestions": "off" +} diff --git a/test/workspace/README.md b/test/workspace/README.md new file mode 100644 index 0000000..318fbf7 --- /dev/null +++ b/test/workspace/README.md @@ -0,0 +1,3 @@ +# Test Workspace + +All files and directories in this folder are used for the Mocha tests defined in the `src` directory above this directory. diff --git a/test/workspace/autocompletion/directive.txt b/test/workspace/autocompletion/directive.txt new file mode 100644 index 0000000..b35f0ac --- /dev/null +++ b/test/workspace/autocompletion/directive.txt @@ -0,0 +1,3 @@ +User-agent: +Allow: +Disallow: diff --git a/test/workspace/diagnostics/crawl-delay.txt b/test/workspace/diagnostics/crawl-delay.txt new file mode 100644 index 0000000..cdeaefd --- /dev/null +++ b/test/workspace/diagnostics/crawl-delay.txt @@ -0,0 +1,6 @@ +User-agent: * +Crawl-delay: +Crawl-delay: a +Crawl-delay: -1 +Crawl-delay: 0 +Crawl-delay: 1 diff --git a/test/workspace/diagnostics/no-user-agent.txt b/test/workspace/diagnostics/no-user-agent.txt new file mode 100644 index 0000000..02986d2 --- /dev/null +++ b/test/workspace/diagnostics/no-user-agent.txt @@ -0,0 +1,6 @@ +Allow: / +Disallow: / +Crawl-delay: 10 +Host: https://www.darianbenam.com + +Sitemap: https://www.darianbenam.com/sitemap.xml diff --git a/test/workspace/diagnostics/sitemap.txt b/test/workspace/diagnostics/sitemap.txt new file mode 100644 index 0000000..401c69d --- /dev/null +++ b/test/workspace/diagnostics/sitemap.txt @@ -0,0 +1,4 @@ +User-agent: * +Sitemap: +Sitemap: /sitemap.xml +Sitemap: https://www.darianbenam.com/sitemap.xml diff --git a/test/workspace/diagnostics/unknown-directive.txt b/test/workspace/diagnostics/unknown-directive.txt new file mode 100644 index 0000000..b04cb0e --- /dev/null +++ b/test/workspace/diagnostics/unknown-directive.txt @@ -0,0 +1,12 @@ +User-agent: * +Allow: / +Crawl-delay: 5 +Directive: +Directive: value + +User-agent: Googlebot +Host: https://www.darianbenam.com +Disallow: / +Invalid: abc + +Sitemap: https://www.darianbenam.com/sitemap.xml diff --git a/test/workspace/diagnostics/user-agent.txt b/test/workspace/diagnostics/user-agent.txt new file mode 100644 index 0000000..728b21f --- /dev/null +++ b/test/workspace/diagnostics/user-agent.txt @@ -0,0 +1,7 @@ +User-agent: * +Allow: / +Disallow: / +Crawl-delay: 10 +Host: https://www.darianbenam.com + +Sitemap: https://www.darianbenam.com/sitemap.xml