From bca5ff7a92ca9338d82f8b3ad1f5980b0d807634 Mon Sep 17 00:00:00 2001 From: Mark Wiemer <7833360+mark-wiemer@users.noreply.github.com> Date: Sun, 13 Oct 2024 15:48:50 -0700 Subject: [PATCH] Add exclude for v1 and v2 (#543) --- .github/workflows/publish.yml | 2 +- .vscode-test.mjs | 1 + .vscode/launch.json | 19 +- ahk2 | 2 +- changelog.md | 10 + e2e/.gitignore | 2 + e2e/excluded.ahk | 5 + e2e/main.ahk | 5 + e2e/out.ahk1 | 0 e2e/out/test.ahk | 2 + e2e/test.ah1 | 5 + e2e/test.ah2 | 2 + e2e/test.ahk1 | 5 + e2e/test.ahk2 | 2 + package-lock.json | 197 ++------ package.json | 16 +- package.nls.json | 4 +- src/common/global.ts | 13 +- src/common/out.ts | 15 +- src/extension.ts | 4 +- src/parser/parser.ts | 34 +- src/parser/parser.utils.test.ts | 106 +++++ src/parser/parser.utils.ts | 129 ++++++ src/providers/completionProvider.e2e.ts | 588 ++++++++++++------------ src/providers/defProvider.ts | 3 +- src/test/ahk2.e2e.ts | 51 -- src/test/config.e2e.ts | 106 +++++ src/test/utils.ts | 4 +- 28 files changed, 776 insertions(+), 556 deletions(-) create mode 100644 e2e/.gitignore create mode 100644 e2e/excluded.ahk create mode 100644 e2e/main.ahk create mode 100644 e2e/out.ahk1 create mode 100644 e2e/out/test.ahk create mode 100644 e2e/test.ah1 create mode 100644 e2e/test.ah2 create mode 100644 e2e/test.ahk1 create mode 100644 e2e/test.ahk2 create mode 100644 src/parser/parser.utils.test.ts create mode 100644 src/parser/parser.utils.ts delete mode 100644 src/test/ahk2.e2e.ts create mode 100644 src/test/config.e2e.ts diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 44354ddb..281a511a 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -13,7 +13,7 @@ jobs: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 - run: npm ci - - run: npm run package + - run: npm run package:ci - name: Publish to Open VSX Registry uses: HaaLeo/publish-vscode-extension@v1 with: diff --git a/.vscode-test.mjs b/.vscode-test.mjs index e9513a87..742eaedf 100644 --- a/.vscode-test.mjs +++ b/.vscode-test.mjs @@ -2,6 +2,7 @@ import { defineConfig } from '@vscode/test-cli'; export default defineConfig({ files: ['out/src/**/*.e2e.js'], + workspaceFolder: 'e2e', // https://mochajs.org/#command-line-usage // https://github.com/mochajs/mocha/tree/main/example/config mocha: { diff --git a/.vscode/launch.json b/.vscode/launch.json index a7cf4768..9c724509 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -9,12 +9,19 @@ "name": "Launch Extension", "type": "extensionHost", "request": "launch", - "args": ["--extensionDevelopmentPath=${workspaceRoot}"], - "preLaunchTask": "npm: build:dev", // :dev for sourcemaps - "env": { - "VSCODE_AHK_SERVER_PATH": "", // default path works for debug as well - "SYNTAXES_PATH": "" // default path works for debug as well - } + "args": ["--extensionDevelopmentPath=${workspaceFolder}"], + "preLaunchTask": "npm: build:dev" // :dev for sourcemaps + }, + { + "name": "Launch Tests", + "type": "extensionHost", + "request": "launch", + "preLaunchTask": "npm: pretest:e2e", + "args": [ + "--disable-extensions", + "--extensionDevelopmentPath=${workspaceFolder}" + ], + "testConfiguration": "${workspaceFolder}/.vscode-test.mjs" } ] } diff --git a/ahk2 b/ahk2 index d7dfd3bf..f75b53eb 160000 --- a/ahk2 +++ b/ahk2 @@ -1 +1 @@ -Subproject commit d7dfd3bf2eb3ceaf5deac6554957e6cf4f22f047 +Subproject commit f75b53eb8b7b14e8bfd4a6ad299dd19caa8526c1 diff --git a/changelog.md b/changelog.md index 67eb4aa6..2231efdf 100644 --- a/changelog.md +++ b/changelog.md @@ -1,5 +1,15 @@ # Changelog +## 6.3.0 - unreleased 🕳️ + +- Add exclude setting ([#488](https://github.com/mark-wiemer-org/ahkpp/issues/488)) + - Changed `v2.exclude` setting to `exclude` + - One setting works for both v1 and v2 + - v2 will exclude excluded files from suggestions even if they're opened in the IDE (different than thqby's extension) + - v1 no longer automatically ignores files with `out`, `target`, and `node_modules` in their name +- Fixup output channel names: "AHK++ (v1)" and "AHK++ (v2)" instead of "AHK" and "AHK++" respectively +- Fix duplicate output channels + ## 6.2.3 - 2024-10-08 📖 - Restore readme to marketplaces once again ([#537](https://github.com/mark-wiemer-org/ahkpp/issues/537)) diff --git a/e2e/.gitignore b/e2e/.gitignore new file mode 100644 index 00000000..1b1fcdc0 --- /dev/null +++ b/e2e/.gitignore @@ -0,0 +1,2 @@ +.vscode +!out \ No newline at end of file diff --git a/e2e/excluded.ahk b/e2e/excluded.ahk new file mode 100644 index 00000000..40b44ae3 --- /dev/null +++ b/e2e/excluded.ahk @@ -0,0 +1,5 @@ +#Requires AutoHotkey v2.0 + +MyExcludedFunc() { + MsgBox("This function is excluded in IDE settings") +} diff --git a/e2e/main.ahk b/e2e/main.ahk new file mode 100644 index 00000000..83791603 --- /dev/null +++ b/e2e/main.ahk @@ -0,0 +1,5 @@ +#Requires AutoHotkey v2.0 + +; Exclude pattern: excluded.ahk +;* Should not suggest "my excluded func" in completion +MyExclu \ No newline at end of file diff --git a/e2e/out.ahk1 b/e2e/out.ahk1 new file mode 100644 index 00000000..e69de29b diff --git a/e2e/out/test.ahk b/e2e/out/test.ahk new file mode 100644 index 00000000..5bea0b35 --- /dev/null +++ b/e2e/out/test.ahk @@ -0,0 +1,2 @@ +#Requires AutoHotkey v2.0 + diff --git a/e2e/test.ah1 b/e2e/test.ah1 new file mode 100644 index 00000000..773b0592 --- /dev/null +++ b/e2e/test.ah1 @@ -0,0 +1,5 @@ +#NoEnv +#SingleInstance, Force +SendMode, Input +SetBatchLines, -1 +SetWorkingDir, %A_ScriptDir% diff --git a/e2e/test.ah2 b/e2e/test.ah2 new file mode 100644 index 00000000..5bea0b35 --- /dev/null +++ b/e2e/test.ah2 @@ -0,0 +1,2 @@ +#Requires AutoHotkey v2.0 + diff --git a/e2e/test.ahk1 b/e2e/test.ahk1 new file mode 100644 index 00000000..773b0592 --- /dev/null +++ b/e2e/test.ahk1 @@ -0,0 +1,5 @@ +#NoEnv +#SingleInstance, Force +SendMode, Input +SetBatchLines, -1 +SetWorkingDir, %A_ScriptDir% diff --git a/e2e/test.ahk2 b/e2e/test.ahk2 new file mode 100644 index 00000000..5bea0b35 --- /dev/null +++ b/e2e/test.ahk2 @@ -0,0 +1,2 @@ +#Requires AutoHotkey v2.0 + diff --git a/package-lock.json b/package-lock.json index 11ce13fb..f5ff6d1b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -20,18 +20,17 @@ }, "devDependencies": { "@eslint/js": "^9.9.0", - "@types/chai": "^4.3.5", "@types/eslint__js": "^8.42.3", "@types/fs-extra": "^9.0.7", "@types/glob": "^7.1.3", "@types/mocha": "^10.0.8", "@types/node": "^20.16.0", + "@types/sinon": "^17.0.3", "@types/vscode": "1.93.0", "@types/xml2js": "^0.4.11", "@vscode/test-cli": "^0.0.10", "@vscode/test-electron": "^2.3.10", "@vscode/vsce": "^3.1.1", - "chai": "^4.3.7", "del-cli": "^6.0.0", "esbuild": "0.24.0", "eslint": "^9.9.0", @@ -40,7 +39,7 @@ "js-yaml": "^4.1.0", "mocha": "^10.0.0", "prettier": "3.3.3", - "sinon": "^18.0.0", + "sinon": "^18.0.1", "sort-package-json": "^1.57.0", "source-map-support": "^0.5.19", "typescript": "5.5.4", @@ -1100,12 +1099,6 @@ "node": ">= 6" } }, - "node_modules/@types/chai": { - "version": "4.3.5", - "resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.3.5.tgz", - "integrity": "sha512-mEo1sAde+UCE6b2hxn332f1g1E8WfYRu6p5SvTKr2ZKC1f7gFJXk4h5PyGP9Dt6gCaG8y8XhwnXWC6Iy2cmBng==", - "dev": true - }, "node_modules/@types/eslint": { "version": "9.6.0", "resolved": "https://registry.npmmirror.com/@types/eslint/-/eslint-9.6.0.tgz", @@ -1190,6 +1183,23 @@ "undici-types": "~6.19.2" } }, + "node_modules/@types/sinon": { + "version": "17.0.3", + "resolved": "https://registry.npmmirror.com/@types/sinon/-/sinon-17.0.3.tgz", + "integrity": "sha512-j3uovdn8ewky9kRBG19bOwaZbexJu/XjtkHyjvUgt4xfPFz18dcORIMqnYh66Fx3Powhcr85NT5+er3+oViapw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/sinonjs__fake-timers": "*" + } + }, + "node_modules/@types/sinonjs__fake-timers": { + "version": "8.1.5", + "resolved": "https://registry.npmmirror.com/@types/sinonjs__fake-timers/-/sinonjs__fake-timers-8.1.5.tgz", + "integrity": "sha512-mQkU2jY8jJEF7YHjHvsQO8+3ughTL1mcnn96igfhONmR+fUPSKIkefQYpSe8bsly2Ep7oQbn/6VG5/9/0qcArQ==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/vscode": { "version": "1.93.0", "resolved": "https://registry.npmmirror.com/@types/vscode/-/vscode-1.93.0.tgz", @@ -2086,15 +2096,6 @@ "node": ">=8" } }, - "node_modules/assertion-error": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", - "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==", - "dev": true, - "engines": { - "node": "*" - } - }, "node_modules/async": { "version": "2.6.4", "resolved": "https://registry.npmjs.org/async/-/async-2.6.4.tgz", @@ -2385,24 +2386,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/chai": { - "version": "4.3.7", - "resolved": "https://registry.npmjs.org/chai/-/chai-4.3.7.tgz", - "integrity": "sha512-HLnAzZ2iupm25PlN0xFreAlBA5zaBSv3og0DdeGA4Ar6h6rJ3A0rolRUKJhSF2V10GZKDgWF/VmAEsNWjCRB+A==", - "dev": true, - "dependencies": { - "assertion-error": "^1.1.0", - "check-error": "^1.0.2", - "deep-eql": "^4.1.2", - "get-func-name": "^2.0.0", - "loupe": "^2.3.1", - "pathval": "^1.1.1", - "type-detect": "^4.0.5" - }, - "engines": { - "node": ">=4" - } - }, "node_modules/chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", @@ -2431,15 +2414,6 @@ "node": ">=8" } }, - "node_modules/check-error": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.2.tgz", - "integrity": "sha512-BrgHpW9NURQgzoNyjfq0Wu6VFO6D7IZEmJNdtgNqpzGG8RuNFHt2jQxWlAs4HMe119chBnv+34syEZtc6IhLtA==", - "dev": true, - "engines": { - "node": "*" - } - }, "node_modules/cheerio": { "version": "1.0.0-rc.12", "resolved": "https://registry.npmjs.org/cheerio/-/cheerio-1.0.0-rc.12.tgz", @@ -2680,18 +2654,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/deep-eql": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-4.1.3.tgz", - "integrity": "sha512-WaEtAOpRA1MQ0eohqZjpGD8zdI0Ovsm8mmFhaDN8dvDZzyoUMcYDnf5Y6iu7HTXxf8JDS23qWa4a+hKCDyOPzw==", - "dev": true, - "dependencies": { - "type-detect": "^4.0.0" - }, - "engines": { - "node": ">=6" - } - }, "node_modules/deep-extend": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", @@ -3540,15 +3502,6 @@ "node": "6.* || 8.* || >= 10.*" } }, - "node_modules/get-func-name": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.2.tgz", - "integrity": "sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ==", - "dev": true, - "engines": { - "node": "*" - } - }, "node_modules/get-intrinsic": { "version": "1.2.4", "resolved": "https://registry.npmmirror.com/get-intrinsic/-/get-intrinsic-1.2.4.tgz", @@ -4413,15 +4366,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/loupe": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/loupe/-/loupe-2.3.6.tgz", - "integrity": "sha512-RaPMZKiMy8/JruncMU5Bt6na1eftNoo++R4Y+N2FrxkDVTrGvcyzFTsaGif4QTeKESheMGegbhw6iUAq+5A8zA==", - "dev": true, - "dependencies": { - "get-func-name": "^2.0.0" - } - }, "node_modules/lru-cache": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", @@ -5018,15 +4962,6 @@ "node": ">=8" } }, - "node_modules/pathval": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.1.tgz", - "integrity": "sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==", - "dev": true, - "engines": { - "node": "*" - } - }, "node_modules/pend": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", @@ -5464,14 +5399,14 @@ } }, "node_modules/sinon": { - "version": "18.0.0", - "resolved": "https://registry.npmmirror.com/sinon/-/sinon-18.0.0.tgz", - "integrity": "sha512-+dXDXzD1sBO6HlmZDd7mXZCR/y5ECiEiGCBSGuFD/kZ0bDTofPYc6JaeGmPSF+1j1MejGUWkORbYOLDyvqCWpA==", + "version": "18.0.1", + "resolved": "https://registry.npmmirror.com/sinon/-/sinon-18.0.1.tgz", + "integrity": "sha512-a2N2TDY1uGviajJ6r4D1CyRAkzE9NNVlYOV1wX5xQDuAk0ONgzgRl0EjCQuRCPxOwp13ghsMwt9Gdldujs39qw==", "dev": true, "license": "BSD-3-Clause", "dependencies": { "@sinonjs/commons": "^3.0.1", - "@sinonjs/fake-timers": "^11.2.2", + "@sinonjs/fake-timers": "11.2.2", "@sinonjs/samsam": "^8.0.0", "diff": "^5.2.0", "nise": "^6.0.0", @@ -6955,12 +6890,6 @@ "integrity": "sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw==", "dev": true }, - "@types/chai": { - "version": "4.3.5", - "resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.3.5.tgz", - "integrity": "sha512-mEo1sAde+UCE6b2hxn332f1g1E8WfYRu6p5SvTKr2ZKC1f7gFJXk4h5PyGP9Dt6gCaG8y8XhwnXWC6Iy2cmBng==", - "dev": true - }, "@types/eslint": { "version": "9.6.0", "resolved": "https://registry.npmmirror.com/@types/eslint/-/eslint-9.6.0.tgz", @@ -7038,6 +6967,21 @@ "undici-types": "~6.19.2" } }, + "@types/sinon": { + "version": "17.0.3", + "resolved": "https://registry.npmmirror.com/@types/sinon/-/sinon-17.0.3.tgz", + "integrity": "sha512-j3uovdn8ewky9kRBG19bOwaZbexJu/XjtkHyjvUgt4xfPFz18dcORIMqnYh66Fx3Powhcr85NT5+er3+oViapw==", + "dev": true, + "requires": { + "@types/sinonjs__fake-timers": "*" + } + }, + "@types/sinonjs__fake-timers": { + "version": "8.1.5", + "resolved": "https://registry.npmmirror.com/@types/sinonjs__fake-timers/-/sinonjs__fake-timers-8.1.5.tgz", + "integrity": "sha512-mQkU2jY8jJEF7YHjHvsQO8+3ughTL1mcnn96igfhONmR+fUPSKIkefQYpSe8bsly2Ep7oQbn/6VG5/9/0qcArQ==", + "dev": true + }, "@types/vscode": { "version": "1.93.0", "resolved": "https://registry.npmmirror.com/@types/vscode/-/vscode-1.93.0.tgz", @@ -7621,12 +7565,6 @@ "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", "dev": true }, - "assertion-error": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", - "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==", - "dev": true - }, "async": { "version": "2.6.4", "resolved": "https://registry.npmjs.org/async/-/async-2.6.4.tgz", @@ -7839,21 +7777,6 @@ "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", "dev": true }, - "chai": { - "version": "4.3.7", - "resolved": "https://registry.npmjs.org/chai/-/chai-4.3.7.tgz", - "integrity": "sha512-HLnAzZ2iupm25PlN0xFreAlBA5zaBSv3og0DdeGA4Ar6h6rJ3A0rolRUKJhSF2V10GZKDgWF/VmAEsNWjCRB+A==", - "dev": true, - "requires": { - "assertion-error": "^1.1.0", - "check-error": "^1.0.2", - "deep-eql": "^4.1.2", - "get-func-name": "^2.0.0", - "loupe": "^2.3.1", - "pathval": "^1.1.1", - "type-detect": "^4.0.5" - } - }, "chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", @@ -7875,12 +7798,6 @@ } } }, - "check-error": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.2.tgz", - "integrity": "sha512-BrgHpW9NURQgzoNyjfq0Wu6VFO6D7IZEmJNdtgNqpzGG8RuNFHt2jQxWlAs4HMe119chBnv+34syEZtc6IhLtA==", - "dev": true - }, "cheerio": { "version": "1.0.0-rc.12", "resolved": "https://registry.npmjs.org/cheerio/-/cheerio-1.0.0-rc.12.tgz", @@ -8053,15 +7970,6 @@ "mimic-response": "^3.1.0" } }, - "deep-eql": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-4.1.3.tgz", - "integrity": "sha512-WaEtAOpRA1MQ0eohqZjpGD8zdI0Ovsm8mmFhaDN8dvDZzyoUMcYDnf5Y6iu7HTXxf8JDS23qWa4a+hKCDyOPzw==", - "dev": true, - "requires": { - "type-detect": "^4.0.0" - } - }, "deep-extend": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", @@ -8642,12 +8550,6 @@ "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", "dev": true }, - "get-func-name": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.2.tgz", - "integrity": "sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ==", - "dev": true - }, "get-intrinsic": { "version": "1.2.4", "resolved": "https://registry.npmmirror.com/get-intrinsic/-/get-intrinsic-1.2.4.tgz", @@ -9283,15 +9185,6 @@ "is-unicode-supported": "^0.1.0" } }, - "loupe": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/loupe/-/loupe-2.3.6.tgz", - "integrity": "sha512-RaPMZKiMy8/JruncMU5Bt6na1eftNoo++R4Y+N2FrxkDVTrGvcyzFTsaGif4QTeKESheMGegbhw6iUAq+5A8zA==", - "dev": true, - "requires": { - "get-func-name": "^2.0.0" - } - }, "lru-cache": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", @@ -9726,12 +9619,6 @@ "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", "dev": true }, - "pathval": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.1.tgz", - "integrity": "sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==", - "dev": true - }, "pend": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", @@ -10024,13 +9911,13 @@ } }, "sinon": { - "version": "18.0.0", - "resolved": "https://registry.npmmirror.com/sinon/-/sinon-18.0.0.tgz", - "integrity": "sha512-+dXDXzD1sBO6HlmZDd7mXZCR/y5ECiEiGCBSGuFD/kZ0bDTofPYc6JaeGmPSF+1j1MejGUWkORbYOLDyvqCWpA==", + "version": "18.0.1", + "resolved": "https://registry.npmmirror.com/sinon/-/sinon-18.0.1.tgz", + "integrity": "sha512-a2N2TDY1uGviajJ6r4D1CyRAkzE9NNVlYOV1wX5xQDuAk0ONgzgRl0EjCQuRCPxOwp13ghsMwt9Gdldujs39qw==", "dev": true, "requires": { "@sinonjs/commons": "^3.0.1", - "@sinonjs/fake-timers": "^11.2.2", + "@sinonjs/fake-timers": "11.2.2", "@sinonjs/samsam": "^8.0.0", "diff": "^5.2.0", "nise": "^6.0.0", diff --git a/package.json b/package.json index 70fad692..548f7820 100644 --- a/package.json +++ b/package.json @@ -51,7 +51,7 @@ "build": "node src/build.mjs --mode=production && cd ahk2 && npm run build", "build:ahk1": "node src/build.mjs --mode=production", "build:dev": "node src/build.mjs && cd ahk2 && npm run build", - "clean:all": "npm run clean:dist && npm run clean:language && npm run clean:out", + "clean": "npm run clean:dist && npm run clean:language && npm run clean:out", "clean:dist": "del-cli dist", "clean:language": "del-cli \"language/*.tmLanguage.json\"", "clean:out": "del-cli out", @@ -65,13 +65,14 @@ "format:fix": "prettier --write .", "lint": "npm run format && npm run eslint && npm run sort-package-json", "lint:fix": "npm run format:fix && npm run eslint:fix && npm run sort-package-json:fix", - "package": "vsce package -o ahkpp.vsix --allow-unused-files-pattern", + "package": "vsce package --allow-unused-files-pattern", + "package:ci": "npm run package -- -o ahkpp.vsix", "prepare": "git submodule update --init --recursive && cd ahk2 && npm install", "sort-package-json": "sort-package-json --check", "sort-package-json:fix": "sort-package-json", "test": "npm run test:grammar && npm run test:unit && npm run test:e2e", "test:ci": "npm run test:grammar && npm run test:e2e:ci", - "pretest:e2e": "npm run compile:ts", + "pretest:e2e": "npm run vscode:prepublish && npm run compile:ts", "test:e2e": "vscode-test", "test:e2e:ci": "npm run test:e2e -- -i --fgrep @ignoreCI", "pretest:grammar": "npm run compile:grammar", @@ -455,7 +456,7 @@ "type": "string" } }, - "AHK++.v2.exclude": { + "AHK++.exclude": { "scope": "window", "type": "array", "default": [], @@ -463,7 +464,7 @@ "type": "string" }, "uniqueItems": true, - "markdownDescription": "%ahk++.config.v2.file.exclude%" + "markdownDescription": "%ahk++.config.exclude%" }, "AHK++.v2.file": { "type": "object", @@ -1156,18 +1157,17 @@ }, "devDependencies": { "@eslint/js": "^9.9.0", - "@types/chai": "^4.3.5", "@types/eslint__js": "^8.42.3", "@types/fs-extra": "^9.0.7", "@types/glob": "^7.1.3", "@types/mocha": "^10.0.8", "@types/node": "^20.16.0", + "@types/sinon": "^17.0.3", "@types/vscode": "1.93.0", "@types/xml2js": "^0.4.11", "@vscode/test-cli": "^0.0.10", "@vscode/test-electron": "^2.3.10", "@vscode/vsce": "^3.1.1", - "chai": "^4.3.7", "del-cli": "^6.0.0", "esbuild": "0.24.0", "eslint": "^9.9.0", @@ -1176,7 +1176,7 @@ "js-yaml": "^4.1.0", "mocha": "^10.0.0", "prettier": "3.3.3", - "sinon": "^18.0.0", + "sinon": "^18.0.1", "sort-package-json": "^1.57.0", "source-map-support": "^0.5.19", "typescript": "5.5.4", diff --git a/package.nls.json b/package.nls.json index 1cf4dfaf..5d05a0c1 100644 --- a/package.nls.json +++ b/package.nls.json @@ -17,12 +17,12 @@ "ahk++.config.v2.actionWhenV1Detected": "Action when v1 script is detected", "ahk++.config.v2.commentTagRegex": "The regular expression for custom symbols to appear in the breadcrumb and elsewhere. Default matches any line that starts with `;;`. Changes take effect after restart.", "ahk++.config.v2.completionCommitCharacters": "Characters which commit auto-completion", - "ahk++.config.v2.completeFunctionCalls": "Whether to automatically add parenetheses when calling a function", + "ahk++.config.v2.completeFunctionCalls": "Whether to automatically add parentheses when calling a function", "ahk++.config.v2.debugConfiguration": "The [launch configuration](https://code.visualstudio.com/docs/editor/debugging#_launch-configurations) to use when debugging", "ahk++.config.v2.diagnostics.classNonDynamicMemberCheck": "Check whether non-dynamic members of a class exist", "ahk++.config.v2.diagnostics.paramsCheck": "Check that the function call has the correct number of arguments", "ahk++.config.v2.file.interpreterPath": "Path to the `AutoHotkey.exe` executable file for AHK v2.", - "ahk++.config.v2.file.exclude": "⚠️ Not yet supported, ref [issue #488](https://github.com/mark-wiemer-org/ahkpp/issues/488).\n\nGlob patterns for excluding files and folders when scanning AHK files.", + "ahk++.config.exclude": "[Glob patterns]() for excluding files and folders. Applies even when files are opened. Changes take effect after restart.", "ahk++.config.v2.file.maxScanDepth": "Depth of folders to scan for IntelliSense. Negative values mean infinite depth.", "ahk++.config.v2.librarySuggestions": "Which libraries to suggest functions from, if any. In case of issues, restart your IDE.", "ahk++.config.v2.symbolFoldingFromOpenBrace": "Fold parameter lists separately from definitions.", diff --git a/src/common/global.ts b/src/common/global.ts index 4e50636f..061a7f6b 100644 --- a/src/common/global.ts +++ b/src/common/global.ts @@ -1,13 +1,18 @@ import * as vscode from 'vscode'; +export const configPrefix = 'AHK++'; + export class Global { private static statusBarItem: vscode.StatusBarItem; /** Gets config value from VS Code */ + // todo move out of class public static getConfig(key: ConfigKey): T | undefined { return ( + // older versions of AHK++ used `ahk++` lowercase + // todo add deprecation warning for lowercase config vars vscode.workspace.getConfiguration('ahk++').get(key) ?? - vscode.workspace.getConfiguration('AHK++').get(key) + vscode.workspace.getConfiguration(configPrefix).get(key) ); } @@ -29,19 +34,19 @@ export class Global { /** Defined in package.json */ // Ref "Settings UI is overwhelming for extensions with lots of settings" // https://github.com/microsoft/vscode/issues/70589 -// todo de-dupe with ahk2/**/config.ts export enum ConfigKey { allowedNumberOfEmptyLines = 'v1.formatter.allowedNumberOfEmptyLines', compileBaseFileV1 = 'v1.file.compileBaseFile', compileBaseFileV2 = 'v2.file.compileBaseFile', compileIcon = 'compiler.compileIcon', compilerPath = 'compiler.compilerPath', - interpreterPathV1 = 'v1.file.interpreterPath', - interpreterPathV2 = 'v2.file.interpreterPath', + exclude = 'exclude', helpPathV1 = 'v1.file.helpPath', helpPathV2 = 'v2.file.helpPath', indentCodeAfterIfDirective = 'v1.formatter.indentCodeAfterIfDirective', indentCodeAfterLabel = 'v1.formatter.indentCodeAfterLabel', + interpreterPathV1 = 'v1.file.interpreterPath', + interpreterPathV2 = 'v2.file.interpreterPath', maximumParseLength = 'v1.intellisense.maximumParseLength', preserveIndent = 'v1.formatter.preserveIndent', showOutput = 'general.showOutput', diff --git a/src/common/out.ts b/src/common/out.ts index de061766..56d185ea 100644 --- a/src/common/out.ts +++ b/src/common/out.ts @@ -1,12 +1,11 @@ import * as vscode from 'vscode'; -/** Logs messages to VS output channel */ +/** Logs messages to IDE output channel */ export class Out { - private static outputChannel: vscode.OutputChannel = - vscode.window.createOutputChannel('AHK'); + private static outputChannel: vscode.OutputChannel; - public static debug(value: unknown) { - this.log(value, false); + public static debug(value: Error | string) { + Out.log(value, false); } /** @@ -14,11 +13,15 @@ export class Out { * Prepends all logs with `new Date().toISOString()`. * @param value The value to log */ - public static log(value: unknown, focus = true) { + public static log(value: Error | string, focus = true) { if (value instanceof Error) { console.trace(value); value = value.message; } + if (!this.outputChannel) { + this.outputChannel = + vscode.window.createOutputChannel('AHK++ (v1)'); + } if (focus) { this.outputChannel.show(focus); } diff --git a/src/extension.ts b/src/extension.ts index 7eb75668..2bc2e9b3 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -21,7 +21,9 @@ import { activate as activateV2 } from '../ahk2/client/src/extension'; export function activate(context: vscode.ExtensionContext) { (async () => { Global.updateStatusBarItems('Indexing AutoHotkey workspace...'); - await Parser.buildByPath(vscode.workspace.rootPath); + await Parser.buildByPath( + vscode.workspace.workspaceFolders?.[0].uri.fsPath, + ); Global.updateStatusBarItems('Indexed AutoHotkey workspace :)'); Global.hide(); })(); diff --git a/src/parser/parser.ts b/src/parser/parser.ts index 00462489..6c75255a 100644 --- a/src/parser/parser.ts +++ b/src/parser/parser.ts @@ -1,9 +1,9 @@ import { ConfigKey, Global } from '../common/global'; -import * as fs from 'fs'; import * as vscode from 'vscode'; import { CodeUtil } from '../common/codeUtil'; -import { Out } from '../common/out'; import { Script, Method, Ref, Label, Block, Variable } from './model'; +import { pathsToBuild } from './parser.utils'; +import { Out } from '../common/out'; export interface BuildScriptOptions { /** Defaults to false. If true, short-circuits when document is in cache. */ @@ -12,6 +12,7 @@ export interface BuildScriptOptions { maximumParseLength?: number; } +/** Parses v1 files */ export class Parser { private static documentCache = new Map(); @@ -20,25 +21,18 @@ export class Parser { * @param buildPath */ public static async buildByPath(buildPath: string) { - if (!buildPath) { - return; - } - if (fs.statSync(buildPath).isDirectory()) { - fs.readdir(buildPath, (err, files) => { - if (err) { - Out.log(err); - return; - } - for (const file of files) { - if (file.match(/(^\.|out|target|node_modules)/)) { - continue; - } - this.buildByPath(buildPath + '/' + file); - } - }); - } else if (buildPath.match(/\b(ahk|ext)$/i)) { + const excludeConfig = Global.getConfig(ConfigKey.exclude); + const paths = await pathsToBuild( + buildPath, + [], + excludeConfig, + Out.debug, + ); + Out.log(`Building ${paths.length} files`); + for (const path of paths) { + Out.log(`Building ${path}`); const document = await vscode.workspace.openTextDocument( - vscode.Uri.file(buildPath), + vscode.Uri.file(path), ); this.buildScript(document); } diff --git a/src/parser/parser.utils.test.ts b/src/parser/parser.utils.test.ts new file mode 100644 index 00000000..80c3d6f9 --- /dev/null +++ b/src/parser/parser.utils.test.ts @@ -0,0 +1,106 @@ +import { suite, test } from 'mocha'; +import * as assert from 'assert'; +import { pathsToBuild } from './parser.utils'; +import sinon from 'sinon'; +import { Dir, Dirent, promises } from 'fs'; +import path from 'path'; + +const rootPath = path.join(__dirname, '..', '..', '..'); +const mockDirName = 'e2e'; +const mockPath = path.join(rootPath, mockDirName); + +/** + * Keys corresponding to arrays are seen as files. + * Keys corresponding to objects are seen as directories. + */ +const mockDirStructure = { + [mockDirName]: { + 'main.ahk': [], + 'main.ah1': [], + 'main.ext': [], + 'main.txt': [], + sub: { + 'sub.ahk': [], + 'sub.ah1': [], + 'sub.ext': [], + 'sub.txt': [], + }, + }, +}; + +const createMockDir = (structure: Record): Dir => + ({ + async *[Symbol.asyncIterator]() { + for (const key of Object.keys(structure)) { + yield createMockDirent(key, structure[key]); + } + }, + }) as Dir; + +const createMockDirent = (name: string, value: object | string[]): Dirent => + ({ + name, + isDirectory: () => typeof value === 'object' && !Array.isArray(value), + isFile: () => Array.isArray(value), + }) as Dirent; + +sinon + .stub(promises, 'opendir') + .callsFake(async (path: string): Promise => { + const relativePath = path.replace(rootPath, '').replace(/\\/g, '/'); + const parts = relativePath.split('/').filter(Boolean); + let currentDir = mockDirStructure; + + for (const part of parts) { + if (typeof currentDir === 'object' && part in currentDir) { + currentDir = currentDir[part]; + } else { + throw new Error( + `Path ${path} not found in mock directory structure`, + ); + } + } + + return createMockDir(currentDir as Record); + }); + +suite('pathsToBuild', () => { + after(() => { + sinon.restore(); + }); + + const tests: [ + name: string, + exclusions: string[], + expected: Awaited>, + ][] = [ + [ + 'no exclusions', + [], + [ + 'main.ahk', + 'main.ah1', + 'main.ext', + 'sub/sub.ahk', + 'sub/sub.ah1', + 'sub/sub.ext', + ], + ], + [ + 'exclude .ext', + ['*.ext'], + ['main.ahk', 'main.ah1', 'sub/sub.ahk', 'sub/sub.ah1'], + ], + ['exclude sub', ['sub/*'], ['main.ahk', 'main.ah1', 'main.ext']], + ['exclude all', ['*'], []], + ]; + tests.forEach(([name, exclusions, relativeExpected]) => + test(name, async () => { + const result = await pathsToBuild(mockPath, [], exclusions); + const absoluteExpected = relativeExpected.map((e) => + path.join(mockPath, e), + ); + assert.deepStrictEqual(result, absoluteExpected); + }), + ); +}); diff --git a/src/parser/parser.utils.ts b/src/parser/parser.utils.ts new file mode 100644 index 00000000..26e6155a --- /dev/null +++ b/src/parser/parser.utils.ts @@ -0,0 +1,129 @@ +import { promises } from 'fs'; +import { resolve } from 'path'; + +interface Exclude { + file: RegExp[]; + folder: RegExp[]; +} + +export async function pathsToBuild( + rootPath: string, + paths: string[] = [], + excludeConfig: string[], + log?: (val: string) => void, +): Promise { + if (!rootPath) { + return []; + } + const exclude = parseExcludeConfig(excludeConfig); + log?.(`folder: ${exclude.folder.map((re) => re.toString()).join('\n')}`); + log?.(`file: ${exclude.file.map((re) => re.toString()).join('\n')}`); + + const pathsToBuildInner = async (rootPath) => { + const dir = await promises.opendir(rootPath); + for await (const dirent of dir) { + const path = resolve(rootPath, dirent.name); + log?.('Checking ' + path); + if ( + dirent.isDirectory() && + !exclude.folder.some((re) => re.test(path)) + ) { + await pathsToBuildInner(path); + } else if ( + dirent.isFile() && + dirent.name.match(/\.(ahk|ah1|ahk1|ext)$/i) && + !exclude.file.some((re) => re.test(path)) + ) { + log?.('Adding ' + path); + paths.push(path); + } else { + log?.('Ignoring ' + path); + } + } + return paths; + }; + + return await pathsToBuildInner(rootPath); +} + +function parseExcludeConfig(exclude: string[] = []): Exclude { + const fileExclude: RegExp[] = []; + const folderExclude: RegExp[] = []; + for (const s of exclude) + try { + (/[\\/]$/.test(s) ? folderExclude : fileExclude).push( + glob2regexp(s), + ); + // eslint-disable-next-line @typescript-eslint/no-unused-vars + } catch (e) { + console.log(`[Error] Invalid glob pattern: ${s}`); + } + return { file: fileExclude, folder: folderExclude }; +} + +// Copied from AHK2 +// todo add tests or replace with known library +function glob2regexp(glob: string) { + let reStr = '', + inGroup = false, + isNot: boolean, + c: string; + if ((isNot = glob.startsWith('!'))) glob = glob.slice(1); + for (let i = 0, j, len = glob.length; i < len; i++) { + switch ((c = glob[i])) { + case '/': + case '\\': + reStr += '[\\x5c/]'; + break; + case '$': + case '^': + case '+': + case '.': + case '(': + case ')': + case '=': + case '|': + reStr += '\\' + c; + break; + case '?': + reStr += '.'; + break; + case '!': + if (!i) isNot = true; + else if (reStr.endsWith('[')) reStr += '^'; + else reStr += '\\' + c; + break; + case '{': + inGroup = true; + reStr += '('; + break; + case '}': + inGroup = false; + reStr += ')'; + break; + case ',': + reStr += inGroup ? '|' : ','; + break; + case '*': + j = i; + while (glob[i + 1] === '*') i++; + if ( + i > j && + /^[\x5c/]?\*+[\x5c/]?$/.test(glob.substring(j - 1, i + 2)) + ) { + reStr += '((?:[^\\x5c/]*(?:[\\x5c/]|$))*)'; + i++; + } else { + reStr += '([^\\x5c/]*)'; + } + break; + default: + reStr += c; + } + } + if (/^([a-zA-Z]:|\*\*)/.test(glob)) reStr = '^' + reStr; + else if (!/[\\/]/.test(glob[0])) reStr = '[\\x5c/]' + reStr; + if (!/[\\/]$/.test(glob)) reStr += '$'; + if (isNot) reStr = reStr.startsWith('^') ? `^(?!${reStr})` : `(?!${reStr})`; + return new RegExp(reStr, 'i'); +} diff --git a/src/providers/completionProvider.e2e.ts b/src/providers/completionProvider.e2e.ts index 1b628d6e..825626d0 100644 --- a/src/providers/completionProvider.e2e.ts +++ b/src/providers/completionProvider.e2e.ts @@ -1,358 +1,352 @@ import * as vscode from 'vscode'; -import { assert } from 'chai'; +import * as assert from 'assert'; import { provideCompletionItemsInner } from './completionProvider'; -// tests for completionItemsForMethod -suite('completionProvider', () => { - // TODO outer - // parsing vs no parsing - // intellisense enabled vs disabled +// TODO outer +// parsing vs no parsing +// intellisense enabled vs disabled - suite('provideCompletionItemsInner', () => { - const tests: [ - name: string, - args: Parameters, - expected: ReturnType, - ][] = [ - ['no methods or variables', [[], 'mockUri', 1, []], []], +suite('provideCompletionItemsInner', () => { + const tests: [ + name: string, + args: Parameters, + expected: ReturnType, + ][] = [ + ['no methods or variables', [[], 'mockUri', 1, []], []], + [ + 'diff file, outside method, no locals', [ - 'diff file, outside method, no locals', - [ - [ - { - comment: 'mockComment', - endLine: 0, - full: 'mockName()', - line: 0, - name: 'mockName', - params: [], - uriString: 'mockUri1', - variables: [], - }, - ], - 'mockUri2', - 1, - [], - ], [ { - detail: 'mockComment', - insertText: 'mockName()', - kind: vscode.CompletionItemKind.Method, - label: 'mockName', + comment: 'mockComment', + endLine: 0, + full: 'mockName()', + line: 0, + name: 'mockName', + params: [], + uriString: 'mockUri1', + variables: [], }, ], + 'mockUri2', + 1, + [], ], [ - 'diff file, outside method, only params', - [ - [ - { - comment: 'mockComment', - endLine: 0, - full: 'mockName(mockParam1, mockParam2)', - line: 0, - name: 'mockName', - params: ['mockParam1', 'mockParam2'], - uriString: 'mockUri1', - variables: [], - }, - ], - 'mockUri2', - 1, - [], - ], - [ - { - detail: 'mockComment', - insertText: new vscode.SnippetString('mockName($1)'), - kind: vscode.CompletionItemKind.Method, - label: 'mockName(mockParam1, mockParam2)', - }, - ], + { + detail: 'mockComment', + insertText: 'mockName()', + kind: vscode.CompletionItemKind.Method, + label: 'mockName', + }, ], + ], + [ + 'diff file, outside method, only params', [ - 'diff file, inside method, ignore local variables', - [ - [ - { - comment: 'mockComment', - endLine: 2, - full: 'mockName(mockParam1, mockParam2)', - line: 0, - name: 'mockName', - params: ['mockParam1', 'mockParam2'], - uriString: 'mockUri1', - variables: ['mockVariable1'], - }, - ], - 'mockUri2', - 1, - [], - ], [ { - detail: 'mockComment', - insertText: new vscode.SnippetString('mockName($1)'), - kind: vscode.CompletionItemKind.Method, - label: 'mockName(mockParam1, mockParam2)', + comment: 'mockComment', + endLine: 0, + full: 'mockName(mockParam1, mockParam2)', + line: 0, + name: 'mockName', + params: ['mockParam1', 'mockParam2'], + uriString: 'mockUri1', + variables: [], }, ], + 'mockUri2', + 1, + [], ], [ - 'same file, outside method, no locals', - [ - [ - { - comment: 'mockComment', - endLine: 0, - full: 'mockName()', - line: 0, - name: 'mockName', - params: [], - uriString: 'mockUri1', - variables: [], - }, - ], - 'mockUri1', - 1, - [], - ], + { + detail: 'mockComment', + insertText: new vscode.SnippetString('mockName($1)'), + kind: vscode.CompletionItemKind.Method, + label: 'mockName(mockParam1, mockParam2)', + }, + ], + ], + [ + 'diff file, inside method, ignore local variables', + [ [ { - detail: 'mockComment', - insertText: 'mockName()', - kind: vscode.CompletionItemKind.Method, - label: 'mockName', + comment: 'mockComment', + endLine: 2, + full: 'mockName(mockParam1, mockParam2)', + line: 0, + name: 'mockName', + params: ['mockParam1', 'mockParam2'], + uriString: 'mockUri1', + variables: ['mockVariable1'], }, ], + 'mockUri2', + 1, + [], ], [ - 'same file, outside method, ignore local variables', - [ - [ - { - comment: 'mockComment', - endLine: 0, - full: 'mockName(mockParam1, mockParam2)', - line: 0, - name: 'mockName', - params: ['mockParam1', 'mockParam2'], - uriString: 'mockUri1', - variables: ['mockVariable1'], - }, - ], - 'mockUri1', - 1, - [], - ], + { + detail: 'mockComment', + insertText: new vscode.SnippetString('mockName($1)'), + kind: vscode.CompletionItemKind.Method, + label: 'mockName(mockParam1, mockParam2)', + }, + ], + ], + [ + 'same file, outside method, no locals', + [ [ { - detail: 'mockComment', - insertText: new vscode.SnippetString('mockName($1)'), - kind: vscode.CompletionItemKind.Method, - label: 'mockName(mockParam1, mockParam2)', + comment: 'mockComment', + endLine: 0, + full: 'mockName()', + line: 0, + name: 'mockName', + params: [], + uriString: 'mockUri1', + variables: [], }, ], + 'mockUri1', + 1, + [], ], [ - 'same file, inside method, include locals (params first)', - [ - [ - { - comment: 'mockComment', - endLine: 2, - full: 'mockName(mockParam1, mockParam2)', - line: 0, - name: 'mockName', - params: ['mockParam1', 'mockParam2'], - uriString: 'mockUri1', - variables: ['mockVariable1'], - }, - ], - 'mockUri1', - 1, - [], - ], + { + detail: 'mockComment', + insertText: 'mockName()', + kind: vscode.CompletionItemKind.Method, + label: 'mockName', + }, + ], + ], + [ + 'same file, outside method, ignore local variables', + [ [ { - detail: 'mockComment', - insertText: new vscode.SnippetString('mockName($1)'), - kind: vscode.CompletionItemKind.Method, - label: 'mockName(mockParam1, mockParam2)', - }, - { - kind: vscode.CompletionItemKind.Variable, - label: 'mockParam1', - }, - { - kind: vscode.CompletionItemKind.Variable, - label: 'mockParam2', - }, - { - kind: vscode.CompletionItemKind.Variable, - label: 'mockVariable1', + comment: 'mockComment', + endLine: 0, + full: 'mockName(mockParam1, mockParam2)', + line: 0, + name: 'mockName', + params: ['mockParam1', 'mockParam2'], + uriString: 'mockUri1', + variables: ['mockVariable1'], }, ], + 'mockUri1', + 1, + [], ], [ - 'same file, inside one of two methods, include locals of only the current method', - [ - [ - { - comment: 'mockComment1', - endLine: 2, - full: 'mockName1(mockParam1_1, mockParam1_2)', - line: 0, - name: 'mockName1', - params: ['mockParam1_1', 'mockParam1_2'], - uriString: 'mockUri', - variables: ['mockVariable1_1'], - }, - { - comment: 'mockComment2', - endLine: 4, - full: 'mockName2(mockParam2_1, mockParam2_2)', - line: 3, - name: 'mockName2', - params: ['mockParam2_1', 'mockParam2_2'], - uriString: 'mockUri', - variables: ['mockVariable2_1'], - }, - ], - 'mockUri', - 1, - [], - ], + { + detail: 'mockComment', + insertText: new vscode.SnippetString('mockName($1)'), + kind: vscode.CompletionItemKind.Method, + label: 'mockName(mockParam1, mockParam2)', + }, + ], + ], + [ + 'same file, inside method, include locals (params first)', + [ [ { - detail: 'mockComment1', - insertText: new vscode.SnippetString('mockName1($1)'), - kind: vscode.CompletionItemKind.Method, - label: 'mockName1(mockParam1_1, mockParam1_2)', - }, - { - kind: vscode.CompletionItemKind.Variable, - label: 'mockParam1_1', - }, - { - kind: vscode.CompletionItemKind.Variable, - label: 'mockParam1_2', - }, - { - kind: vscode.CompletionItemKind.Variable, - label: 'mockVariable1_1', - }, - { - detail: 'mockComment2', - insertText: new vscode.SnippetString('mockName2($1)'), - kind: vscode.CompletionItemKind.Method, - label: 'mockName2(mockParam2_1, mockParam2_2)', + comment: 'mockComment', + endLine: 2, + full: 'mockName(mockParam1, mockParam2)', + line: 0, + name: 'mockName', + params: ['mockParam1', 'mockParam2'], + uriString: 'mockUri1', + variables: ['mockVariable1'], }, ], + 'mockUri1', + 1, + [], + ], + [ + { + detail: 'mockComment', + insertText: new vscode.SnippetString('mockName($1)'), + kind: vscode.CompletionItemKind.Method, + label: 'mockName(mockParam1, mockParam2)', + }, + { + kind: vscode.CompletionItemKind.Variable, + label: 'mockParam1', + }, + { + kind: vscode.CompletionItemKind.Variable, + label: 'mockParam2', + }, + { + kind: vscode.CompletionItemKind.Variable, + label: 'mockVariable1', + }, ], + ], + [ + 'same file, inside one of two methods, include locals of only the current method', [ - 'just variables', - [[], 'mockUri', 1, ['mockVariable1', 'mockVariable2']], [ { - kind: vscode.CompletionItemKind.Variable, - label: 'mockVariable1', + comment: 'mockComment1', + endLine: 2, + full: 'mockName1(mockParam1_1, mockParam1_2)', + line: 0, + name: 'mockName1', + params: ['mockParam1_1', 'mockParam1_2'], + uriString: 'mockUri', + variables: ['mockVariable1_1'], }, { - kind: vscode.CompletionItemKind.Variable, - label: 'mockVariable2', + comment: 'mockComment2', + endLine: 4, + full: 'mockName2(mockParam2_1, mockParam2_2)', + line: 3, + name: 'mockName2', + params: ['mockParam2_1', 'mockParam2_2'], + uriString: 'mockUri', + variables: ['mockVariable2_1'], }, ], + 'mockUri', + 1, + [], ], [ - 'the big one', - [ - [ - { - comment: 'mockComment1', - endLine: 2, - full: 'mockName1(mockParam1_1, mockParam1_2)', - line: 0, - name: 'mockName1', - params: ['mockParam1_1', 'mockParam1_2'], - uriString: 'mockUri1', - variables: ['mockVariable1_1'], - }, - { - comment: 'mockComment2', - endLine: 4, - full: 'mockName2(mockParam2_1, mockParam2_2)', - line: 3, - name: 'mockName2', - params: ['mockParam2_1', 'mockParam2_2'], - uriString: 'mockUri1', - variables: ['mockVariable2_1'], - }, - { - comment: 'mockComment3', - endLine: 2, - full: 'mockName3(mockParam3_1, mockParam3_2)', - line: 0, - name: 'mockName3', - params: ['mockParam3_1', 'mockParam3_2'], - uriString: 'mockUri2', - variables: ['mockVariable3_1'], - }, - ], - 'mockUri1', - 1, - ['mockVariable1', 'mockVariable2'], - ], + { + detail: 'mockComment1', + insertText: new vscode.SnippetString('mockName1($1)'), + kind: vscode.CompletionItemKind.Method, + label: 'mockName1(mockParam1_1, mockParam1_2)', + }, + { + kind: vscode.CompletionItemKind.Variable, + label: 'mockParam1_1', + }, + { + kind: vscode.CompletionItemKind.Variable, + label: 'mockParam1_2', + }, + { + kind: vscode.CompletionItemKind.Variable, + label: 'mockVariable1_1', + }, + { + detail: 'mockComment2', + insertText: new vscode.SnippetString('mockName2($1)'), + kind: vscode.CompletionItemKind.Method, + label: 'mockName2(mockParam2_1, mockParam2_2)', + }, + ], + ], + [ + 'just variables', + [[], 'mockUri', 1, ['mockVariable1', 'mockVariable2']], + [ + { + kind: vscode.CompletionItemKind.Variable, + label: 'mockVariable1', + }, + { + kind: vscode.CompletionItemKind.Variable, + label: 'mockVariable2', + }, + ], + ], + [ + 'the big one', + [ [ { - detail: 'mockComment1', - insertText: new vscode.SnippetString('mockName1($1)'), - kind: vscode.CompletionItemKind.Method, - label: 'mockName1(mockParam1_1, mockParam1_2)', - }, - { - kind: vscode.CompletionItemKind.Variable, - label: 'mockParam1_1', - }, - { - kind: vscode.CompletionItemKind.Variable, - label: 'mockParam1_2', + comment: 'mockComment1', + endLine: 2, + full: 'mockName1(mockParam1_1, mockParam1_2)', + line: 0, + name: 'mockName1', + params: ['mockParam1_1', 'mockParam1_2'], + uriString: 'mockUri1', + variables: ['mockVariable1_1'], }, { - kind: vscode.CompletionItemKind.Variable, - label: 'mockVariable1_1', + comment: 'mockComment2', + endLine: 4, + full: 'mockName2(mockParam2_1, mockParam2_2)', + line: 3, + name: 'mockName2', + params: ['mockParam2_1', 'mockParam2_2'], + uriString: 'mockUri1', + variables: ['mockVariable2_1'], }, { - detail: 'mockComment2', - insertText: new vscode.SnippetString('mockName2($1)'), - kind: vscode.CompletionItemKind.Method, - label: 'mockName2(mockParam2_1, mockParam2_2)', - }, - { - detail: 'mockComment3', - insertText: new vscode.SnippetString('mockName3($1)'), - kind: vscode.CompletionItemKind.Method, - label: 'mockName3(mockParam3_1, mockParam3_2)', - }, - { - kind: vscode.CompletionItemKind.Variable, - label: 'mockVariable1', - }, - { - kind: vscode.CompletionItemKind.Variable, - label: 'mockVariable2', + comment: 'mockComment3', + endLine: 2, + full: 'mockName3(mockParam3_1, mockParam3_2)', + line: 0, + name: 'mockName3', + params: ['mockParam3_1', 'mockParam3_2'], + uriString: 'mockUri2', + variables: ['mockVariable3_1'], }, ], + 'mockUri1', + 1, + ['mockVariable1', 'mockVariable2'], + ], + [ + { + detail: 'mockComment1', + insertText: new vscode.SnippetString('mockName1($1)'), + kind: vscode.CompletionItemKind.Method, + label: 'mockName1(mockParam1_1, mockParam1_2)', + }, + { + kind: vscode.CompletionItemKind.Variable, + label: 'mockParam1_1', + }, + { + kind: vscode.CompletionItemKind.Variable, + label: 'mockParam1_2', + }, + { + kind: vscode.CompletionItemKind.Variable, + label: 'mockVariable1_1', + }, + { + detail: 'mockComment2', + insertText: new vscode.SnippetString('mockName2($1)'), + kind: vscode.CompletionItemKind.Method, + label: 'mockName2(mockParam2_1, mockParam2_2)', + }, + { + detail: 'mockComment3', + insertText: new vscode.SnippetString('mockName3($1)'), + kind: vscode.CompletionItemKind.Method, + label: 'mockName3(mockParam3_1, mockParam3_2)', + }, + { + kind: vscode.CompletionItemKind.Variable, + label: 'mockVariable1', + }, + { + kind: vscode.CompletionItemKind.Variable, + label: 'mockVariable2', + }, ], - ]; - tests.forEach(([name, args, expected]) => - test(name, () => - assert.deepEqual( - provideCompletionItemsInner(...args), - expected, - ), - ), - ); - }); + ], + ]; + tests.forEach(([name, args, expected]) => + test(name, () => + assert.deepEqual(provideCompletionItemsInner(...args), expected), + ), + ); }); diff --git a/src/providers/defProvider.ts b/src/providers/defProvider.ts index 5bee2ec7..5fe5a558 100644 --- a/src/providers/defProvider.ts +++ b/src/providers/defProvider.ts @@ -110,8 +110,7 @@ export class DefProvider implements vscode.DefinitionProvider { return this.tryGetFileLink( document, position, - // TODO deprecated - vscode.workspace.rootPath, + vscode.workspace.workspaceFolders?.[0].uri.fsPath, ); } else { return undefined; diff --git a/src/test/ahk2.e2e.ts b/src/test/ahk2.e2e.ts deleted file mode 100644 index 597a9c12..00000000 --- a/src/test/ahk2.e2e.ts +++ /dev/null @@ -1,51 +0,0 @@ -import * as assert from 'assert'; -import * as vscode from 'vscode'; -import * as path from 'path'; -import { - closePanel, - getDocument, - isOutputVisible, - showDocument, - updateConfig, -} from './utils'; - -// Currently in `out` folder, need to get back to main `src` folder -const filesParentPath = path.join( - __dirname, // ./out/src/test - '..', // ./out/src - '..', // ./out - '..', // . - 'src', // ./src - 'test', // ./src/test - 'samples', // ./src/test/samples -); - -suite('ahk2', () => { - // CI does not have AHK installed - suite('general.showOutput @ignoreCI', () => { - const before = async (show: 'always' | 'never') => { - await updateConfig('general', { showOutput: show }); - const filePath = path.join(filesParentPath, 'ahk2.ahk2'); - const doc = await getDocument(filePath); - await showDocument(doc); - }; - - const runTests: [name: string, show: 'always' | 'never'][] = [ - ['always + run', 'always'], - ['never + run', 'never'], - ]; - - runTests.forEach(([name, show]) => { - test(name, async () => { - await before(show); - - // run cmd opens panel when `showOutput` is 'always' - await closePanel(); - - await vscode.commands.executeCommand(`ahk++.run`); - - assert.equal(await isOutputVisible(), show === 'always'); - }); - }); - }); -}); diff --git a/src/test/config.e2e.ts b/src/test/config.e2e.ts new file mode 100644 index 00000000..218f916c --- /dev/null +++ b/src/test/config.e2e.ts @@ -0,0 +1,106 @@ +import * as assert from 'assert'; +import * as vscode from 'vscode'; +import * as path from 'path'; +import { + closePanel, + getDocument, + isOutputVisible, + showDocument, + sleep, + updateConfig, +} from './utils'; +import { resolve } from 'path'; + +const rootPath = path.join(__dirname, '..', '..', '..'); + +// Currently in `out` folder, need to get back to main `src` folder +const samplesParentPath = path.join(rootPath, 'src/test/samples'); + +// CI does not have AHK installed +suite('general.showOutput @ignoreCI', () => { + const before = async (show: 'always' | 'never') => { + await updateConfig('general', { showOutput: show }); + const filePath = path.join(samplesParentPath, 'ahk2.ahk2'); + const doc = await getDocument(filePath); + await showDocument(doc); + }; + + const runTests: [name: string, show: 'always' | 'never'][] = [ + ['always + run', 'always'], + ['never + run', 'never'], + ]; + + runTests.forEach(([name, show]) => { + test(name, async () => { + await before(show); + + // run cmd opens panel when `showOutput` is 'always' + await closePanel(); + + await vscode.commands.executeCommand(`ahk++.run`); + + assert.equal(await isOutputVisible(), show === 'always'); + }); + }); +}); + +suite('exclude', () => { + // todo can only run one test at a time as changes take effect after restart + test.skip('no exclusions', async () => { + await vscode.workspace + .getConfiguration('AHK++') + .update('exclude', [], vscode.ConfigurationTarget.Workspace); + const filePath = resolve(rootPath, './e2e/main.ahk'); + const doc = await getDocument(filePath); + const editor = await showDocument(doc); + editor.insertSnippet( + new vscode.SnippetString('MyExclu') + .appendTabstop(0) + .appendText('\n'), + ); + await sleep(100); + editor.selection = new vscode.Selection(0, 0, 0, 'MyExclu'.length); + await sleep(100); + + // Get completion items + const completionItems = + await vscode.commands.executeCommand( + 'vscode.executeCompletionItemProvider', + doc.uri, + editor.selection.active, + ); + const labels = completionItems?.items.map((i) => i.label); + assert.strictEqual(labels.includes('MyExcludedFunc'), true); + }); + + test('exclusions', async () => { + await vscode.workspace + .getConfiguration('AHK++') + .update( + 'exclude', + ['excluded.ahk'], + vscode.ConfigurationTarget.Workspace, + ); + const filePath = resolve(rootPath, './e2e/main.ahk'); + const doc = await getDocument(filePath); + const editor = await showDocument(doc); + editor.insertSnippet( + new vscode.SnippetString('MyExclu') + .appendTabstop(0) + .appendText('\n'), + ); + await sleep(100); + editor.selection = new vscode.Selection(0, 0, 0, 'MyExclu'.length); + await sleep(100); + + // Get completion items + const completionItems = + await vscode.commands.executeCommand( + 'vscode.executeCompletionItemProvider', + doc.uri, + editor.selection.active, + ); + const labels = completionItems?.items.map((i) => i.label); + assert.strictEqual(labels.includes('MyExcludedFunc'), false); + }); +}); diff --git a/src/test/utils.ts b/src/test/utils.ts index bb98e2e2..ef8ea58c 100644 --- a/src/test/utils.ts +++ b/src/test/utils.ts @@ -42,6 +42,6 @@ export const closePanel = async (): Promise => { export const updateConfig = async (section: string, value: unknown) => { await vscode.workspace .getConfiguration('AHK++') - .update(section, value, true); - await sleep(80); + .update(section, value, false); + await sleep(1500); // todo tests are flaky even at 1_000ms };