diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 594e55cda..d1005b9e2 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -39,7 +39,7 @@ npm run watch - `out` Compiled extension code - `images` Icons, logos, etc. -- `snippets` Bundled MongoDB Snippets +- `snippets` Bundled Terraform Snippets - `syntaxes` [Syntax highlighting](https://code.visualstudio.com/api/language-extensions/syntax-highlight-guide#injection-grammars) for MongoDB keywords - `src/test/suite` Where tests live with '`*.test.ts`' files names - `scripts` Project helper scripts diff --git a/package-lock.json b/package-lock.json index 4b30cd969..4664496fa 100644 --- a/package-lock.json +++ b/package-lock.json @@ -18,13 +18,13 @@ "@iconify/react": "^1.1.4", "@leafygreen-ui/logo": "^6.3.0", "@leafygreen-ui/toggle": "^7.0.5", - "@mongodb-js/mongodb-constants": "^0.2.2", + "@mongodb-js/mongodb-constants": "^0.3.0", "@mongosh/browser-runtime-electron": "^1.8.0", "@mongosh/i18n": "^1.8.0", "@mongosh/service-provider-server": "^1.8.0", "@mongosh/shell-api": "^1.8.0", "analytics-node": "^6.2.0", - "bson": "^5.0.1", + "bson": "^5.1.0", "bson-transpilers": "^2.0.3", "classnames": "^2.3.2", "debug": "^4.3.4", @@ -55,8 +55,6 @@ "@types/babel__core": "^7.20.0", "@types/babel__traverse": "^7.18.3", "@types/chai": "^4.3.4", - "@types/chai-fs": "^2.0.2", - "@types/chai-json-schema": "^1.4.6", "@types/debug": "^4.1.7", "@types/enzyme": "^3.10.12", "@types/glob": "^7.2.0", @@ -64,14 +62,14 @@ "@types/micromatch": "^4.0.2", "@types/mkdirp": "^2.0.0", "@types/mocha": "^8.2.3", - "@types/node": "^14.18.38", + "@types/node": "^14.18.40", "@types/react": "^17.0.53", "@types/react-dom": "^17.0.19", "@types/sinon": "^9.0.11", "@types/uuid": "^8.3.4", "@types/vscode": "^1.76.0", - "@typescript-eslint/eslint-plugin": "^5.55.0", - "@typescript-eslint/parser": "^5.55.0", + "@typescript-eslint/eslint-plugin": "^5.56.0", + "@typescript-eslint/parser": "^5.56.0", "@vscode/test-electron": "^2.3.0", "@vscode/vsce": "^2.18.0", "@wojtekmaj/enzyme-adapter-react-17": "^0.8.0", @@ -79,8 +77,6 @@ "buffer": "^6.0.3", "chai": "^4.3.7", "chai-as-promised": "^7.1.1", - "chai-fs": "^2.0.0", - "chai-json-schema": "^1.5.1", "chalk": "^4.1.2", "cli-ux": "^5.6.7", "context-map-webpack-plugin": "^0.1.0", @@ -88,7 +84,7 @@ "css-loader": "^3.6.0", "depcheck": "^1.4.3", "download": "^8.0.0", - "electron": "^23.0.0", + "electron": "^23.2.0", "enzyme": "^3.11.0", "eslint": "^8.36.0", "eslint-config-mongodb-js": "^5.0.3", @@ -104,14 +100,14 @@ "mocha": "^8.4.0", "mocha-junit-reporter": "^2.2.0", "mocha-multi": "^1.1.7", - "mongodb-client-encryption": "^2.6.0", + "mongodb-client-encryption": "^2.7.1", "mongodb-runner": "^4.10.0", "node-loader": "^0.6.0", "npm-run-all": "^4.1.5", "ora": "^5.4.1", "postcss-loader": "^3.0.0", "pre-commit": "^1.2.2", - "prettier": "^2.8.5", + "prettier": "^2.8.6", "process": "^0.11.10", "semver": "^7.3.8", "sinon": "^9.2.4", @@ -122,7 +118,7 @@ "ts-loader": "^9.4.2", "ts-node": "^10.9.1", "typescript": "^4.9.5", - "webpack": "^5.74.0", + "webpack": "^5.76.3", "webpack-cli": "^4.10.0", "xvfb-maybe": "^0.2.1", "yargs-parser": "^20.2.9" @@ -3278,9 +3274,12 @@ } }, "node_modules/@mongodb-js/mongodb-constants": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/@mongodb-js/mongodb-constants/-/mongodb-constants-0.2.2.tgz", - "integrity": "sha512-vm1G+/WRWmXGyE9ZnhDv9toe+LRu1x0F/lGEwqWESfBiUUUuVZhj25fS2o4IL7H4pJ31sFxr7/gu+ER8OkmtzA==" + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@mongodb-js/mongodb-constants/-/mongodb-constants-0.3.0.tgz", + "integrity": "sha512-HLkCteg3jXnPY6l3h+EdZwd/CSmS2hXE5tPQvpKyuU76/CXPWkLBcThv+BEc2voXlY6HRkQxMpKDiGMiL+O8+A==", + "dependencies": { + "semver": "^7.3.8" + } }, "node_modules/@mongodb-js/prettier-config-compass": { "version": "1.0.0", @@ -3383,6 +3382,11 @@ "node": ">=14.15.1" } }, + "node_modules/@mongosh/autocomplete/node_modules/@mongodb-js/mongodb-constants": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/@mongodb-js/mongodb-constants/-/mongodb-constants-0.2.2.tgz", + "integrity": "sha512-vm1G+/WRWmXGyE9ZnhDv9toe+LRu1x0F/lGEwqWESfBiUUUuVZhj25fS2o4IL7H4pJ31sFxr7/gu+ER8OkmtzA==" + }, "node_modules/@mongosh/browser-runtime-core": { "version": "1.8.0", "resolved": "https://registry.npmjs.org/@mongosh/browser-runtime-core/-/browser-runtime-core-1.8.0.tgz", @@ -4045,25 +4049,6 @@ "resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.3.4.tgz", "integrity": "sha512-KnRanxnpfpjUTqTCXslZSEdLfXExwgNxYPdiO2WGUj8+HDjFi8R3k5RVKPeSCzLjCcshCAtVO2QBbVuAV4kTnw==" }, - "node_modules/@types/chai-fs": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/@types/chai-fs/-/chai-fs-2.0.2.tgz", - "integrity": "sha512-nS385nRPNvi9UjSUCDE7f84IXZnIzooPh7Ky88kdRfSFk72juvQENqrqyrKkJeXJsN9bMG9/5zVGr06GcBRB5g==", - "dev": true, - "dependencies": { - "@types/chai": "*", - "@types/node": "*" - } - }, - "node_modules/@types/chai-json-schema": { - "version": "1.4.6", - "resolved": "https://registry.npmjs.org/@types/chai-json-schema/-/chai-json-schema-1.4.6.tgz", - "integrity": "sha512-Z+zM4XwNkno4nWkKkbeLvf6IZTnA7ffGpX0QAcql0wQdKniGc849Leblx4ojRsv7IjS4Ct16MGr1SLNrJQZsRg==", - "dev": true, - "dependencies": { - "@types/tv4": "*" - } - }, "node_modules/@types/cheerio": { "version": "0.22.22", "resolved": "https://registry.npmjs.org/@types/cheerio/-/cheerio-0.22.22.tgz", @@ -4324,9 +4309,9 @@ "dev": true }, "node_modules/@types/node": { - "version": "14.18.38", - "resolved": "https://registry.npmjs.org/@types/node/-/node-14.18.38.tgz", - "integrity": "sha512-zMRIidN2Huikv/+/U7gRPFYsXDR/7IGqFZzTLnCEj5+gkrQjsowfamaxEnyvArct5hxGA3bTxMXlYhH78V6Cew==" + "version": "14.18.40", + "resolved": "https://registry.npmjs.org/@types/node/-/node-14.18.40.tgz", + "integrity": "sha512-pGteXO/JQX7wPxGR8lyT+doqjMa7XvlVowwrDwLfX92k5SdLkk4cwC7CYSLBxrenw/R5oQwKioVIak7ZgplM3g==" }, "node_modules/@types/normalize-package-data": { "version": "2.4.0", @@ -4417,12 +4402,6 @@ "integrity": "sha512-RJJrrySY7A8havqpGObOB4W92QXKJo63/jFLLgpvOtsGUqbQZ9Sbgl35KMm1DjC6j7AvmmU2bIno+3IyEaemaw==", "dev": true }, - "node_modules/@types/tv4": { - "version": "1.2.29", - "resolved": "https://registry.npmjs.org/@types/tv4/-/tv4-1.2.29.tgz", - "integrity": "sha512-NtJmi+XbYocrLb5Au4Q64srX4FlCPDvrSF/OnK3H0QJwrw40tIUoQPDoUHnZ5wpAB2KThtVyeS+kOEQyZabORg==", - "dev": true - }, "node_modules/@types/use-sync-external-store": { "version": "0.0.3", "resolved": "https://registry.npmjs.org/@types/use-sync-external-store/-/use-sync-external-store-0.0.3.tgz", @@ -4471,15 +4450,15 @@ } }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "5.55.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.55.0.tgz", - "integrity": "sha512-IZGc50rtbjk+xp5YQoJvmMPmJEYoC53SiKPXyqWfv15XoD2Y5Kju6zN0DwlmaGJp1Iw33JsWJcQ7nw0lGCGjVg==", + "version": "5.56.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.56.0.tgz", + "integrity": "sha512-ZNW37Ccl3oMZkzxrYDUX4o7cnuPgU+YrcaYXzsRtLB16I1FR5SHMqga3zGsaSliZADCWo2v8qHWqAYIj8nWCCg==", "dev": true, "dependencies": { "@eslint-community/regexpp": "^4.4.0", - "@typescript-eslint/scope-manager": "5.55.0", - "@typescript-eslint/type-utils": "5.55.0", - "@typescript-eslint/utils": "5.55.0", + "@typescript-eslint/scope-manager": "5.56.0", + "@typescript-eslint/type-utils": "5.56.0", + "@typescript-eslint/utils": "5.56.0", "debug": "^4.3.4", "grapheme-splitter": "^1.0.4", "ignore": "^5.2.0", @@ -4514,14 +4493,14 @@ } }, "node_modules/@typescript-eslint/parser": { - "version": "5.55.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.55.0.tgz", - "integrity": "sha512-ppvmeF7hvdhUUZWSd2EEWfzcFkjJzgNQzVST22nzg958CR+sphy8A6K7LXQZd6V75m1VKjp+J4g/PCEfSCmzhw==", + "version": "5.56.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.56.0.tgz", + "integrity": "sha512-sn1OZmBxUsgxMmR8a8U5QM/Wl+tyqlH//jTqCg8daTAmhAk26L2PFhcqPLlYBhYUJMZJK276qLXlHN3a83o2cg==", "dev": true, "dependencies": { - "@typescript-eslint/scope-manager": "5.55.0", - "@typescript-eslint/types": "5.55.0", - "@typescript-eslint/typescript-estree": "5.55.0", + "@typescript-eslint/scope-manager": "5.56.0", + "@typescript-eslint/types": "5.56.0", + "@typescript-eslint/typescript-estree": "5.56.0", "debug": "^4.3.4" }, "engines": { @@ -4541,13 +4520,13 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "5.55.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.55.0.tgz", - "integrity": "sha512-OK+cIO1ZGhJYNCL//a3ROpsd83psf4dUJ4j7pdNVzd5DmIk+ffkuUIX2vcZQbEW/IR41DYsfJTB19tpCboxQuw==", + "version": "5.56.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.56.0.tgz", + "integrity": "sha512-jGYKyt+iBakD0SA5Ww8vFqGpoV2asSjwt60Gl6YcO8ksQ8s2HlUEyHBMSa38bdLopYqGf7EYQMUIGdT/Luw+sw==", "dev": true, "dependencies": { - "@typescript-eslint/types": "5.55.0", - "@typescript-eslint/visitor-keys": "5.55.0" + "@typescript-eslint/types": "5.56.0", + "@typescript-eslint/visitor-keys": "5.56.0" }, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" @@ -4558,13 +4537,13 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "5.55.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.55.0.tgz", - "integrity": "sha512-ObqxBgHIXj8rBNm0yh8oORFrICcJuZPZTqtAFh0oZQyr5DnAHZWfyw54RwpEEH+fD8suZaI0YxvWu5tYE/WswA==", + "version": "5.56.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.56.0.tgz", + "integrity": "sha512-8WxgOgJjWRy6m4xg9KoSHPzBNZeQbGlQOH7l2QEhQID/+YseaFxg5J/DLwWSsi9Axj4e/cCiKx7PVzOq38tY4A==", "dev": true, "dependencies": { - "@typescript-eslint/typescript-estree": "5.55.0", - "@typescript-eslint/utils": "5.55.0", + "@typescript-eslint/typescript-estree": "5.56.0", + "@typescript-eslint/utils": "5.56.0", "debug": "^4.3.4", "tsutils": "^3.21.0" }, @@ -4585,9 +4564,9 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "5.55.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.55.0.tgz", - "integrity": "sha512-M4iRh4AG1ChrOL6Y+mETEKGeDnT7Sparn6fhZ5LtVJF1909D5O4uqK+C5NPbLmpfZ0XIIxCdwzKiijpZUOvOug==", + "version": "5.56.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.56.0.tgz", + "integrity": "sha512-JyAzbTJcIyhuUhogmiu+t79AkdnqgPUEsxMTMc/dCZczGMJQh1MK2wgrju++yMN6AWroVAy2jxyPcPr3SWCq5w==", "dev": true, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" @@ -4598,13 +4577,13 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "5.55.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.55.0.tgz", - "integrity": "sha512-I7X4A9ovA8gdpWMpr7b1BN9eEbvlEtWhQvpxp/yogt48fy9Lj3iE3ild/1H3jKBBIYj5YYJmS2+9ystVhC7eaQ==", + "version": "5.56.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.56.0.tgz", + "integrity": "sha512-41CH/GncsLXOJi0jb74SnC7jVPWeVJ0pxQj8bOjH1h2O26jXN3YHKDT1ejkVz5YeTEQPeLCCRY0U2r68tfNOcg==", "dev": true, "dependencies": { - "@typescript-eslint/types": "5.55.0", - "@typescript-eslint/visitor-keys": "5.55.0", + "@typescript-eslint/types": "5.56.0", + "@typescript-eslint/visitor-keys": "5.56.0", "debug": "^4.3.4", "globby": "^11.1.0", "is-glob": "^4.0.3", @@ -4625,17 +4604,17 @@ } }, "node_modules/@typescript-eslint/utils": { - "version": "5.55.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.55.0.tgz", - "integrity": "sha512-FkW+i2pQKcpDC3AY6DU54yl8Lfl14FVGYDgBTyGKB75cCwV3KpkpTMFi9d9j2WAJ4271LR2HeC5SEWF/CZmmfw==", + "version": "5.56.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.56.0.tgz", + "integrity": "sha512-XhZDVdLnUJNtbzaJeDSCIYaM+Tgr59gZGbFuELgF7m0IY03PlciidS7UQNKLE0+WpUTn1GlycEr6Ivb/afjbhA==", "dev": true, "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@types/json-schema": "^7.0.9", "@types/semver": "^7.3.12", - "@typescript-eslint/scope-manager": "5.55.0", - "@typescript-eslint/types": "5.55.0", - "@typescript-eslint/typescript-estree": "5.55.0", + "@typescript-eslint/scope-manager": "5.56.0", + "@typescript-eslint/types": "5.56.0", + "@typescript-eslint/typescript-estree": "5.56.0", "eslint-scope": "^5.1.1", "semver": "^7.3.7" }, @@ -4651,12 +4630,12 @@ } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "5.55.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.55.0.tgz", - "integrity": "sha512-q2dlHHwWgirKh1D3acnuApXG+VNXpEY5/AwRxDVuEQpxWaB0jCDe0jFMVMALJ3ebSfuOVE8/rMS+9ZOYGg1GWw==", + "version": "5.56.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.56.0.tgz", + "integrity": "sha512-1mFdED7u5bZpX6Xxf5N9U2c18sb+8EvU3tyOIj6LQZ5OOvnmj8BVeNNP603OFPm5KkS1a7IvCIcwrdHXaEMG/Q==", "dev": true, "dependencies": { - "@typescript-eslint/types": "5.55.0", + "@typescript-eslint/types": "5.56.0", "eslint-visitor-keys": "^3.3.0" }, "engines": { @@ -5627,19 +5606,6 @@ "node": ">=8" } }, - "node_modules/array-events": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/array-events/-/array-events-0.2.0.tgz", - "integrity": "sha1-/0KsU+ZvSF1viDI0wyJSvCKGEw4=", - "dev": true, - "dependencies": { - "async-arrays": "*", - "extended-emitter": "*" - }, - "engines": { - "node": "*" - } - }, "node_modules/array-filter": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/array-filter/-/array-filter-1.0.0.tgz", @@ -5762,18 +5728,6 @@ "resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz", "integrity": "sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo=" }, - "node_modules/async-arrays": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/async-arrays/-/async-arrays-1.0.1.tgz", - "integrity": "sha1-NHrytw8qeldnotVnnMQrvxwiD9k=", - "dev": true, - "dependencies": { - "sift": "*" - }, - "engines": { - "node": "*" - } - }, "node_modules/asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", @@ -6122,18 +6076,6 @@ "file-uri-to-path": "1.0.0" } }, - "node_modules/bit-mask": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/bit-mask/-/bit-mask-1.0.2.tgz", - "integrity": "sha512-UGtq08LSiazxL4zVmBzrhdCWnT4RWx3JhhD/3crhfv8xxjnVHxf/WoVjEstjSUaZeZRP7kZrWNqup1VvUClCaQ==", - "dev": true, - "dependencies": { - "array-events": "^0.2.0" - }, - "engines": { - "node": "*" - } - }, "node_modules/bl": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/bl/-/bl-2.2.1.tgz", @@ -6461,12 +6403,6 @@ "get-intrinsic": "^1.0.0" } }, - "node_modules/call-me-maybe": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/call-me-maybe/-/call-me-maybe-1.0.1.tgz", - "integrity": "sha1-JtII6onje1y95gJQoV8DHBak1ms=", - "dev": true - }, "node_modules/caller-callsite": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/caller-callsite/-/caller-callsite-2.0.0.tgz", @@ -6598,32 +6534,6 @@ "check-error": "^1.0.2" } }, - "node_modules/chai-fs": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/chai-fs/-/chai-fs-2.0.0.tgz", - "integrity": "sha1-Na4Dn7uwcQ9RIqrhf6uh6PQRB8Y=", - "dev": true, - "dependencies": { - "bit-mask": "^1.0.1", - "readdir-enhanced": "^1.4.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/chai-json-schema": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/chai-json-schema/-/chai-json-schema-1.5.1.tgz", - "integrity": "sha512-TR/xPDxRhqwFFCWg1HgL8nNWbpNfUwaib6pBN++QKpnd0t+o3+MBvAn5CM1mpdUMaM76oJAtUjGKdjGad01lIA==", - "dev": true, - "dependencies": { - "jsonpointer.js": "0.4.0", - "tv4": "^1.3.0" - }, - "engines": { - "node": ">= 6" - } - }, "node_modules/chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", @@ -8822,9 +8732,9 @@ } }, "node_modules/electron": { - "version": "23.1.4", - "resolved": "https://registry.npmjs.org/electron/-/electron-23.1.4.tgz", - "integrity": "sha512-3Z6CpAPdhv6haYX9DTO7k0l6uAUreZM3/EngQYqGN3Gz7Fp0DPb/egt8BwR3ClG/jTlQM+PQ+5WkTK0eMjm07A==", + "version": "23.2.0", + "resolved": "https://registry.npmjs.org/electron/-/electron-23.2.0.tgz", + "integrity": "sha512-De9e21cri0QYct/w6tTNOnKyCt9RVKUw5F8PEN4FPzGR9tr6IT53uyt42uH754uJWrZeLMCAdoXy6/0GmMmYZA==", "dev": true, "hasInstallScript": true, "dependencies": { @@ -9085,12 +8995,6 @@ "type": "^1.0.1" } }, - "node_modules/es6-promise": { - "version": "4.2.8", - "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.8.tgz", - "integrity": "sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w==", - "dev": true - }, "node_modules/es6-symbol": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.3.tgz", @@ -10529,19 +10433,6 @@ "node": ">=0.10.0" } }, - "node_modules/extended-emitter": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/extended-emitter/-/extended-emitter-1.0.4.tgz", - "integrity": "sha512-QBGuIo+pCXnYNeLUObaH/IKrCrzWzm4KhQNvA/mwNTs7/wzFylmA765zxh0WwWqpX1skQGXvzcRMHScc87Om/g==", - "dev": true, - "dependencies": { - "sift": "*", - "wolfy87-eventemitter": "*" - }, - "engines": { - "node": "*" - } - }, "node_modules/external-editor": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.1.0.tgz", @@ -11293,12 +11184,6 @@ "node": ">= 6" } }, - "node_modules/glob-to-regexp": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.3.0.tgz", - "integrity": "sha1-jFoUlNIGbFcMw7/kSWF1rMTVAqs=", - "dev": true - }, "node_modules/glob/node_modules/minimatch": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", @@ -15215,12 +15100,6 @@ "graceful-fs": "^4.1.6" } }, - "node_modules/jsonpointer.js": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/jsonpointer.js/-/jsonpointer.js-0.4.0.tgz", - "integrity": "sha1-ACyxI/dnqv3rAZYTLOXE+ZQcyro=", - "dev": true - }, "node_modules/jsprim": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", @@ -16688,9 +16567,9 @@ } }, "node_modules/mongodb-client-encryption": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/mongodb-client-encryption/-/mongodb-client-encryption-2.6.0.tgz", - "integrity": "sha512-2fJFZLX+VK7zOyMkOy/LAH2TO1TpBLzPW0YzIujhsng//KRcdvro/JmQ3R/iwptjZJSrPOOZcVUq9yi5KY5akg==", + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/mongodb-client-encryption/-/mongodb-client-encryption-2.7.1.tgz", + "integrity": "sha512-LN1udDf9nZ/paUcuY2AATIVWKf7oGHb2IKMQqGu/7iiqpNlV0M6xjbQix0bxEQvzZZW0T9PG6PN8mYeMR+YDnA==", "devOptional": true, "hasInstallScript": true, "dependencies": { @@ -19774,9 +19653,9 @@ } }, "node_modules/prettier": { - "version": "2.8.5", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.5.tgz", - "integrity": "sha512-3gzuxrHbKUePRBB4ZeU08VNkUcqEHaUaouNt0m7LGP4Hti/NuB07C7PPTM/LkWqXoJYJn2McEo5+kxPNrtQkLQ==", + "version": "2.8.6", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.6.tgz", + "integrity": "sha512-mtuzdiBbHwPEgl7NxWlqOkithPyp4VN93V7VeHVWBF+ad3I5avc0RVDT4oImXQy9H/AqxA2NSQH8pSxHW6FYbQ==", "dev": true, "bin": { "prettier": "bin-prettier.js" @@ -20153,17 +20032,6 @@ "string_decoder": "~0.10.x" } }, - "node_modules/readdir-enhanced": { - "version": "1.5.2", - "resolved": "https://registry.npmjs.org/readdir-enhanced/-/readdir-enhanced-1.5.2.tgz", - "integrity": "sha1-YUYwSGkKxqRVt1ti+nioj43IXlM=", - "dev": true, - "dependencies": { - "call-me-maybe": "^1.0.1", - "es6-promise": "^4.1.0", - "glob-to-regexp": "^0.3.0" - } - }, "node_modules/readdirp": { "version": "3.5.0", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.5.0.tgz", @@ -21112,12 +20980,6 @@ "object-inspect": "^1.8.0" } }, - "node_modules/sift": { - "version": "13.4.0", - "resolved": "https://registry.npmjs.org/sift/-/sift-13.4.0.tgz", - "integrity": "sha512-sJAs3ujQjx6QVzWPKmqK1LhTAnpEiP2Q7zJi4VSmRYGAuz9SmlyAzo8w4jJQrrGXJb/QNUd0iJvD17dRezzfAA==", - "dev": true - }, "node_modules/signal-exit": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.3.tgz", @@ -22786,15 +22648,6 @@ "node": "*" } }, - "node_modules/tv4": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/tv4/-/tv4-1.3.0.tgz", - "integrity": "sha1-0CDIRvrdUMhVq7JeuuzGj8EPeWM=", - "dev": true, - "engines": { - "node": ">= 0.8.0" - } - }, "node_modules/tweetnacl": { "version": "0.14.5", "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", @@ -23285,9 +23138,9 @@ } }, "node_modules/webpack": { - "version": "5.76.2", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.76.2.tgz", - "integrity": "sha512-Th05ggRm23rVzEOlX8y67NkYCHa9nTNcwHPBhdg+lKG+mtiW7XgggjAeeLnADAe7mLjJ6LUNfgHAuRRh+Z6J7w==", + "version": "5.76.3", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.76.3.tgz", + "integrity": "sha512-18Qv7uGPU8b2vqGeEEObnfICyw2g39CHlDEK4I7NK13LOur1d0HGmGNKGT58Eluwddpn3oEejwvBPoP4M7/KSA==", "dev": true, "dependencies": { "@types/eslint-scope": "^3.7.3", @@ -23645,12 +23498,6 @@ "node": ">=0.1.90" } }, - "node_modules/wolfy87-eventemitter": { - "version": "5.2.9", - "resolved": "https://registry.npmjs.org/wolfy87-eventemitter/-/wolfy87-eventemitter-5.2.9.tgz", - "integrity": "sha512-P+6vtWyuDw+MB01X7UeF8TaHBvbCovf4HPEMF/SV7BdDc1SMTiBy13SRD71lQh4ExFTG1d/WNzDGDCyOKSMblw==", - "dev": true - }, "node_modules/word-wrap": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", @@ -26734,9 +26581,12 @@ } }, "@mongodb-js/mongodb-constants": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/@mongodb-js/mongodb-constants/-/mongodb-constants-0.2.2.tgz", - "integrity": "sha512-vm1G+/WRWmXGyE9ZnhDv9toe+LRu1x0F/lGEwqWESfBiUUUuVZhj25fS2o4IL7H4pJ31sFxr7/gu+ER8OkmtzA==" + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@mongodb-js/mongodb-constants/-/mongodb-constants-0.3.0.tgz", + "integrity": "sha512-HLkCteg3jXnPY6l3h+EdZwd/CSmS2hXE5tPQvpKyuU76/CXPWkLBcThv+BEc2voXlY6HRkQxMpKDiGMiL+O8+A==", + "requires": { + "semver": "^7.3.8" + } }, "@mongodb-js/prettier-config-compass": { "version": "1.0.0", @@ -26814,6 +26664,13 @@ "@mongodb-js/mongodb-constants": "^0.2.2", "@mongosh/shell-api": "1.8.0", "semver": "^7.3.2" + }, + "dependencies": { + "@mongodb-js/mongodb-constants": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/@mongodb-js/mongodb-constants/-/mongodb-constants-0.2.2.tgz", + "integrity": "sha512-vm1G+/WRWmXGyE9ZnhDv9toe+LRu1x0F/lGEwqWESfBiUUUuVZhj25fS2o4IL7H4pJ31sFxr7/gu+ER8OkmtzA==" + } } }, "@mongosh/browser-runtime-core": { @@ -27359,25 +27216,6 @@ "resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.3.4.tgz", "integrity": "sha512-KnRanxnpfpjUTqTCXslZSEdLfXExwgNxYPdiO2WGUj8+HDjFi8R3k5RVKPeSCzLjCcshCAtVO2QBbVuAV4kTnw==" }, - "@types/chai-fs": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/@types/chai-fs/-/chai-fs-2.0.2.tgz", - "integrity": "sha512-nS385nRPNvi9UjSUCDE7f84IXZnIzooPh7Ky88kdRfSFk72juvQENqrqyrKkJeXJsN9bMG9/5zVGr06GcBRB5g==", - "dev": true, - "requires": { - "@types/chai": "*", - "@types/node": "*" - } - }, - "@types/chai-json-schema": { - "version": "1.4.6", - "resolved": "https://registry.npmjs.org/@types/chai-json-schema/-/chai-json-schema-1.4.6.tgz", - "integrity": "sha512-Z+zM4XwNkno4nWkKkbeLvf6IZTnA7ffGpX0QAcql0wQdKniGc849Leblx4ojRsv7IjS4Ct16MGr1SLNrJQZsRg==", - "dev": true, - "requires": { - "@types/tv4": "*" - } - }, "@types/cheerio": { "version": "0.22.22", "resolved": "https://registry.npmjs.org/@types/cheerio/-/cheerio-0.22.22.tgz", @@ -27624,9 +27462,9 @@ "dev": true }, "@types/node": { - "version": "14.18.38", - "resolved": "https://registry.npmjs.org/@types/node/-/node-14.18.38.tgz", - "integrity": "sha512-zMRIidN2Huikv/+/U7gRPFYsXDR/7IGqFZzTLnCEj5+gkrQjsowfamaxEnyvArct5hxGA3bTxMXlYhH78V6Cew==" + "version": "14.18.40", + "resolved": "https://registry.npmjs.org/@types/node/-/node-14.18.40.tgz", + "integrity": "sha512-pGteXO/JQX7wPxGR8lyT+doqjMa7XvlVowwrDwLfX92k5SdLkk4cwC7CYSLBxrenw/R5oQwKioVIak7ZgplM3g==" }, "@types/normalize-package-data": { "version": "2.4.0", @@ -27717,12 +27555,6 @@ "integrity": "sha512-RJJrrySY7A8havqpGObOB4W92QXKJo63/jFLLgpvOtsGUqbQZ9Sbgl35KMm1DjC6j7AvmmU2bIno+3IyEaemaw==", "dev": true }, - "@types/tv4": { - "version": "1.2.29", - "resolved": "https://registry.npmjs.org/@types/tv4/-/tv4-1.2.29.tgz", - "integrity": "sha512-NtJmi+XbYocrLb5Au4Q64srX4FlCPDvrSF/OnK3H0QJwrw40tIUoQPDoUHnZ5wpAB2KThtVyeS+kOEQyZabORg==", - "dev": true - }, "@types/use-sync-external-store": { "version": "0.0.3", "resolved": "https://registry.npmjs.org/@types/use-sync-external-store/-/use-sync-external-store-0.0.3.tgz", @@ -27771,15 +27603,15 @@ } }, "@typescript-eslint/eslint-plugin": { - "version": "5.55.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.55.0.tgz", - "integrity": "sha512-IZGc50rtbjk+xp5YQoJvmMPmJEYoC53SiKPXyqWfv15XoD2Y5Kju6zN0DwlmaGJp1Iw33JsWJcQ7nw0lGCGjVg==", + "version": "5.56.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.56.0.tgz", + "integrity": "sha512-ZNW37Ccl3oMZkzxrYDUX4o7cnuPgU+YrcaYXzsRtLB16I1FR5SHMqga3zGsaSliZADCWo2v8qHWqAYIj8nWCCg==", "dev": true, "requires": { "@eslint-community/regexpp": "^4.4.0", - "@typescript-eslint/scope-manager": "5.55.0", - "@typescript-eslint/type-utils": "5.55.0", - "@typescript-eslint/utils": "5.55.0", + "@typescript-eslint/scope-manager": "5.56.0", + "@typescript-eslint/type-utils": "5.56.0", + "@typescript-eslint/utils": "5.56.0", "debug": "^4.3.4", "grapheme-splitter": "^1.0.4", "ignore": "^5.2.0", @@ -27797,53 +27629,53 @@ } }, "@typescript-eslint/parser": { - "version": "5.55.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.55.0.tgz", - "integrity": "sha512-ppvmeF7hvdhUUZWSd2EEWfzcFkjJzgNQzVST22nzg958CR+sphy8A6K7LXQZd6V75m1VKjp+J4g/PCEfSCmzhw==", + "version": "5.56.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.56.0.tgz", + "integrity": "sha512-sn1OZmBxUsgxMmR8a8U5QM/Wl+tyqlH//jTqCg8daTAmhAk26L2PFhcqPLlYBhYUJMZJK276qLXlHN3a83o2cg==", "dev": true, "requires": { - "@typescript-eslint/scope-manager": "5.55.0", - "@typescript-eslint/types": "5.55.0", - "@typescript-eslint/typescript-estree": "5.55.0", + "@typescript-eslint/scope-manager": "5.56.0", + "@typescript-eslint/types": "5.56.0", + "@typescript-eslint/typescript-estree": "5.56.0", "debug": "^4.3.4" } }, "@typescript-eslint/scope-manager": { - "version": "5.55.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.55.0.tgz", - "integrity": "sha512-OK+cIO1ZGhJYNCL//a3ROpsd83psf4dUJ4j7pdNVzd5DmIk+ffkuUIX2vcZQbEW/IR41DYsfJTB19tpCboxQuw==", + "version": "5.56.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.56.0.tgz", + "integrity": "sha512-jGYKyt+iBakD0SA5Ww8vFqGpoV2asSjwt60Gl6YcO8ksQ8s2HlUEyHBMSa38bdLopYqGf7EYQMUIGdT/Luw+sw==", "dev": true, "requires": { - "@typescript-eslint/types": "5.55.0", - "@typescript-eslint/visitor-keys": "5.55.0" + "@typescript-eslint/types": "5.56.0", + "@typescript-eslint/visitor-keys": "5.56.0" } }, "@typescript-eslint/type-utils": { - "version": "5.55.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.55.0.tgz", - "integrity": "sha512-ObqxBgHIXj8rBNm0yh8oORFrICcJuZPZTqtAFh0oZQyr5DnAHZWfyw54RwpEEH+fD8suZaI0YxvWu5tYE/WswA==", + "version": "5.56.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.56.0.tgz", + "integrity": "sha512-8WxgOgJjWRy6m4xg9KoSHPzBNZeQbGlQOH7l2QEhQID/+YseaFxg5J/DLwWSsi9Axj4e/cCiKx7PVzOq38tY4A==", "dev": true, "requires": { - "@typescript-eslint/typescript-estree": "5.55.0", - "@typescript-eslint/utils": "5.55.0", + "@typescript-eslint/typescript-estree": "5.56.0", + "@typescript-eslint/utils": "5.56.0", "debug": "^4.3.4", "tsutils": "^3.21.0" } }, "@typescript-eslint/types": { - "version": "5.55.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.55.0.tgz", - "integrity": "sha512-M4iRh4AG1ChrOL6Y+mETEKGeDnT7Sparn6fhZ5LtVJF1909D5O4uqK+C5NPbLmpfZ0XIIxCdwzKiijpZUOvOug==", + "version": "5.56.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.56.0.tgz", + "integrity": "sha512-JyAzbTJcIyhuUhogmiu+t79AkdnqgPUEsxMTMc/dCZczGMJQh1MK2wgrju++yMN6AWroVAy2jxyPcPr3SWCq5w==", "dev": true }, "@typescript-eslint/typescript-estree": { - "version": "5.55.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.55.0.tgz", - "integrity": "sha512-I7X4A9ovA8gdpWMpr7b1BN9eEbvlEtWhQvpxp/yogt48fy9Lj3iE3ild/1H3jKBBIYj5YYJmS2+9ystVhC7eaQ==", + "version": "5.56.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.56.0.tgz", + "integrity": "sha512-41CH/GncsLXOJi0jb74SnC7jVPWeVJ0pxQj8bOjH1h2O26jXN3YHKDT1ejkVz5YeTEQPeLCCRY0U2r68tfNOcg==", "dev": true, "requires": { - "@typescript-eslint/types": "5.55.0", - "@typescript-eslint/visitor-keys": "5.55.0", + "@typescript-eslint/types": "5.56.0", + "@typescript-eslint/visitor-keys": "5.56.0", "debug": "^4.3.4", "globby": "^11.1.0", "is-glob": "^4.0.3", @@ -27852,28 +27684,28 @@ } }, "@typescript-eslint/utils": { - "version": "5.55.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.55.0.tgz", - "integrity": "sha512-FkW+i2pQKcpDC3AY6DU54yl8Lfl14FVGYDgBTyGKB75cCwV3KpkpTMFi9d9j2WAJ4271LR2HeC5SEWF/CZmmfw==", + "version": "5.56.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.56.0.tgz", + "integrity": "sha512-XhZDVdLnUJNtbzaJeDSCIYaM+Tgr59gZGbFuELgF7m0IY03PlciidS7UQNKLE0+WpUTn1GlycEr6Ivb/afjbhA==", "dev": true, "requires": { "@eslint-community/eslint-utils": "^4.2.0", "@types/json-schema": "^7.0.9", "@types/semver": "^7.3.12", - "@typescript-eslint/scope-manager": "5.55.0", - "@typescript-eslint/types": "5.55.0", - "@typescript-eslint/typescript-estree": "5.55.0", + "@typescript-eslint/scope-manager": "5.56.0", + "@typescript-eslint/types": "5.56.0", + "@typescript-eslint/typescript-estree": "5.56.0", "eslint-scope": "^5.1.1", "semver": "^7.3.7" } }, "@typescript-eslint/visitor-keys": { - "version": "5.55.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.55.0.tgz", - "integrity": "sha512-q2dlHHwWgirKh1D3acnuApXG+VNXpEY5/AwRxDVuEQpxWaB0jCDe0jFMVMALJ3ebSfuOVE8/rMS+9ZOYGg1GWw==", + "version": "5.56.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.56.0.tgz", + "integrity": "sha512-1mFdED7u5bZpX6Xxf5N9U2c18sb+8EvU3tyOIj6LQZ5OOvnmj8BVeNNP603OFPm5KkS1a7IvCIcwrdHXaEMG/Q==", "dev": true, "requires": { - "@typescript-eslint/types": "5.55.0", + "@typescript-eslint/types": "5.56.0", "eslint-visitor-keys": "^3.3.0" }, "dependencies": { @@ -28709,16 +28541,6 @@ "integrity": "sha512-THtfYS6KtME/yIAhKjZ2ul7XI96lQGHRputJQHO80LAWQnuGP4iCIN8vdMRboGbIEYBwU33q8Tch1os2+X0kMg==", "dev": true }, - "array-events": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/array-events/-/array-events-0.2.0.tgz", - "integrity": "sha1-/0KsU+ZvSF1viDI0wyJSvCKGEw4=", - "dev": true, - "requires": { - "async-arrays": "*", - "extended-emitter": "*" - } - }, "array-filter": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/array-filter/-/array-filter-1.0.0.tgz", @@ -28814,15 +28636,6 @@ "resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz", "integrity": "sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo=" }, - "async-arrays": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/async-arrays/-/async-arrays-1.0.1.tgz", - "integrity": "sha1-NHrytw8qeldnotVnnMQrvxwiD9k=", - "dev": true, - "requires": { - "sift": "*" - } - }, "asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", @@ -29119,15 +28932,6 @@ "file-uri-to-path": "1.0.0" } }, - "bit-mask": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/bit-mask/-/bit-mask-1.0.2.tgz", - "integrity": "sha512-UGtq08LSiazxL4zVmBzrhdCWnT4RWx3JhhD/3crhfv8xxjnVHxf/WoVjEstjSUaZeZRP7kZrWNqup1VvUClCaQ==", - "dev": true, - "requires": { - "array-events": "^0.2.0" - } - }, "bl": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/bl/-/bl-2.2.1.tgz", @@ -29397,12 +29201,6 @@ "get-intrinsic": "^1.0.0" } }, - "call-me-maybe": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/call-me-maybe/-/call-me-maybe-1.0.1.tgz", - "integrity": "sha1-JtII6onje1y95gJQoV8DHBak1ms=", - "dev": true - }, "caller-callsite": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/caller-callsite/-/caller-callsite-2.0.0.tgz", @@ -29505,26 +29303,6 @@ "check-error": "^1.0.2" } }, - "chai-fs": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/chai-fs/-/chai-fs-2.0.0.tgz", - "integrity": "sha1-Na4Dn7uwcQ9RIqrhf6uh6PQRB8Y=", - "dev": true, - "requires": { - "bit-mask": "^1.0.1", - "readdir-enhanced": "^1.4.0" - } - }, - "chai-json-schema": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/chai-json-schema/-/chai-json-schema-1.5.1.tgz", - "integrity": "sha512-TR/xPDxRhqwFFCWg1HgL8nNWbpNfUwaib6pBN++QKpnd0t+o3+MBvAn5CM1mpdUMaM76oJAtUjGKdjGad01lIA==", - "dev": true, - "requires": { - "jsonpointer.js": "0.4.0", - "tv4": "^1.3.0" - } - }, "chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", @@ -31296,9 +31074,9 @@ } }, "electron": { - "version": "23.1.4", - "resolved": "https://registry.npmjs.org/electron/-/electron-23.1.4.tgz", - "integrity": "sha512-3Z6CpAPdhv6haYX9DTO7k0l6uAUreZM3/EngQYqGN3Gz7Fp0DPb/egt8BwR3ClG/jTlQM+PQ+5WkTK0eMjm07A==", + "version": "23.2.0", + "resolved": "https://registry.npmjs.org/electron/-/electron-23.2.0.tgz", + "integrity": "sha512-De9e21cri0QYct/w6tTNOnKyCt9RVKUw5F8PEN4FPzGR9tr6IT53uyt42uH754uJWrZeLMCAdoXy6/0GmMmYZA==", "dev": true, "requires": { "@electron/get": "^2.0.0", @@ -31530,12 +31308,6 @@ } } }, - "es6-promise": { - "version": "4.2.8", - "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.8.tgz", - "integrity": "sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w==", - "dev": true - }, "es6-symbol": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.3.tgz", @@ -32662,16 +32434,6 @@ } } }, - "extended-emitter": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/extended-emitter/-/extended-emitter-1.0.4.tgz", - "integrity": "sha512-QBGuIo+pCXnYNeLUObaH/IKrCrzWzm4KhQNvA/mwNTs7/wzFylmA765zxh0WwWqpX1skQGXvzcRMHScc87Om/g==", - "dev": true, - "requires": { - "sift": "*", - "wolfy87-eventemitter": "*" - } - }, "external-editor": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.1.0.tgz", @@ -33275,12 +33037,6 @@ "is-glob": "^4.0.1" } }, - "glob-to-regexp": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.3.0.tgz", - "integrity": "sha1-jFoUlNIGbFcMw7/kSWF1rMTVAqs=", - "dev": true - }, "global": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/global/-/global-4.4.0.tgz", @@ -36527,12 +36283,6 @@ "graceful-fs": "^4.1.6" } }, - "jsonpointer.js": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/jsonpointer.js/-/jsonpointer.js-0.4.0.tgz", - "integrity": "sha1-ACyxI/dnqv3rAZYTLOXE+ZQcyro=", - "dev": true - }, "jsprim": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", @@ -37725,9 +37475,9 @@ } }, "mongodb-client-encryption": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/mongodb-client-encryption/-/mongodb-client-encryption-2.6.0.tgz", - "integrity": "sha512-2fJFZLX+VK7zOyMkOy/LAH2TO1TpBLzPW0YzIujhsng//KRcdvro/JmQ3R/iwptjZJSrPOOZcVUq9yi5KY5akg==", + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/mongodb-client-encryption/-/mongodb-client-encryption-2.7.1.tgz", + "integrity": "sha512-LN1udDf9nZ/paUcuY2AATIVWKf7oGHb2IKMQqGu/7iiqpNlV0M6xjbQix0bxEQvzZZW0T9PG6PN8mYeMR+YDnA==", "devOptional": true, "requires": { "bindings": "^1.5.0", @@ -40156,9 +39906,9 @@ "dev": true }, "prettier": { - "version": "2.8.5", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.5.tgz", - "integrity": "sha512-3gzuxrHbKUePRBB4ZeU08VNkUcqEHaUaouNt0m7LGP4Hti/NuB07C7PPTM/LkWqXoJYJn2McEo5+kxPNrtQkLQ==", + "version": "2.8.6", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.6.tgz", + "integrity": "sha512-mtuzdiBbHwPEgl7NxWlqOkithPyp4VN93V7VeHVWBF+ad3I5avc0RVDT4oImXQy9H/AqxA2NSQH8pSxHW6FYbQ==", "dev": true }, "process": { @@ -40447,17 +40197,6 @@ "string_decoder": "~0.10.x" } }, - "readdir-enhanced": { - "version": "1.5.2", - "resolved": "https://registry.npmjs.org/readdir-enhanced/-/readdir-enhanced-1.5.2.tgz", - "integrity": "sha1-YUYwSGkKxqRVt1ti+nioj43IXlM=", - "dev": true, - "requires": { - "call-me-maybe": "^1.0.1", - "es6-promise": "^4.1.0", - "glob-to-regexp": "^0.3.0" - } - }, "readdirp": { "version": "3.5.0", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.5.0.tgz", @@ -41231,12 +40970,6 @@ "object-inspect": "^1.8.0" } }, - "sift": { - "version": "13.4.0", - "resolved": "https://registry.npmjs.org/sift/-/sift-13.4.0.tgz", - "integrity": "sha512-sJAs3ujQjx6QVzWPKmqK1LhTAnpEiP2Q7zJi4VSmRYGAuz9SmlyAzo8w4jJQrrGXJb/QNUd0iJvD17dRezzfAA==", - "dev": true - }, "signal-exit": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.3.tgz", @@ -42540,12 +42273,6 @@ "safe-buffer": "^5.0.1" } }, - "tv4": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/tv4/-/tv4-1.3.0.tgz", - "integrity": "sha1-0CDIRvrdUMhVq7JeuuzGj8EPeWM=", - "dev": true - }, "tweetnacl": { "version": "0.14.5", "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", @@ -42946,9 +42673,9 @@ "dev": true }, "webpack": { - "version": "5.76.2", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.76.2.tgz", - "integrity": "sha512-Th05ggRm23rVzEOlX8y67NkYCHa9nTNcwHPBhdg+lKG+mtiW7XgggjAeeLnADAe7mLjJ6LUNfgHAuRRh+Z6J7w==", + "version": "5.76.3", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.76.3.tgz", + "integrity": "sha512-18Qv7uGPU8b2vqGeEEObnfICyw2g39CHlDEK4I7NK13LOur1d0HGmGNKGT58Eluwddpn3oEejwvBPoP4M7/KSA==", "dev": true, "requires": { "@types/eslint-scope": "^3.7.3", @@ -43209,12 +42936,6 @@ } } }, - "wolfy87-eventemitter": { - "version": "5.2.9", - "resolved": "https://registry.npmjs.org/wolfy87-eventemitter/-/wolfy87-eventemitter-5.2.9.tgz", - "integrity": "sha512-P+6vtWyuDw+MB01X7UeF8TaHBvbCovf4HPEMF/SV7BdDc1SMTiBy13SRD71lQh4ExFTG1d/WNzDGDCyOKSMblw==", - "dev": true - }, "word-wrap": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", diff --git a/package.json b/package.json index 2827b6f44..27a211103 100644 --- a/package.json +++ b/package.json @@ -37,11 +37,10 @@ "clean": "rimraf ./out/* ./dist/* ", "lint": "eslint . && prettier --check .", "update-grammar": "ts-node ./scripts/update-grammar.ts", - "update-snippets": "ts-node ./scripts/update-snippets.ts", "precompile": "npm run clean", "compile": "npm-run-all compile:*", "compile:keyfile": "ts-node ./scripts/generate-keyfile.ts", - "compile:resources": "npm run update-grammar && npm run update-snippets", + "compile:resources": "npm run update-grammar", "compile:extension": "tsc -p ./", "compile:extension-bundles": "webpack --mode development", "watch": "npm run compile && npm-run-all -p watch:*", @@ -133,10 +132,6 @@ } ], "snippets": [ - { - "language": "javascript", - "path": "./snippets/stage-autocompleter.json" - }, { "language": "terraform", "path": "./snippets/atlas-terraform.json" @@ -973,13 +968,13 @@ "@iconify/react": "^1.1.4", "@leafygreen-ui/logo": "^6.3.0", "@leafygreen-ui/toggle": "^7.0.5", - "@mongodb-js/mongodb-constants": "^0.2.2", + "@mongodb-js/mongodb-constants": "^0.3.0", "@mongosh/browser-runtime-electron": "^1.8.0", "@mongosh/i18n": "^1.8.0", "@mongosh/service-provider-server": "^1.8.0", "@mongosh/shell-api": "^1.8.0", "analytics-node": "^6.2.0", - "bson": "^5.0.1", + "bson": "^5.1.0", "bson-transpilers": "^2.0.3", "classnames": "^2.3.2", "debug": "^4.3.4", @@ -1010,8 +1005,6 @@ "@types/babel__core": "^7.20.0", "@types/babel__traverse": "^7.18.3", "@types/chai": "^4.3.4", - "@types/chai-fs": "^2.0.2", - "@types/chai-json-schema": "^1.4.6", "@types/debug": "^4.1.7", "@types/enzyme": "^3.10.12", "@types/glob": "^7.2.0", @@ -1019,14 +1012,14 @@ "@types/micromatch": "^4.0.2", "@types/mkdirp": "^2.0.0", "@types/mocha": "^8.2.3", - "@types/node": "^14.18.38", + "@types/node": "^14.18.40", "@types/react": "^17.0.53", "@types/react-dom": "^17.0.19", "@types/sinon": "^9.0.11", "@types/uuid": "^8.3.4", "@types/vscode": "^1.76.0", - "@typescript-eslint/eslint-plugin": "^5.55.0", - "@typescript-eslint/parser": "^5.55.0", + "@typescript-eslint/eslint-plugin": "^5.56.0", + "@typescript-eslint/parser": "^5.56.0", "@vscode/test-electron": "^2.3.0", "@vscode/vsce": "^2.18.0", "@wojtekmaj/enzyme-adapter-react-17": "^0.8.0", @@ -1034,8 +1027,6 @@ "buffer": "^6.0.3", "chai": "^4.3.7", "chai-as-promised": "^7.1.1", - "chai-fs": "^2.0.0", - "chai-json-schema": "^1.5.1", "chalk": "^4.1.2", "cli-ux": "^5.6.7", "context-map-webpack-plugin": "^0.1.0", @@ -1043,7 +1034,7 @@ "css-loader": "^3.6.0", "depcheck": "^1.4.3", "download": "^8.0.0", - "electron": "^23.0.0", + "electron": "^23.2.0", "enzyme": "^3.11.0", "eslint": "^8.36.0", "eslint-config-mongodb-js": "^5.0.3", @@ -1059,14 +1050,14 @@ "mocha": "^8.4.0", "mocha-junit-reporter": "^2.2.0", "mocha-multi": "^1.1.7", - "mongodb-client-encryption": "^2.6.0", + "mongodb-client-encryption": "^2.7.1", "mongodb-runner": "^4.10.0", "node-loader": "^0.6.0", "npm-run-all": "^4.1.5", "ora": "^5.4.1", "postcss-loader": "^3.0.0", "pre-commit": "^1.2.2", - "prettier": "^2.8.5", + "prettier": "^2.8.6", "process": "^0.11.10", "semver": "^7.3.8", "sinon": "^9.2.4", @@ -1077,7 +1068,7 @@ "ts-loader": "^9.4.2", "ts-node": "^10.9.1", "typescript": "^4.9.5", - "webpack": "^5.74.0", + "webpack": "^5.76.3", "webpack-cli": "^4.10.0", "xvfb-maybe": "^0.2.1", "yargs-parser": "^20.2.9" diff --git a/scripts/update-snippets.ts b/scripts/update-snippets.ts deleted file mode 100644 index 943ed3462..000000000 --- a/scripts/update-snippets.ts +++ /dev/null @@ -1,80 +0,0 @@ -import path from 'path'; -import mkdirp from 'mkdirp'; -import ora from 'ora'; -import fs from 'fs'; -import { promisify } from 'util'; -import { STAGE_OPERATORS } from '@mongodb-js/mongodb-constants'; - -const writeFile = promisify(fs.writeFile); -const SNIPPETS_DIR = path.join(__dirname, '..', 'snippets'); - -/** - * Transforms stage operator metadata into the vscode snippets. - * - * @param {String} prefix - The stage operator. - * @param {String} description - The stage description. - * @param {String} snippet - The stage snippet. - * @param {String} comment - The optional comment. - * - * @returns {String} - The vscode snippet. - */ -const snippetTemplate = ( - prefix: string, - description: string, - snippet: string, - comment?: string -): { prefix: string; body: Array; description: string } => { - const find = /[$]/; - const re = new RegExp(find, 'g'); - let body = snippet.split('\n'); - - // Stage operators store stage name separate from the stage body. In vscode - // extension we want to autopopulate the body together with the prefix. We - // also need to escape the `$` symbol in prefix. - body[0] = `\\${prefix}: ${body[0]}`; - - // The stage comments are also stored separately - // and might contain the `$` symbol - // that is being interpreted by vscode as variable name, - // but the variable is not known. - // The solution is to escape this symbol before building the stage body. - body = comment - ? [...comment.trim().replace(re, '\\$').split('\n'), ...body] - : [...body]; - - return { prefix, body, description }; -}; - -const snippets = STAGE_OPERATORS.reduce( - ( - prev: any, - curr: { - label: string; - name: string; - description: string; - snippet: string; - comment?: string; - } - ) => { - prev[`MongoDB Aggregations ${curr.name}`] = snippetTemplate( - curr.label, - curr.description, - curr.snippet, - curr.comment - ); - return prev; - }, - {} -); - -(async () => { - const ui = ora('Update snippets').start(); - - ui.info(`Create the ${SNIPPETS_DIR} folder`); - await mkdirp(SNIPPETS_DIR); - await writeFile( - `${SNIPPETS_DIR}/stage-autocompleter.json`, - JSON.stringify(snippets, null, 2) - ); - ui.succeed(`Updated ${SNIPPETS_DIR}/stage-autocompleter.json`); -})(); diff --git a/snippets/stage-autocompleter.json b/snippets/stage-autocompleter.json deleted file mode 100644 index a994d9646..000000000 --- a/snippets/stage-autocompleter.json +++ /dev/null @@ -1,648 +0,0 @@ -{ - "MongoDB Aggregations $addFields": { - "prefix": "$addFields", - "body": [ - "/**", - " * newField: The new field name.", - " * expression: The new field expression.", - " */", - "\\$addFields: {", - " ${1:newField}: ${2:expression}, ${3:...}", - "}" - ], - "description": "Adds new field(s) to a document with a computed value, or reassigns an existing field(s) with a computed value." - }, - "MongoDB Aggregations $bucket": { - "prefix": "$bucket", - "body": [ - "/**", - " * groupBy: The expression to group by.", - " * boundaries: An array of the lower boundaries for each bucket.", - " * default: The bucket name for documents that do not fall within the specified boundaries", - " * output: {", - " * outputN: Optional. The output object may contain a single or numerous field names used to accumulate values per bucket.", - " * }", - " */", - "\\$bucket: {", - " groupBy: ${1:expression},", - " boundaries: [ ${2:lowerbound}, ${3:...} ],", - " default: ${4:literal},", - " output: {", - " ${5:outputN}: { ${6:accumulator} }, ${7:...}", - " }", - "}" - ], - "description": "Categorizes incoming documents into groups, called buckets, based on specified boundaries." - }, - "MongoDB Aggregations $bucketAuto": { - "prefix": "$bucketAuto", - "body": [ - "/**", - " * groupBy: The expression to group by.", - " * buckets: The desired number of buckets", - " * output: {", - " * outputN: Optional. The output object may contain a single or numerous field names used to accumulate values per bucket.", - " * }", - " * granularity: Optional number series", - " */", - "\\$bucketAuto: {", - " groupBy: ${1:expression},", - " buckets: ${2:number},", - " output: {", - " ${3:outputN}: ${4:accumulator}, ${5:...}", - " },", - " granularity: '${6:string}'", - "}" - ], - "description": "Automatically categorizes documents into a specified number of buckets, attempting even distribution if possible." - }, - "MongoDB Aggregations $collStats": { - "prefix": "$collStats", - "body": [ - "/**", - " * histograms: Optional latency histograms.", - " * storageStats: Optional storage stats.", - " */", - "\\$collStats: {", - " latencyStats: {", - " histograms: ${1:boolean}", - " },", - " storageStats: {${2:}},", - "}" - ], - "description": "Returns statistics regarding a collection or view." - }, - "MongoDB Aggregations $count": { - "prefix": "$count", - "body": [ - "/**", - " * Provide the field name for the count.", - " */", - "\\$count: '${1:string}'" - ], - "description": "Returns a count of the number of documents at this stage of the aggregation pipeline." - }, - "MongoDB Aggregations $densify": { - "prefix": "$densify", - "body": [ - "/**", - " * field: The required field to densify.", - " * partitionByFields: The set of fields that acts as a compound key to define each partition.", - " * range: {", - " * step: The amount to increment the field value in each document.", - " * unit: If specified field must evaluate to a date for every document in the collection, otherwise must evaluate to a numeric.", - " * bounds: A string or array of numeric/date bounds, corresponding to the type of the field.", - " * }", - " */", - "\\$densify: {", - " field: ${1:string},", - " partitionByFields: [${2:string}, ${3:string}, ...],", - " range: {", - " step: ${4:number},", - " unit: ${5:string},", - " bounds: [${6:lowerbound}, ${7:upperbound}, ...]", - " }", - "}" - ], - "description": "Creates new documents to eliminate the gaps in the time or numeric domain at the required granularity level." - }, - "MongoDB Aggregations $documents": { - "prefix": "$documents", - "body": [ - "/**", - " * expression: Any valid expression.", - " */", - "\\$documents: {", - " ${1:expression}", - "}" - ], - "description": "Returns literal documents from input values." - }, - "MongoDB Aggregations $facet": { - "prefix": "$facet", - "body": [ - "/**", - " * outputFieldN: The first output field.", - " * stageN: The first aggregation stage.", - " */", - "\\$facet: {", - " ${1:outputFieldN}: [ ${2:stageN}, ${3:...} ]", - "}" - ], - "description": "Allows for multiple parellel aggregations to be specified." - }, - "MongoDB Aggregations $fill": { - "prefix": "$fill", - "body": [ - "/**", - " * sortBy: Syntax is the same as \\$sort, required if \"method\" is used in at least one output spec otherwise optional", - " * partitionBy: Optional, default is a single partition. Specification is the same as _id in \\$group (same as partitionBy in window functions).", - " * partitionByFields: Optional, set of fields that acts as a compound key to define each partition.", - " * output - Required, object for each field to fill in. For a single field, can be a single object.", - " * output. - A field to be filled with value, if missing or null in the current document.", - " */", - "\\$fill: {", - " sortBy: ${1:sortSpec},", - " partitionBy: ${2:expression},", - " partitionByFields: [${3:string}, ${4:string}, ...],", - " output: {", - " field1: {value: ${5:expression}},", - " field2: {method: ${6:string}},", - " ...", - " }", - "}" - ], - "description": "Populates null and missing field values within documents." - }, - "MongoDB Aggregations $geoNear": { - "prefix": "$geoNear", - "body": [ - "/**", - " * near: The point to search near.", - " * distanceField: The calculated distance.", - " * maxDistance: The maximum distance, in meters, documents can be before being excluded from results.", - " * query: Limits results that match the query", - " * includeLocs: Optional. Labels and includes the point used to match the document.", - " * num: Optional. The maximum number of documents to return.", - " * spherical: Defaults to false. Specifies whether to use spherical geometry.", - " */", - "\\$geoNear: {", - " near: { type: 'Point', coordinates: [ ${1:number}, ${2:number} ] },", - " distanceField: '${3:string}',", - " maxDistance: ${4:number},", - " query: {${5}},", - " includeLocs: '${6}',", - " num: ${7:number},", - " spherical: ${8:boolean}", - "}" - ], - "description": "Returns documents based on proximity to a geospatial point." - }, - "MongoDB Aggregations $graphLookup": { - "prefix": "$graphLookup", - "body": [ - "/**", - " * from: The target collection.", - " * startWith: Expression to start.", - " * connectFromField: Field to connect.", - " * connectToField: Field to connect to.", - " * as: Name of the array field.", - " * maxDepth: Optional max recursion depth.", - " * depthField: Optional Name of the depth field.", - " * restrictSearchWithMatch: Optional query.", - " */", - "\\$graphLookup: {", - " from: '${1:string}',", - " startWith: ${2:expression},", - " connectFromField: '${3:string}',", - " connectToField: '${4:string}',", - " as: '${5:string}',", - " maxDepth: ${6:number},", - " depthField: '${7:string}',", - " restrictSearchWithMatch: {${8}}", - "}" - ], - "description": "Performs a recursive search on a collection." - }, - "MongoDB Aggregations $group": { - "prefix": "$group", - "body": [ - "/**", - " * _id: The id of the group.", - " * fieldN: The first field name.", - " */", - "\\$group: {", - " _id: ${1:expression},", - " ${2:fieldN}: {", - " ${3:accumulatorN}: ${4:expressionN}", - " }", - "}" - ], - "description": "Groups documents by a specified expression." - }, - "MongoDB Aggregations $indexStats": { - "prefix": "$indexStats", - "body": [ - "/**", - " * No parameters.", - " */", - "\\$indexStats: {}" - ], - "description": "Returns statistics regarding the use of each index for the collection." - }, - "MongoDB Aggregations $limit": { - "prefix": "$limit", - "body": [ - "/**", - " * Provide the number of documents to limit.", - " */", - "\\$limit: ${1:number}" - ], - "description": "Limits the number of documents that flow into subsequent stages." - }, - "MongoDB Aggregations $lookup": { - "prefix": "$lookup", - "body": [ - "/**", - " * from: The target collection.", - " * localField: The local join field.", - " * foreignField: The target join field.", - " * as: The name for the results.", - " * pipeline: Optional pipeline to run on the foreign collection.", - " * let: Optional variables to use in the pipeline field stages.", - " */", - "\\$lookup: {", - " from: ${1:collection},", - " localField: ${2:field},", - " foreignField: ${3:field},", - " as: ${4:result}", - "}" - ], - "description": "Performs a join between two collections." - }, - "MongoDB Aggregations $match": { - "prefix": "$match", - "body": [ - "/**", - " * query: The query in MQL.", - " */", - "\\$match: {", - " ${1:query}", - "}" - ], - "description": "Filters the document stream to allow only matching documents to pass through to subsequent stages." - }, - "MongoDB Aggregations $merge": { - "prefix": "$merge", - "body": [ - "/**", - " * atlas: Location to write the documents from the aggregation pipeline.", - " * on: Fields to identify.", - " * let: Defined variables.", - " * whenMatched: Action for matching docs.", - " * whenNotMatched: Action for non-matching docs.", - " */", - "\\$merge: {", - " into: {", - " atlas: {", - " clusterName: '${1:atlasClusterName}',", - " db: '${2:database}',", - " coll: '${3:collection}',", - " projectId: '${4:optionalAtlasProjectId}'", - " }", - " },", - " on: '${5:identifier}',", - " let: { ${6:specification(s)} },", - " whenMatched: '${7:string}',", - " whenNotMatched: '${8:string}'", - "}" - ], - "description": "Merges the resulting documents into a collection, optionally overriding existing documents." - }, - "MongoDB Aggregations $out": { - "prefix": "$out", - "body": [ - "/**", - " * Use any one of the following:", - " * s3: Parameters to save the data to S3.", - " * atlas: Parameters to save the data to Atlas. Example:", - " * {", - " * atlas: {", - " * db: 'string',", - " * coll: 'string',", - " * projectId: 'string',", - " * clusterName: 'string'", - " * }", - " * }", - " */", - "\\$out: {", - " s3: {", - " bucket: '${1:string}',", - " region: '${2:string}',", - " filename: '${3:string}',", - " format: {", - " name: '${4:string}',", - " maxFileSize: '${5:bytes}',", - " maxRowGroupSize: '${6:string}',", - " columnCompression: '${7:string}'", - " },", - " errorMode: '${8:string}'", - " }", - "}" - ], - "description": "Writes the result of a pipeline to an Atlas cluster or S3 bucket." - }, - "MongoDB Aggregations $project": { - "prefix": "$project", - "body": [ - "/**", - " * specifications: The fields to", - " * include or exclude.", - " */", - "\\$project: {", - " ${1:specification(s)}", - "}" - ], - "description": "Adds new field(s) to a document with a computed value, or reassigns an existing field(s) with a computed value. Unlike $addFields, $project can also remove fields." - }, - "MongoDB Aggregations $redact": { - "prefix": "$redact", - "body": [ - "/**", - " * expression: Any valid expression that", - " * evaluates to \\$\\$DESCEND, \\$\\$PRUNE, or \\$\\$KEEP.", - " */", - "\\$redact: {", - " ${1:expression}", - "}" - ], - "description": "Restricts the content for each document based on information stored in the documents themselves" - }, - "MongoDB Aggregations $replaceWith": { - "prefix": "$replaceWith", - "body": [ - "/**", - " * replacementDocument: A document or string.", - " */", - "\\$replaceWith: {", - " newWith: ${1:replacementDocument}", - "}" - ], - "description": "Replaces a document with the specified embedded document." - }, - "MongoDB Aggregations $replaceRoot": { - "prefix": "$replaceRoot", - "body": [ - "/**", - " * replacementDocument: A document or string.", - " */", - "\\$replaceRoot: {", - " newRoot: ${1:replacementDocument}", - "}" - ], - "description": "Replaces a document with the specified embedded document." - }, - "MongoDB Aggregations $sample": { - "prefix": "$sample", - "body": [ - "/**", - " * size: The number of documents to sample.", - " */", - "\\$sample: {", - " size: ${1:number}", - "}" - ], - "description": "Randomly selects the specified number of documents from its input." - }, - "MongoDB Aggregations $search": { - "prefix": "$search", - "body": [ - "/**", - " * index: The name of the Search index.", - " * text: Analyzed search, with required fields of query and path, the analyzed field(s) to search.", - " * term: Un-analyzed search.", - " * compound: Combines ops.", - " * span: Find in text field regions.", - " * exists: Test for presence of a field.", - " * near: Find near number or date.", - " * range: Find in numeric or date range.", - " */", - "\\$search: {", - " index: '${1:string}',", - " text: {", - " query: '${2:string}',", - " path: '${3:string}'", - " }", - "}" - ], - "description": "Performs a full-text search on the specified field(s)." - }, - "MongoDB Aggregations $searchMeta": { - "prefix": "$searchMeta", - "body": [ - "/**", - " * index: The name of the Search index.", - " * count: The count of the results.", - " * facet: {", - " * operator: Analyzed search, with required fields of query and path, can either be replaced with the name of a valid operator.", - " * facets: {", - " * stringFacet: Narrows search results based on unique string values, with required fields of type and path.", - " * numberFacet: Narrows search results by breaking them up into separate ranges of numbers, with required fields of type, path, and boundaries.", - " * dateFacet: Narrows search results by breaking them up into separate ranges of dates, with required fields of type, path, and boundaries.", - " * }", - " * }", - " */", - "\\$searchMeta: {", - " index: ${1:string},", - " facet: {", - " operator: {", - " text: {", - " query: ${2:string},", - " path: ${3:string}", - " }", - " },", - " facets: {", - " ${4:stringFacet}: {", - " type: ${5:string},", - " path: ${6:string},", - " numBuckets: ${7:integer}", - " },", - " numberFacet: {", - " type: 'number',", - " path: ${8:string},", - " boundaries: [${9:lowerbound}, ${10:upperbound}, ...],", - " default: ${11:string}", - " }", - " }", - " }", - "}" - ], - "description": "Performs a full-text search on the specified field(s) and gets back only the generated search meta data from a query." - }, - "MongoDB Aggregations $set": { - "prefix": "$set", - "body": [ - "/**", - " * field: The field name", - " * expression: The expression.", - " */", - "\\$set: {", - " ${1:field}: ${2:expression}", - "}" - ], - "description": "Adds new fields to documents. $set outputs documents that contain all existing fields from the input documents and newly added fields." - }, - "MongoDB Aggregations $setWindowFields": { - "prefix": "$setWindowFields", - "body": [ - "/**", - " * partitionBy: partitioning of data.", - " * sortBy: fields to sort by.", - " * output: {", - " * path: {", - " * function: The window function to compute over the given window.", - " * window: {", - " * documents: A number of documents before and after the current document.", - " * range: A range of possible values around the value in the current document's sortBy field.", - " * unit: Specifies the units for the window bounds.", - " * }", - " * }", - " * }", - " */", - "\\$setWindowFields: {", - " partitionBy: ${1:expression},", - " sortBy: ${2:sortSpec},", - " output: {", - " ${3:path}: {", - " ${4:function}: ${5:functionArgs},", - " window: {", - " documents: [${6:lowerbound}, ${7:upperbound}],", - " range: [${8:lowerbound}, ${9:upperbound}],", - " unit: ${10:string}", - " }", - " },", - " ${11:path2}: ...", - " }", - "}" - ], - "description": "Capable of partitioning incoming data, and can apply one or more functions to defined windows within each partition." - }, - "MongoDB Aggregations $skip": { - "prefix": "$skip", - "body": [ - "/**", - " * Provide the number of documents to skip.", - " */", - "\\$skip: ${1:number}" - ], - "description": "Skips a specified number of documents before advancing to the next stage." - }, - "MongoDB Aggregations $sort": { - "prefix": "$sort", - "body": [ - "/**", - " * Provide any number of field/order pairs.", - " */", - "\\$sort: {", - " ${1:field1}: ${2:sortOrder}", - "}" - ], - "description": "Reorders the document stream by a specified sort key and direction." - }, - "MongoDB Aggregations $sortByCount": { - "prefix": "$sortByCount", - "body": [ - "/**", - " * expression: Grouping expression or string.", - " */", - "\\$sortByCount: {", - " ${1:expression}", - "}" - ], - "description": "Groups incoming documents based on the value of a specified expression, then computes the count of documents in each distinct group." - }, - "MongoDB Aggregations $unionWith": { - "prefix": "$unionWith", - "body": [ - "/**", - " * coll: The collection name.", - " * pipeline: The pipeline on the other collection.", - " */", - "\\$unionWith: {", - " coll: '${1:coll}',", - " pipeline: [${2:pipeline}]", - "}" - ], - "description": "Perform a union with a pipeline on another collection." - }, - "MongoDB Aggregations $unset": { - "prefix": "$unset", - "body": [ - "/**", - " * Provide the field name to exclude.", - " * To exclude multiple fields, pass the field names in an array.", - " */", - "\\$unset: '${1:string}'" - ], - "description": "Excludes fields from the result document." - }, - "MongoDB Aggregations $unwind": { - "prefix": "$unwind", - "body": [ - "/**", - " * path: Path to the array field.", - " * includeArrayIndex: Optional name for index.", - " * preserveNullAndEmptyArrays: Optional", - " * toggle to unwind null and empty values.", - " */", - "\\$unwind: {", - " path: ${1:path},", - " includeArrayIndex: '${2:string}',", - " preserveNullAndEmptyArrays: ${3:boolean}", - "}" - ], - "description": "Outputs a new document for each element in a specified array. " - }, - "MongoDB Aggregations $changeStream": { - "prefix": "$changeStream", - "body": [ - "/**", - " * allChangesForCluster: Optional boolean to include all changes in the cluster.", - " * fullDocument: Optional value to request a copy of full document when modified by update operations (Introduced in 6.0).", - " * fullDocumentBeforeChange: Value to configure whether to return a full document before the change or not.", - " * resumeAfter: Specifies a resume token as the logical starting point for the change stream. Cannot be used with startAfter or startAtOperationTime fields.", - " * showExpandedEvents: Specifies whether to include additional change events, such as such as DDL and index operations (Introduced in 6.0).", - " * startAfter: Specifies a resume token as the logical starting point for the change stream. Cannot be used with resumeAfter or startAtOperationTime fields.", - " * startAtOperationTime: Specifies a time as the logical starting point for the change stream. Cannot be used with resumeAfter or startAfter fields.", - " */", - "\\$changeStream: {", - " allChangesForCluster: ${1:boolean},", - " fullDocument: '${2:string}',", - " fullDocumentBeforeChange: '${3:string}',", - " resumeAfter: ${4:resumeToken},", - " showExpandedEvents: ${5:boolean},", - " startAfter: ${6:resumeToken},", - " startAtOperationTime: ${7:time},", - "}" - ], - "description": "Returns a Change Stream cursor for the collection." - }, - "MongoDB Aggregations $currentOp": { - "prefix": "$currentOp", - "body": [ - "/**", - " * allUsers: Optional boolean value to specify whether to return operations for all users or not.", - " * idleConnections: Optional boolean value to specify whether to return all operations including idle connections or not.", - " * idleCursors: Optional boolean value to specify whether to report on cursors that are idle or not.", - " * idleSessions: Optional boolean value to specify whether to report on dormant sessions or not.", - " * localOps: Optional boolean value to specify whether to report on operations running locally on targetted mongos or not.", - " * backtrace: Optional boolean value to specify whether callstack information is returned as part of the waitingForLatch output field.", - " */", - "\\$currentOp: {", - " allUsers: ${1:false},", - " idleConnections: ${2:false},", - " idleCursors: ${3:false},", - " idleSessions: ${4:true},", - " localOps: ${5:false},", - " backtrace: ${6:false},", - "}" - ], - "description": "Returns a cursor over information on active and/or dormant operations for the MongoDB deployment as well as inactive sessions that are holding locks as part of a transaction." - }, - "MongoDB Aggregations $listLocalSessions": { - "prefix": "$listLocalSessions", - "body": [ - "/**", - " * users: Optional list of users for which local sessions need to be returned.", - " * allUsers: Optional boolean value to specify whether to return local sessions for all users or not.", - " */", - "\\$listLocalSessions: {", - " allUsers: ${1:false},", - " users: [", - " { user: '${2:string}', db: '${3:string}' }", - " ]", - "}" - ], - "description": "Lists the sessions cached in memory by the mongod or mongos instance." - } -} \ No newline at end of file diff --git a/src/language/mongoDBService.ts b/src/language/mongoDBService.ts index f95b730a7..6688a9c4c 100644 --- a/src/language/mongoDBService.ts +++ b/src/language/mongoDBService.ts @@ -1,6 +1,7 @@ import * as util from 'util'; import { CompletionItemKind, + InsertTextFormat, MarkupKind, DiagnosticSeverity, } from 'vscode-languageserver/node'; @@ -11,17 +12,18 @@ import type { MarkupContent, Diagnostic, } from 'vscode-languageserver/node'; +import { CliServiceProvider } from '@mongosh/service-provider-server'; +import type { Document } from '@mongosh/service-provider-core'; +import { getFilteredCompletions } from '@mongodb-js/mongodb-constants'; +import parseSchema from 'mongodb-schema'; import path from 'path'; import { signatures } from '@mongosh/shell-api'; import translator from '@mongosh/i18n'; import { Worker as WorkerThreads } from 'worker_threads'; -import type { Document } from '@mongosh/service-provider-core'; -import { CliServiceProvider } from '@mongosh/service-provider-server'; -import parseSchema from 'mongodb-schema'; +import { ExportToLanguageMode } from '../types/playgroundType'; import formatError from '../utils/formatError'; import { ServerCommands } from './serverCommands'; -import { ExportToLanguageMode } from '../types/playgroundType'; import type { ShellEvaluateResult, PlaygroundEvaluateParams, @@ -30,8 +32,18 @@ import type { MongoClientOptions, } from '../types/playgroundType'; import { Visitor } from './visitor'; +import type { CompletionState } from './visitor'; + import DIAGNOSTIC_CODES from './diagnosticCodes'; +const PROJECT = '$project'; + +const GROUP = '$group'; + +const MATCH = '$match'; + +const SET_WINDOW_FIELDS = '$setWindowFields'; + export const languageServerWorkerFileName = 'languageServerWorker.js'; interface ServiceProviderParams { @@ -47,21 +59,21 @@ export default class MongoDBService { _connectionString?: string; _connectionOptions?: MongoClientOptions; - _cachedDatabases: CompletionItem[] = []; - _cachedFields: { [namespace: string]: CompletionItem[] } = {}; - _cachedCollections: { [database: string]: CompletionItem[] } = {}; - _cachedShellSymbols: { [symbol: string]: CompletionItem[] } = {}; - _cachedTopLevelIdentifiers: CompletionItem[] = []; + _databaseCompletionItems: CompletionItem[] = []; + _collectionCompletionItems: { [database: string]: CompletionItem[] } = {}; + _shellSymbolCompletionItems: { [symbol: string]: CompletionItem[] } = {}; + _globalSymbolCompletionItems: CompletionItem[] = []; + _fields: { [namespace: string]: string[] } = {}; _visitor: Visitor; _serviceProvider?: CliServiceProvider; constructor(connection: Connection) { this._connection = connection; - this._visitor = new Visitor(connection.console); + this._visitor = new Visitor(); - this._cacheShellSymbolsCompletionItems(); - this._cacheTopLevelIdentifierCompletionItems(); + this._cacheShellSymbolCompletionItems(); + this._cacheGlobalSymbolCompletionItems(); } /** @@ -313,8 +325,8 @@ export default class MongoDBService { /** * Return 'db' and 'use' completion items. */ - _cacheTopLevelIdentifierCompletionItems() { - this._cachedTopLevelIdentifiers = [ + _cacheGlobalSymbolCompletionItems() { + this._globalSymbolCompletionItems = [ { label: 'db', kind: CompletionItemKind.Method, @@ -331,11 +343,11 @@ export default class MongoDBService { /** * Create and cache Shell symbols completion items. */ - _cacheShellSymbolsCompletionItems() { - const shellSymbols = {}; + _cacheShellSymbolCompletionItems() { + const shellSymbolCompletionItems = {}; Object.keys(signatures).map((symbol) => { - shellSymbols[symbol] = Object.keys( + shellSymbolCompletionItems[symbol] = Object.keys( signatures[symbol].attributes || {} ).map((item) => { const documentation = @@ -368,7 +380,7 @@ export default class MongoDBService { }); }); - this._cachedShellSymbols = shellSymbols; + this._shellSymbolCompletionItems = shellSymbolCompletionItems; } /** @@ -387,11 +399,11 @@ export default class MongoDBService { ): ExportToLanguageMode { const state = this._visitor.parseAST(params); - if (state.isArray) { + if (state.isArraySelection) { return ExportToLanguageMode.AGGREGATION; } - if (state.isObject) { + if (state.isObjectSelection) { return ExportToLanguageMode.QUERY; } @@ -428,7 +440,10 @@ export default class MongoDBService { currentDatabaseName: string | null, currentCollectionName: string | null ) { - if (currentDatabaseName && !this._cachedCollections[currentDatabaseName]) { + if ( + currentDatabaseName && + !this._collectionCompletionItems[currentDatabaseName] + ) { // Get collection names for the current database. const collections = await this._getCollections(currentDatabaseName); @@ -444,7 +459,7 @@ export default class MongoDBService { if (currentDatabaseName && currentCollectionName) { const namespace = `${currentDatabaseName}.${currentCollectionName}`; - if (!this._cachedFields[namespace]) { + if (!this._fields[namespace]) { // Get field names for the current namespace. const schemaFields = await this._getSchemaFields( currentDatabaseName, @@ -452,108 +467,305 @@ export default class MongoDBService { ); // Create and cache field completion items. - this._cacheFieldCompletionItems(namespace, schemaFields); + this._cacheFields(namespace, schemaFields); } } } /** - * Parse code from a playground to identify - * where the cursor is and suggests only suitable completion items. + * If the current node is 'db.collection.aggregate([{}])'. */ - // eslint-disable-next-line complexity - async provideCompletionItems( - textFromEditor: string, - position: { line: number; character: number } - ): Promise { - this._connection.console.log( - `LS current symbol position: ${util.inspect(position)}` - ); + _provideStageCompletionItems(state: CompletionState) { + if (state.isStage) { + this._connection.console.log('VISITOR found stage operator completions'); - const state = this._visitor.parseASTWithPlaceholder( - textFromEditor, - position - ); - this._connection.console.log( - `VISITOR completion state: ${util.inspect(state)}` - ); + return getFilteredCompletions({ meta: ['stage'] }).map((item) => { + let snippet = item.value; - await this._getCompletionValuesAndUpdateCache( - textFromEditor, - position, - state.databaseName, - state.collectionName - ); + if (item.snippet) { + const escapedOp = item.value.replace('$', '\\$'); + snippet = `${escapedOp}: ${item.snippet}`; + } + + return { + label: item.value, + insertText: snippet, + insertTextFormat: InsertTextFormat.Snippet, + kind: CompletionItemKind.Keyword, + preselect: true, + detail: item.description, + }; + }); + } + } + + /** + * If the current node is 'db.collection.aggregate([{ $match: {} }])' + * or 'db.collection.find({})' + * we check a playground text before the current cursor position. + * If we found 'use("db")' or 'db.collection' we also suggest field names. + */ + _provideQueryOperatorCompletionItems(state: CompletionState) { + if ( + state.stageOperator === MATCH || + (state.stageOperator === null && state.isObjectKey) + ) { + const fields = + this._fields[`${state.databaseName}.${state.collectionName}`] || []; + const message = [ + 'VISITOR found', + 'query operator', + fields.length ? 'and fields' : '', + 'completions', + ]; + this._connection.console.log(message.join(' ')); + + return getFilteredCompletions({ + fields, + meta: ['query', 'field:identifier'], + }).map((item) => { + return { + label: item.value, + kind: + item.meta === 'field:identifier' + ? CompletionItemKind.Field + : CompletionItemKind.Keyword, + preselect: true, + detail: item.description, + }; + }); + } + } - // We check a playground code before the current cursor position. - // If we found 'use("db")' or 'db.collection' - // and the current node is an object property - // e.g. 'db.collection.find({ })'. - if (state.databaseName && state.collectionName && state.isObjectKey) { - this._connection.console.log('VISITOR found field names completion'); - return this._cachedFields[ - `${state.databaseName}.${state.collectionName}` + /** + * If the current node is 'db.collection.aggregate([{ $: {} }])' + * we check a playground text before the current cursor position. + * If we found 'use("db")' or 'db.collection' we also suggest field names. + */ + _provideAggregationOperatorCompletionItems(state: CompletionState) { + if (state.stageOperator) { + const fields = + this._fields[`${state.databaseName}.${state.collectionName}`] || []; + const message = [ + 'VISITOR found', + 'aggregation operator', + fields.length ? 'and fields' : '', + 'completions', ]; + this._connection.console.log(message.join(' ')); + + return getFilteredCompletions({ + fields, + meta: [ + 'expr:*', + 'conv', + ...([PROJECT, GROUP].includes(state.stageOperator ?? '') + ? ([ + 'accumulator', + 'accumulator:bottom-n', + 'accumulator:top-n', + ] as const) + : []), + ...(state.stageOperator === SET_WINDOW_FIELDS + ? (['accumulator:window'] as const) + : []), + ], + }).map((item) => { + return { + label: item.value, + kind: + item.meta === 'field:identifier' + ? CompletionItemKind.Field + : CompletionItemKind.Keyword, + preselect: true, + detail: item.description, + }; + }); } + } - // If the current node is 'db.collection().' - // or 'db["test"].'. - if (state.isShellMethod) { - this._connection.console.log( - 'VISITOR found shell collection methods completion' + /** + * If the current node is 'db.collection.find({ _id: });'. + */ + _provideBSONCompletionItems(state: CompletionState) { + if (state.isIdentifierObjectValue) { + this._connection.console.log('VISITOR found bson completions'); + return getFilteredCompletions({ meta: ['bson'] }).map((item) => { + let snippet = item.value; + + if (item.snippet) { + const escapedOp = item.value.replace('$', '\\$'); + snippet = `${escapedOp}: ${item.snippet}`; + } + + return { + label: item.value, + insertText: snippet, + insertTextFormat: InsertTextFormat.Snippet, + kind: CompletionItemKind.Constructor, + preselect: true, + detail: item.description, + }; + }); + } + } + + /** + * If the current node is 'db.collection.find({ $expr: { $gt: [{ $getField: { $literal: '' } }, 200] } });'. + */ + _provideFieldReferenceCompletionItems(state: CompletionState) { + if (state.isTextObjectValue) { + const fields = + this._fields[`${state.databaseName}.${state.collectionName}`]; + this._connection.console.log('VISITOR found field reference completions'); + + return getFilteredCompletions({ fields, meta: ['field:reference'] }).map( + (item) => { + return { + label: item.value, + kind: CompletionItemKind.Reference, + preselect: true, + detail: item.description, + }; + } ); - return this._cachedShellSymbols.Collection; } + } - // If the current node is 'db.collection().aggregate().'. - if (state.isAggregationCursor) { + /** + * If the current node is 'db.collection.' or 'db["test"].'. + */ + _provideCollectionSymbolCompletionItems(state: CompletionState) { + if (state.isCollectionSymbol) { this._connection.console.log( - 'VISITOR found shell aggregation cursor methods completion' + 'VISITOR found collection symbol completions' ); - return this._cachedShellSymbols.AggregationCursor; + return this._shellSymbolCompletionItems.Collection; } + } - // If the current node is 'db.collection().find().'. + /** + * If the current node is 'db.collection.find().'. + */ + _provideFindCursorCompletionItems(state: CompletionState) { if (state.isFindCursor) { + this._connection.console.log('VISITOR found find cursor completions'); + return this._shellSymbolCompletionItems.Cursor; + } + } + + /** + * If the current node is 'db.collection.aggregate().'. + */ + _provideAggregationCursorCompletionItems(state: CompletionState) { + if (state.isAggregationCursor) { this._connection.console.log( - 'VISITOR found shell cursor methods completion' + 'VISITOR found aggregation cursor completions' ); - return this._cachedShellSymbols.Cursor; + return this._shellSymbolCompletionItems.AggregationCursor; } + } - // If the current node is 'db' or 'use'. - if (state.isTopLevelIdentifier) { - return this._cachedTopLevelIdentifiers; + /** + * If the current node is 'db' or 'use'. + */ + _provideGlobalSymbolCompletionItems(state: CompletionState) { + if (state.isGlobalSymbol) { + this._connection.console.log('VISITOR found global symbol completions'); + return this._globalSymbolCompletionItems; } + } + /** + * If the current node is 'db.'. + */ + _provideDbSymbolCompletionItems(state: CompletionState) { // If we found 'use("db")' and the current node is 'db.'. - if (state.isDbCallExpression && state.databaseName) { + if (state.isDbSymbol && state.databaseName) { this._connection.console.log( - 'VISITOR found shell db methods and collection names completion' + 'VISITOR found db symbol and collection name completions' ); return [ - ...this._cachedShellSymbols.Database, - ...this._cachedCollections[state.databaseName], + ...this._shellSymbolCompletionItems.Database, + ...this._collectionCompletionItems[state.databaseName], ]; } // If the current node is 'db.'. - if (state.isDbCallExpression) { - this._connection.console.log('VISITOR found shell db methods completion'); - return this._cachedShellSymbols.Database; + if (state.isDbSymbol) { + this._connection.console.log('VISITOR found db symbol completions'); + return this._shellSymbolCompletionItems.Database; } + } - // If the current node can be used as a collection name - // e.g. 'db..find()' or 'let a = db.'. + /** + * If the current node can be used as a collection name + * e.g. 'db..find()' or 'let a = db.'. + */ + _provideCollectionNameCompletionItems(state: CompletionState) { if (state.isCollectionName && state.databaseName) { - this._connection.console.log('VISITOR found collection names completion'); - return this._cachedCollections[state.databaseName]; + this._connection.console.log('VISITOR found collection name completions'); + return this._collectionCompletionItems[state.databaseName]; } + } - // If the current node is 'use(""")'. + /** + * If the current node is 'use(""")'. + */ + _provideDbNameCompletionItems(state: CompletionState) { if (state.isUseCallExpression) { this._connection.console.log('VISITOR found database names completion'); - return this._cachedDatabases; + return this._databaseCompletionItems; + } + } + + /** + * Parse code from a playground to identify + * where the cursor is and suggests only suitable completion items. + */ + async provideCompletionItems( + textFromEditor: string, + position: { line: number; character: number } + ): Promise { + this._connection.console.log( + `LS current symbol position: ${util.inspect(position)}` + ); + + const state = this._visitor.parseASTWithPlaceholder( + textFromEditor, + position + ); + this._connection.console.log( + `VISITOR completion state: ${util.inspect(state)}` + ); + + await this._getCompletionValuesAndUpdateCache( + textFromEditor, + position, + state.databaseName, + state.collectionName + ); + + const completionOptions = [ + this._provideStageCompletionItems.bind(this, state), + this._provideQueryOperatorCompletionItems.bind(this, state), + this._provideAggregationOperatorCompletionItems.bind(this, state), + this._provideBSONCompletionItems.bind(this, state), + this._provideFieldReferenceCompletionItems.bind(this, state), + this._provideCollectionSymbolCompletionItems.bind(this, state), + this._provideFindCursorCompletionItems.bind(this, state), + this._provideAggregationCursorCompletionItems.bind(this, state), + this._provideGlobalSymbolCompletionItems.bind(this, state), + this._provideDbSymbolCompletionItems.bind(this, state), + this._provideCollectionNameCompletionItems.bind(this, state), + this._provideDbNameCompletionItems.bind(this, state), + ]; + + for (const func of completionOptions) { + const result = func(); + if (result !== null && result !== undefined) { + return result; + } } this._connection.console.log('VISITOR no completion'); @@ -660,15 +872,9 @@ export default class MongoDBService { /** * Convert schema field names to Completion Items and cache them. */ - _cacheFieldCompletionItems(namespace: string, schemaFields: string[]): void { + _cacheFields(namespace: string, fields: string[]): void { if (namespace) { - const fields = schemaFields.map((field) => ({ - label: field, - kind: CompletionItemKind.Field, - preselect: true, - })); - - this._cachedFields[namespace] = fields ? fields : []; + this._fields[namespace] = fields ? fields : []; } } @@ -676,8 +882,8 @@ export default class MongoDBService { * Convert database names to Completion Items and cache them. */ _cacheDatabaseCompletionItems(databases: Document[]): void { - this._cachedDatabases = databases.map((db) => ({ - label: (db as { name: string }).name, + this._databaseCompletionItems = databases.map((db) => ({ + label: db.name, kind: CompletionItemKind.Field, preselect: true, })); @@ -692,7 +898,7 @@ export default class MongoDBService { database: string, collections: Document[] ): void { - this._cachedCollections[database] = collections.map((item) => { + this._collectionCompletionItems[database] = collections.map((item) => { if (this._isValidPropertyName(item.name)) { return { label: item.name, @@ -734,15 +940,15 @@ export default class MongoDBService { } _clearCachedFields(): void { - this._cachedFields = {}; + this._fields = {}; } _clearCachedDatabases(): void { - this._cachedDatabases = []; + this._databaseCompletionItems = []; } _clearCachedCollections(): void { - this._cachedCollections = {}; + this._collectionCompletionItems = {}; } async _clearCurrentConnection(): Promise { diff --git a/src/language/server.ts b/src/language/server.ts index d4437cf6a..b21c9be51 100644 --- a/src/language/server.ts +++ b/src/language/server.ts @@ -179,7 +179,7 @@ connection.onRequest(ServerCommands.DISCONNECT_TO_SERVICE_PROVIDER, () => { connection.onRequest( ServerCommands.UPDATE_CURRENT_SESSION_FIELDS, ({ namespace, schemaFields }) => { - return mongoDBService._cacheFieldCompletionItems(namespace, schemaFields); + return mongoDBService._cacheFields(namespace, schemaFields); } ); diff --git a/src/language/visitor.ts b/src/language/visitor.ts index 7a228ddd0..fb2c6727c 100644 --- a/src/language/visitor.ts +++ b/src/language/visitor.ts @@ -1,7 +1,6 @@ import type * as babel from '@babel/core'; import * as parser from '@babel/parser'; import traverse from '@babel/traverse'; -import { RemoteConsole } from 'vscode-languageserver/node'; import * as util from 'util'; const PLACEHOLDER = 'TRIGGER_CHARACTER'; @@ -16,16 +15,25 @@ export interface VisitorTextAndSelection { selection: VisitorSelection; } +type ObjectKey = + | babel.types.ObjectProperty + | babel.types.SpreadElement + | babel.types.ObjectMethod; + export interface CompletionState { databaseName: string | null; collectionName: string | null; - isObject: boolean; - isArray: boolean; + isObjectSelection: boolean; + isArraySelection: boolean; isObjectKey: boolean; - isShellMethod: boolean; + isIdentifierObjectValue: boolean; + isTextObjectValue: boolean; + isStage: boolean; + stageOperator: string | null; + isCollectionSymbol: boolean; isUseCallExpression: boolean; - isTopLevelIdentifier: boolean; - isDbCallExpression: boolean; + isGlobalSymbol: boolean; + isDbSymbol: boolean; isCollectionName: boolean; isAggregationCursor: boolean; isFindCursor: boolean; @@ -34,68 +42,70 @@ export interface CompletionState { export class Visitor { _state: CompletionState; _selection: VisitorSelection; - _console: RemoteConsole; - constructor(console: RemoteConsole) { + constructor() { this._state = this._getDefaultNodesValues(); this._selection = { start: { line: 0, character: 0 }, end: { line: 0, character: 0 }, }; - this._console = console; } - _visitCallExpression(node: babel.types.Node): void { - if (node.type !== 'CallExpression') { + _visitCallExpression(path: babel.NodePath): void { + if (path.node.type !== 'CallExpression') { return; } - this._checkIsBSONSelection(node); - this._checkIsUseCall(node); - this._checkIsCollectionName(node); - this._checkHasDatabaseName(node); + this._checkIsBSONSelection(path.node); + this._checkIsUseCall(path.node); + this._checkIsCollectionNameAsCallExpression(path.node); + this._checkHasDatabaseName(path.node); } - _visitMemberExpression(node: babel.types.Node): void { - if (node.type !== 'MemberExpression') { + _visitMemberExpression(path: babel.NodePath): void { + if (path.node.type !== 'MemberExpression') { return; } - this._checkHasAggregationCall(node); - this._checkHasFindCall(node); - this._checkIsShellMethod(node); - this._checkIsCollectionName(node); - this._checkHasCollectionName(node); + this._checkHasAggregationCall(path.node); + this._checkHasFindCall(path.node); + this._checkIsCollectionSymbol(path.node); + this._checkIsCollectionNameAsMemberExpression(path.node); + this._checkHasCollectionName(path.node); } - _visitExpressionStatement(node: babel.types.Node): void { - if (node.type === 'ExpressionStatement') { - this._checkIsTopLevelIdentifier(node); - this._checkIsDbCall(node); + _visitExpressionStatement(path: babel.NodePath): void { + if (path.node.type === 'ExpressionStatement') { + this._checkIsGlobalSymbol(path.node); + this._checkIsDbSymbol(path.node); } } - _visitObjectExpression(node: babel.types.Node): void { - if (node.type === 'ObjectExpression') { - this._checkIsObjectKey(node); + _visitObjectExpression(path: babel.NodePath): void { + if (path.node.type === 'ObjectExpression') { + this._checkIsObjectKey(path.node); + this._checkIsIdentifierObjectValue(path.node); + this._checkIsTextObjectValue(path.node); } } - _visitArrayExpression(node: babel.types.Node): void { - if (node.type === 'ArrayExpression') { - this._checkIsBSONSelection(node); + _visitArrayExpression(path: babel.NodePath): void { + if (path.node.type === 'ArrayExpression') { + this._checkIsBSONSelection(path.node); + this._checkIsStage(path.node); + this._checkIsStageOperator(path); } } - _visitVariableDeclarator(node: babel.types.Node): void { - if (node.type === 'VariableDeclarator') { - this._checkIsBSONSelection(node); + _visitVariableDeclarator(path: babel.NodePath): void { + if (path.node.type === 'VariableDeclarator') { + this._checkIsBSONSelection(path.node); } } - _visitObjectProperty(node: babel.types.Node): void { - if (node.type === 'ObjectProperty') { - this._checkIsBSONSelection(node); + _visitObjectProperty(path: babel.NodePath): void { + if (path.node.type === 'ObjectProperty') { + this._checkIsBSONSelection(path.node); } } @@ -141,30 +151,29 @@ export class Visitor { textFromEditor, selection, }: VisitorTextAndSelection): CompletionState { - let ast; - this._state = this._getDefaultNodesValues(); this._selection = selection; + let ast; try { ast = parser.parse(textFromEditor, { // Parse in strict mode and allow module declarations sourceType: 'module', }); } catch (error) { - this._console.error(`parseAST error: ${util.inspect(error)}`); + console.error(`parseAST error: ${util.inspect(error)}`); return this._state; } traverse(ast, { enter: (path: babel.NodePath) => { - this._visitCallExpression(path.node); - this._visitMemberExpression(path.node); - this._visitExpressionStatement(path.node); - this._visitObjectExpression(path.node); - this._visitArrayExpression(path.node); - this._visitVariableDeclarator(path.node); - this._visitObjectProperty(path.node); + this._visitCallExpression(path); + this._visitMemberExpression(path); + this._visitExpressionStatement(path); + this._visitObjectExpression(path); + this._visitArrayExpression(path); + this._visitVariableDeclarator(path); + this._visitObjectProperty(path); }, }); @@ -175,13 +184,17 @@ export class Visitor { return { databaseName: null, collectionName: null, - isObject: false, - isArray: false, + isObjectSelection: false, + isArraySelection: false, isObjectKey: false, - isShellMethod: false, + isIdentifierObjectValue: false, + isTextObjectValue: false, + isStage: false, + stageOperator: null, + isCollectionSymbol: false, isUseCallExpression: false, - isTopLevelIdentifier: false, - isDbCallExpression: false, + isGlobalSymbol: false, + isDbSymbol: false, isCollectionName: false, isAggregationCursor: false, isFindCursor: false, @@ -192,7 +205,6 @@ export class Visitor { if ( node.callee.type === 'Identifier' && node.callee.name === 'use' && - node.arguments && node.arguments.length === 1 && node.arguments[0].type === 'StringLiteral' && node.arguments[0].value.includes(PLACEHOLDER) @@ -206,7 +218,6 @@ export class Visitor { node.arguments && node.arguments.length === 1 && node.arguments[0].type === 'TemplateLiteral' && - node.arguments[0].quasis && node.arguments[0].quasis.length === 1 && node.arguments[0].quasis[0].value?.raw && node.arguments[0].quasis[0].value?.raw.includes(PLACEHOLDER) @@ -220,40 +231,115 @@ export class Visitor { this._checkIsUseCallAsTemplate(node); } - _checkIsTopLevelIdentifier(node: babel.types.ExpressionStatement): void { + _checkIsGlobalSymbol(node: babel.types.ExpressionStatement): void { if ( node.expression.type === 'Identifier' && node.expression.name.includes('TRIGGER_CHARACTER') ) { - this._state.isTopLevelIdentifier = true; + this._state.isGlobalSymbol = true; } } - _checkIsDbCall(node: babel.types.ExpressionStatement): void { + _checkIsDbSymbol(node: babel.types.ExpressionStatement): void { if ( node.expression.type === 'MemberExpression' && node.expression.object.type === 'Identifier' && node.expression.object.name === 'db' ) { - this._state.isDbCallExpression = true; + this._state.isDbSymbol = true; } } _checkIsObjectKey(node: babel.types.ObjectExpression): void { - this._state.isObjectKey = !!node.properties.find( - ( - item: - | babel.types.ObjectProperty - | babel.types.SpreadElement - | babel.types.ObjectMethod - ) => + node.properties.find((item: ObjectKey) => { + if ( item.type === 'ObjectProperty' && item.key.type === 'Identifier' && - !!(item.key.name && item.key.name.includes(PLACEHOLDER)) - ); + item.key.name.includes(PLACEHOLDER) + ) { + this._state.isObjectKey = true; + } + }); + } + + _checkIsIdentifierObjectValue(node: babel.types.ObjectExpression): void { + node.properties.find((item: ObjectKey) => { + if ( + item.type === 'ObjectProperty' && + item.value.type === 'Identifier' && + item.value.name.includes(PLACEHOLDER) + ) { + this._state.isIdentifierObjectValue = true; + } + }); + } + + _checkIsTextObjectValue(node: babel.types.ObjectExpression): void { + node.properties.find((item: ObjectKey) => { + if ( + (item.type === 'ObjectProperty' && + item.value.type === 'StringLiteral' && + item.value.value.includes(PLACEHOLDER)) || + (item.type === 'ObjectProperty' && + item.value.type === 'TemplateLiteral' && + item.value?.quasis.length === 1 && + item.value.quasis[0].value?.raw.includes(PLACEHOLDER)) + ) { + this._state.isTextObjectValue = true; + } + }); + } + + _checkIsStage(node: babel.types.ArrayExpression): void { + if (node.elements) { + node.elements.forEach((item) => { + if (item?.type === 'ObjectExpression') { + item.properties.find((item: ObjectKey) => { + if ( + item.type === 'ObjectProperty' && + item.key.type === 'Identifier' && + item.key.name.includes(PLACEHOLDER) + ) { + this._state.isStage = true; + } + }); + } + }); + } + } + + _checkIsStageOperator(path: babel.NodePath): void { + if (path.node.type === 'ArrayExpression' && path.node.elements) { + path.node.elements.forEach((item) => { + if (item?.type === 'ObjectExpression') { + item.properties.find((item: ObjectKey) => { + if ( + item.type === 'ObjectProperty' && + item.key.type === 'Identifier' && + item.value.type === 'ObjectExpression' + ) { + const name = item.key.name; + path.scope.traverse(item, { + enter: (path: babel.NodePath) => { + if ( + path.node.type === 'ObjectProperty' && + path.node.key.type === 'Identifier' && + path.node.key.name.includes(PLACEHOLDER) + ) { + this._state.stageOperator = name; + } + }, + }); + } + }); + } + }); + } } - _isParentAroundSelection(node: babel.types.Node): boolean { + _isParentAroundSelection( + node: babel.types.ArrayExpression | babel.types.CallExpression + ): boolean { if ( node.loc?.start?.line && (node.loc.start.line - 1 < this._selection.start?.line || @@ -298,7 +384,9 @@ export class Visitor { return false; } - _isWithinSelection(node: babel.types.Node): boolean { + _isWithinSelection( + node: babel.types.ArrayExpression | babel.types.ObjectExpression + ): boolean { if ( node.loc?.start?.line && node.loc.start.line - 1 === this._selection.start?.line && @@ -317,20 +405,19 @@ export class Visitor { _checkIsArrayWithinSelection(node: babel.types.Node): void { if (node.type === 'ArrayExpression' && this._isWithinSelection(node)) { - this._state.isArray = true; + this._state.isArraySelection = true; } } _checkIsObjectWithinSelection(node: babel.types.Node): void { if (node.type === 'ObjectExpression' && this._isWithinSelection(node)) { - this._state.isObject = true; + this._state.isObjectSelection = true; } } _checkIsBSONSelectionInArray(node: babel.types.Node): void { if ( node.type === 'ArrayExpression' && - node.elements && this._isParentAroundSelection(node) ) { node.elements.forEach((item) => { @@ -343,11 +430,7 @@ export class Visitor { } _checkIsBSONSelectionInFunction(node: babel.types.Node): void { - if ( - node.type === 'CallExpression' && - node.arguments && - this._isParentAroundSelection(node) - ) { + if (node.type === 'CallExpression' && this._isParentAroundSelection(node)) { node.arguments.forEach((item) => { if (item) { this._checkIsObjectWithinSelection(item); @@ -371,7 +454,6 @@ export class Visitor { _checkIsBSONSelectionInObject(node: babel.types.Node) { if ( node.type === 'ObjectProperty' && - node.value && this._isObjectPropBeforeSelection(node) ) { this._checkIsObjectWithinSelection(node.value); @@ -386,32 +468,54 @@ export class Visitor { this._checkIsBSONSelectionInObject(node); } - _checkIsCollectionNameAsMemberExpression(node: babel.types.Node): void { + _checkIsCollectionNameAsMemberExpression( + node: babel.types.MemberExpression + ): void { if ( - node.type === 'MemberExpression' && node.object.type === 'Identifier' && node.object.name === 'db' && - node.property.type === 'Identifier' && - node.property.name.includes(PLACEHOLDER) + ((node.property.type === 'Identifier' && + node.property.name.includes(PLACEHOLDER)) || + (node.property.type === 'StringLiteral' && + node.property.value.includes(PLACEHOLDER))) + ) { + this._state.isCollectionName = true; + } + } + + _checkGetCollectionAsSimpleString(node: babel.types.CallExpression): void { + if ( + node.arguments[0].type === 'StringLiteral' && + node.arguments[0].value.includes(PLACEHOLDER) ) { this._state.isCollectionName = true; } } - _checkIsCollectionNameAsCallExpression(node: babel.types.Node): void { + _checkGetCollectionAsTemplate(node: babel.types.CallExpression): void { if ( - node.type === 'CallExpression' && - node.callee.type === 'MemberExpression' + node.arguments[0].type === 'TemplateLiteral' && + node.arguments[0].quasis.length === 1 && + node.arguments[0].quasis[0].value.raw.includes(PLACEHOLDER) ) { - this._checkIsCollectionName(node.callee); + this._state.isCollectionName = true; } } - _checkIsCollectionName( - node: babel.types.CallExpression | babel.types.MemberExpression + _checkIsCollectionNameAsCallExpression( + node: babel.types.CallExpression ): void { - this._checkIsCollectionNameAsMemberExpression(node); - this._checkIsCollectionNameAsCallExpression(node); + if ( + node.callee.type === 'MemberExpression' && + node.callee.object.type === 'Identifier' && + node.callee.object.name === 'db' && + node.callee.property.type === 'Identifier' && + node.callee.property.name === 'getCollection' && + node.arguments.length === 1 + ) { + this._checkGetCollectionAsSimpleString(node); + this._checkGetCollectionAsTemplate(node); + } } _checkHasAggregationCall(node: babel.types.MemberExpression): void { @@ -446,7 +550,6 @@ export class Visitor { if ( node.callee.type === 'Identifier' && node.callee.name === 'use' && - node.arguments && node.arguments.length === 1 && node.arguments[0].type === 'StringLiteral' && node.loc && @@ -464,13 +567,15 @@ export class Visitor { node.object.object.type === 'Identifier' && node.object.object.name === 'db' ) { - this._state.collectionName = ( - node.object.property as babel.types.Identifier - ).name; + if (node.object.property.type === 'Identifier') { + this._state.collectionName = node.object.property.name; + } else if (node.object.property.type === 'StringLiteral') { + this._state.collectionName = node.object.property.value; + } } } - _checkIsShellMethod(node: babel.types.MemberExpression): void { + _checkIsCollectionSymbol(node: babel.types.MemberExpression): void { if ( node.object.type === 'MemberExpression' && node.object.object.type === 'Identifier' && @@ -478,7 +583,7 @@ export class Visitor { node.property.type === 'Identifier' && node.property.name.includes(PLACEHOLDER) ) { - this._state.isShellMethod = true; + this._state.isCollectionSymbol = true; } } } diff --git a/src/test/suite/language/mongoDBService.test.ts b/src/test/suite/language/mongoDBService.test.ts index c50afe661..e31e44929 100644 --- a/src/test/suite/language/mongoDBService.test.ts +++ b/src/test/suite/language/mongoDBService.test.ts @@ -3,6 +3,7 @@ import { before } from 'mocha'; import { CancellationTokenSource, CompletionItemKind, + InsertTextFormat, DiagnosticSeverity, } from 'vscode-languageclient/node'; import type { CompletionItem } from 'vscode-languageclient/node'; @@ -131,14 +132,11 @@ suite('MongoDBService Test Suite', () => { 'db.test.', { line: 0, character: 8 } ); - const findCompletion = result.find( + const completion = result.find( (item: CompletionItem) => item.label === 'find' ); - expect(findCompletion).to.have.property( - 'kind', - CompletionItemKind.Method - ); + expect(completion).to.have.property('kind', CompletionItemKind.Method); }); test('provide shell collection methods completion if function scope', async () => { @@ -146,14 +144,11 @@ suite('MongoDBService Test Suite', () => { 'const name = () => { db.test. }', { line: 0, character: 29 } ); - const findCompletion = result.find( + const completion = result.find( (item: CompletionItem) => item.label === 'find' ); - expect(findCompletion).to.have.property( - 'kind', - CompletionItemKind.Method - ); + expect(completion).to.have.property('kind', CompletionItemKind.Method); }); test('provide shell collection methods completion if collection name is computed property', async () => { @@ -161,14 +156,11 @@ suite('MongoDBService Test Suite', () => { ['use("test");', 'db["test"].'].join('\n'), { line: 1, character: 11 } ); - const findCompletion = result.find( + const completion = result.find( (item: CompletionItem) => item.label === 'find' ); - expect(findCompletion).to.have.property( - 'kind', - CompletionItemKind.Method - ); + expect(completion).to.have.property('kind', CompletionItemKind.Method); }); test('provide shell collection methods completion if single quotes', async () => { @@ -176,14 +168,11 @@ suite('MongoDBService Test Suite', () => { ["use('test');", "db['test']."].join('\n'), { line: 1, character: 11 } ); - const findCompletion = result.find( + const completion = result.find( (item: CompletionItem) => item.label === 'find' ); - expect(findCompletion).to.have.property( - 'kind', - CompletionItemKind.Method - ); + expect(completion).to.have.property('kind', CompletionItemKind.Method); }); test('provide shell db methods completion with dot the same line', async () => { @@ -191,14 +180,11 @@ suite('MongoDBService Test Suite', () => { line: 0, character: 3, }); - const findCompletion = result.find( + const completion = result.find( (item: CompletionItem) => item.label === 'getCollectionNames' ); - expect(findCompletion).to.have.property( - 'kind', - CompletionItemKind.Method - ); + expect(completion).to.have.property('kind', CompletionItemKind.Method); }); test('provide shell db methods completion with dot next line', async () => { @@ -209,14 +195,11 @@ suite('MongoDBService Test Suite', () => { character: 1, } ); - const findCompletion = result.find( + const completion = result.find( (item: CompletionItem) => item.label === 'getCollectionNames' ); - expect(findCompletion).to.have.property( - 'kind', - CompletionItemKind.Method - ); + expect(completion).to.have.property('kind', CompletionItemKind.Method); }); test('provide shell db methods completion with dot after space', async () => { @@ -224,14 +207,11 @@ suite('MongoDBService Test Suite', () => { line: 0, character: 4, }); - const findCompletion = result.find( + const completion = result.find( (item: CompletionItem) => item.label === 'getCollectionNames' ); - expect(findCompletion).to.have.property( - 'kind', - CompletionItemKind.Method - ); + expect(completion).to.have.property('kind', CompletionItemKind.Method); }); test('provide shell aggregation cursor methods completion', async () => { @@ -242,12 +222,12 @@ suite('MongoDBService Test Suite', () => { const aggCompletion = result.find( (item: CompletionItem) => item.label === 'toArray' ); - const findCompletion = result.find( + const completion = result.find( (item: CompletionItem) => item.label === 'allowPartialResults' ); expect(aggCompletion).to.have.property('kind', CompletionItemKind.Method); - expect(findCompletion).to.be.undefined; + expect(completion).to.be.undefined; }); test('provide shell find cursor methods completion without args', async () => { @@ -255,14 +235,11 @@ suite('MongoDBService Test Suite', () => { 'db.collection.find().', { line: 0, character: 21 } ); - const findCompletion = result.find( + const completion = result.find( (item: CompletionItem) => item.label === 'allowPartialResults' ); - expect(findCompletion).to.have.property( - 'kind', - CompletionItemKind.Method - ); + expect(completion).to.have.property('kind', CompletionItemKind.Method); }); test('provide shell find cursor methods completion with args at the same line', async () => { @@ -272,14 +249,11 @@ suite('MongoDBService Test Suite', () => { ), { line: 2, character: 36 } ); - const findCompletion = result.find( + const completion = result.find( (item: CompletionItem) => item.label === 'allowPartialResults' ); - expect(findCompletion).to.have.property( - 'kind', - CompletionItemKind.Method - ); + expect(completion).to.have.property('kind', CompletionItemKind.Method); }); test('provide shell find cursor methods completion with args next line', async () => { @@ -293,52 +267,57 @@ suite('MongoDBService Test Suite', () => { ].join('\n'), { line: 4, character: 3 } ); - const findCompletion = result.find( + const completion = result.find( (item: CompletionItem) => item.label === 'allowPartialResults' ); - expect(findCompletion).to.have.property( - 'kind', - CompletionItemKind.Method - ); + expect(completion).to.have.property('kind', CompletionItemKind.Method); }); - test('provide fields completion if has db, connection and is object key', async () => { - testMongoDBService._cacheFieldCompletionItems('test.collection', [ - 'JavaScript', - ]); + test('provide fields completion in find in dot notation when has db', async () => { + testMongoDBService._cacheFields('test.collection', ['JavaScript']); const result = await testMongoDBService.provideCompletionItems( 'use("test"); db.collection.find({ j});', { line: 0, character: 35 } ); - const findCompletion = result.find( + const completion = result.find( (item: CompletionItem) => item.label === 'JavaScript' ); - expect(findCompletion).to.have.property('kind', CompletionItemKind.Field); + expect(completion).to.have.property('kind', CompletionItemKind.Field); }); - test('provide fields completion if text not formatted', async () => { - testMongoDBService._cacheFieldCompletionItems('test.collection', [ - 'JavaScript', - ]); + test('provide fields completion in find in bracket notation when has db', async () => { + testMongoDBService._cacheFields('test.collection', ['JavaScript']); + + const result = await testMongoDBService.provideCompletionItems( + 'use("test"); db["collection"].find({ j});', + { line: 0, character: 38 } + ); + const completion = result.find( + (item: CompletionItem) => item.label === 'JavaScript' + ); + + expect(completion).to.have.property('kind', CompletionItemKind.Field); + }); + + test('provide fields completion in find if text not formatted', async () => { + testMongoDBService._cacheFields('test.collection', ['JavaScript']); const result = await testMongoDBService.provideCompletionItems( 'use("test");db.collection.find({j});', { line: 0, character: 33 } ); - const findCompletion = result.find( + const completion = result.find( (item: CompletionItem) => item.label === 'JavaScript' ); - expect(findCompletion).to.have.property('kind', CompletionItemKind.Field); + expect(completion).to.have.property('kind', CompletionItemKind.Field); }); - test('provide fields completion if functions are multi-lined', async () => { - testMongoDBService._cacheFieldCompletionItems('test.collection', [ - 'JavaScript', - ]); + test('provide fields completion in find if functions are multi-lined', async () => { + testMongoDBService._cacheFields('test.collection', ['JavaScript']); const result = await testMongoDBService.provideCompletionItems( [ @@ -349,52 +328,44 @@ suite('MongoDBService Test Suite', () => { ].join('\n'), { line: 2, character: 24 } ); - const findCompletion = result.find( + const completion = result.find( (item: CompletionItem) => item.label === 'JavaScript' ); - expect(findCompletion).to.have.property('kind', CompletionItemKind.Field); + expect(completion).to.have.property('kind', CompletionItemKind.Field); }); - test('provide fields completion if object is multi-lined', async () => { - testMongoDBService._cacheFieldCompletionItems('test.collection', [ - 'JavaScript', - ]); + test('provide fields completion in find if object is multi-lined', async () => { + testMongoDBService._cacheFields('test.collection', ['JavaScript']); const result = await testMongoDBService.provideCompletionItems( ['use("test");', '', 'db.collection.find({', ' j', '});'].join('\n'), { line: 3, character: 3 } ); - const findCompletion = result.find( + const completion = result.find( (item: CompletionItem) => item.label === 'JavaScript' ); - expect(findCompletion).to.have.property('kind', CompletionItemKind.Field); + expect(completion).to.have.property('kind', CompletionItemKind.Field); }); - test('provide fields completion if object key is surrounded by spaces', async () => { - testMongoDBService._cacheFieldCompletionItems('test.collection', [ - 'JavaScript', - ]); + test('provide fields completion in find if a key is surrounded by spaces', async () => { + testMongoDBService._cacheFields('test.collection', ['JavaScript']); const result = await testMongoDBService.provideCompletionItems( 'use("test"); db.collection.find({ j });', { line: 0, character: 35 } ); - const findCompletion = result.find( + const completion = result.find( (item: CompletionItem) => item.label === 'JavaScript' ); - expect(findCompletion).to.have.property('kind', CompletionItemKind.Field); + expect(completion).to.have.property('kind', CompletionItemKind.Field); }); - test('provide fields completion for proper db', async () => { - testMongoDBService._cacheFieldCompletionItems('test.collection', [ - 'JavaScript', - ]); - testMongoDBService._cacheFieldCompletionItems('second.collection', [ - 'TypeScript', - ]); + test('provide fields completion in find for a proper db', async () => { + testMongoDBService._cacheFields('test.collection', ['JavaScript']); + testMongoDBService._cacheFields('second.collection', ['TypeScript']); const result = await testMongoDBService.provideCompletionItems( 'use("first"); use("second"); db.collection.find({ t});', @@ -412,43 +383,25 @@ suite('MongoDBService Test Suite', () => { expect(tsCompletion).to.have.property('kind', CompletionItemKind.Field); }); - test('provide fields completion if function scope', async () => { - testMongoDBService._cacheFieldCompletionItems('test.collection', [ - 'JavaScript', - ]); + test('provide fields completion in find inside function scope', async () => { + testMongoDBService._cacheFields('test.collection', ['JavaScript']); const result = await testMongoDBService.provideCompletionItems( 'use("test"); const name = () => { db.collection.find({ j}); }', { line: 0, character: 56 } ); - const findCompletion = result.find( - (item: CompletionItem) => item.label === 'JavaScript' - ); - - expect(findCompletion).to.have.property('kind', CompletionItemKind.Field); - }); - - test('provide fields completion if snippets mode', async () => { - testMongoDBService._cacheFieldCompletionItems('test.collection', [ - 'JavaScript', - ]); - - const result = await testMongoDBService.provideCompletionItems( - 'use("test"); db.collection.aggregate([ { $match: { j} } ])', - { line: 0, character: 52 } - ); - const findCompletion = result.find( + const completion = result.find( (item: CompletionItem) => item.label === 'JavaScript' ); - expect(findCompletion).to.have.property('kind', CompletionItemKind.Field); + expect(completion).to.have.property('kind', CompletionItemKind.Field); }); - test('provide fields completion for proper collection', async () => { - testMongoDBService._cacheFieldCompletionItems('test.firstCollection', [ + test('provide fields completion in find for a proper collection', async () => { + testMongoDBService._cacheFields('test.firstCollection', [ 'JavaScript First', ]); - testMongoDBService._cacheFieldCompletionItems('test.secondCollection', [ + testMongoDBService._cacheFields('test.secondCollection', [ 'JavaScript Second', ]); @@ -456,43 +409,381 @@ suite('MongoDBService Test Suite', () => { 'use("test"); db.firstCollection.find({ j});', { line: 0, character: 40 } ); - const findCompletion = result.find( + const completion = result.find( (item: CompletionItem) => item.label === 'JavaScript First' ); - expect(findCompletion).to.have.property('kind', CompletionItemKind.Field); + expect(completion).to.have.property('kind', CompletionItemKind.Field); }); - test('do not provide fields completion if has not db', async () => { - testMongoDBService._cacheFieldCompletionItems('test.collection', [ - 'JavaScript', - ]); + test('do not provide fields completion in find if db not found', async () => { + testMongoDBService._cacheFields('test.collection', ['JavaScript']); const result = await testMongoDBService.provideCompletionItems( 'db.collection.find({ j});', { line: 0, character: 22 } ); - const findCompletion = result.find( + const completion = result.find( (item: CompletionItem) => item.label === 'JavaScript' ); - expect(findCompletion).to.be.undefined; + expect(completion).to.be.undefined; }); - test('do not provide fields completion if not object id', async () => { - testMongoDBService._cacheFieldCompletionItems('test.collection', [ - 'JavaScript', - ]); + test('do not provide fields completionin find outside object property', async () => { + testMongoDBService._cacheFields('test.collection', ['JavaScript']); const result = await testMongoDBService.provideCompletionItems( - 'use("test"); db.collection(j);', + 'use("test"); db.collection.find(j);', { line: 0, character: 28 } ); - const findCompletion = result.find( + const completion = result.find( + (item: CompletionItem) => item.label === 'JavaScript' + ); + + expect(completion).to.be.undefined; + }); + + test('provide fields completion in aggregate inside the $match stage', async () => { + testMongoDBService._cacheFields('test.collection', ['JavaScript']); + + const result = await testMongoDBService.provideCompletionItems( + 'use("test"); db.collection.aggregate([ { $match: { j} } ])', + { line: 0, character: 52 } + ); + const completion = result.find( (item: CompletionItem) => item.label === 'JavaScript' ); - expect(findCompletion).to.be.undefined; + expect(completion).to.have.property('kind', CompletionItemKind.Field); + }); + + test('provide stages completion in aggregata when has db', async () => { + const result = await testMongoDBService.provideCompletionItems( + 'use("test"); db.collection.aggregate([{ $m}]);', + { line: 0, character: 42 } + ); + const completion = result.find( + (item: CompletionItem) => item.label === '$match' + ); + + expect(completion).to.have.property('kind', CompletionItemKind.Keyword); + expect(completion).to.have.property('insertText'); + expect(completion).to.have.property( + 'insertTextFormat', + InsertTextFormat.Snippet + ); + expect(completion).to.have.property('detail'); + }); + + test('provide stages completion if db not found', async () => { + const result = await testMongoDBService.provideCompletionItems( + 'db.collection.aggregate([{ $m}]);', + { line: 0, character: 29 } + ); + const completion = result.find( + (item: CompletionItem) => item.label === '$match' + ); + + expect(completion).to.have.property('kind', CompletionItemKind.Keyword); + }); + + test('provide stages completion if object is multi-lined', async () => { + const result = await testMongoDBService.provideCompletionItems( + ['use("test");', '', 'db.aggregate.find([{', ' $c', '}]);'].join('\n'), + { line: 3, character: 4 } + ); + const completion = result.find( + (item: CompletionItem) => item.label === '$count' + ); + + expect(completion).to.have.property('kind', CompletionItemKind.Keyword); + }); + + test('provide query completion for the $match stage', async () => { + const result = await testMongoDBService.provideCompletionItems( + 'db.collection.aggregate([{ $match: { $e} }]);', + { line: 0, character: 39 } + ); + const completion = result.find( + (item: CompletionItem) => item.label === '$expr' + ); + + expect(completion).to.have.property('kind', CompletionItemKind.Keyword); + }); + + test('provide query completion in find', async () => { + const result = await testMongoDBService.provideCompletionItems( + 'db.collection.find({ $e});', + { line: 0, character: 23 } + ); + const completion = result.find( + (item: CompletionItem) => item.label === '$expr' + ); + + expect(completion).to.have.property('kind', CompletionItemKind.Keyword); + }); + + test('do not provide query completion for other than $match stages', async () => { + const result = await testMongoDBService.provideCompletionItems( + 'db.collection.aggregate([{ $merge: { $e} }]);', + { line: 0, character: 39 } + ); + const completion = result.find( + (item: CompletionItem) => item.label === '$expr' + ); + + expect(completion).to.be.undefined; + }); + + test('provide bson completion in find', async () => { + const result = await testMongoDBService.provideCompletionItems( + 'db.collection.find({ _id: O});', + { line: 0, character: 27 } + ); + const completion = result.find( + (item: CompletionItem) => item.label === 'ObjectId' + ); + + expect(completion).to.have.property( + 'kind', + CompletionItemKind.Constructor + ); + expect(completion).to.have.property('insertText'); + expect(completion).to.have.property( + 'insertTextFormat', + InsertTextFormat.Snippet + ); + expect(completion).to.have.property('detail'); + }); + + test('provide bson completion in aggregate', async () => { + const result = await testMongoDBService.provideCompletionItems( + 'db.collection.aggregate([{ $match: { _id: O }}]);', + { line: 0, character: 42 } + ); + const completion = result.find( + (item: CompletionItem) => item.label === 'ObjectId' + ); + + expect(completion).to.have.property( + 'kind', + CompletionItemKind.Constructor + ); + expect(completion).to.have.property('insertText'); + expect(completion).to.have.property( + 'insertTextFormat', + InsertTextFormat.Snippet + ); + expect(completion).to.have.property('detail'); + }); + + test('provide field reference completion in find when has db', async () => { + testMongoDBService._cacheFields('test.collection', ['price']); + + const result = await testMongoDBService.provideCompletionItems( + "use('test'); db.collection.find({ $expr: { $gt: [{ $getField: { $literal: '$p' } }, 200] } });", + { line: 0, character: 77 } + ); + const completion = result.find( + (item: CompletionItem) => item.label === '$price' + ); + + expect(completion).to.have.property('kind', CompletionItemKind.Reference); + }); + + test('do not provide field reference completion in find if db not found', async () => { + const result = await testMongoDBService.provideCompletionItems( + "db.collection.find({ $expr: { $gt: [{ $getField: { $literal: '$p' } }, 200] } });", + { line: 0, character: 64 } + ); + const completion = result.find( + (item: CompletionItem) => item.label === '$price' + ); + + expect(completion).to.be.undefined; + }); + + test('provide field reference completion in aggregate when has db', async () => { + testMongoDBService._cacheFields('test.collection', ['price']); + + const result = await testMongoDBService.provideCompletionItems( + "use('test'); db.collection.aggregate({ $match: { $expr: { $gt: [{ $getField: { $literal: '$p' } }, 200] } } });", + { line: 0, character: 92 } + ); + const completion = result.find( + (item: CompletionItem) => item.label === '$price' + ); + + expect(completion).to.have.property('kind', CompletionItemKind.Reference); + }); + + test('provide aggregation expression completion for other than $match stages', async () => { + const result = await testMongoDBService.provideCompletionItems( + 'db.collection.aggregate([{ $project: { yearMonthDayUTC: { $d } } }]);', + { line: 0, character: 59 } + ); + const completion = result.find( + (item: CompletionItem) => item.label === '$dateToString' + ); + + expect(completion).to.have.property('kind', CompletionItemKind.Keyword); + }); + + test('do not provide aggregation expression completion for the $match stage', async () => { + const result = await testMongoDBService.provideCompletionItems( + 'db.collection.aggregate({ $match: { $d } });', + { line: 0, character: 38 } + ); + const completion = result.find( + (item: CompletionItem) => item.label === '$dateToString' + ); + + expect(completion).to.be.undefined; + }); + + test('provide aggregation conversion completion for other than $match stages', async () => { + const result = await testMongoDBService.provideCompletionItems( + 'db.collection.aggregate([{ $project: { result: {$c} } }]);', + { line: 0, character: 50 } + ); + const completion = result.find( + (item: CompletionItem) => item.label === '$convert' + ); + + expect(completion).to.have.property('kind', CompletionItemKind.Keyword); + }); + + test('do not provide aggregation conversion completion for the $match stage', async () => { + const result = await testMongoDBService.provideCompletionItems( + 'db.collection.aggregate([{ $match: { $c } }]);', + { line: 0, character: 39 } + ); + const completion = result.find( + (item: CompletionItem) => item.label === '$convert' + ); + + expect(completion).to.be.undefined; + }); + + test('provide aggregation accumulator completion for the $project stage', async () => { + const result = await testMongoDBService.provideCompletionItems( + 'db.collection.aggregate([{ $project: { revenue: { $a} } }]);', + { line: 0, character: 52 } + ); + const completion = result.find( + (item: CompletionItem) => item.label === '$addToSet' + ); + + expect(completion).to.have.property('kind', CompletionItemKind.Keyword); + }); + + test('provide aggregation accumulator completion for the $group stage', async () => { + const result = await testMongoDBService.provideCompletionItems( + 'db.collection.aggregate([{ $group: { _id: "$author", avgCopies: { $a} } }]);', + { line: 0, character: 68 } + ); + const completion = result.find( + (item: CompletionItem) => item.label === '$addToSet' + ); + + expect(completion).to.have.property('kind', CompletionItemKind.Keyword); + }); + + test('do not provide aggregation accumulator completion for the $match stage', async () => { + const result = await testMongoDBService.provideCompletionItems( + 'db.collection.aggregate([{ $match: { $a } }]);', + { line: 0, character: 39 } + ); + const completion = result.find( + (item: CompletionItem) => item.label === '$addToSet' + ); + + expect(completion).to.be.undefined; + }); + + test('do not provide aggregation accumulator completion for the $documents stage', async () => { + const result = await testMongoDBService.provideCompletionItems( + 'db.collection.aggregate([{ $documents: { $a } }]);', + { line: 0, character: 43 } + ); + const completion = result.find( + (item: CompletionItem) => item.label === '$addToSet' + ); + + expect(completion).to.be.undefined; + }); + + test('provide aggregation accumulator direction completion for the $project stage', async () => { + const result = await testMongoDBService.provideCompletionItems( + 'db.collection.aggregate([{ $project: { revenue: { $b} } }]);', + { line: 0, character: 52 } + ); + const completion = result.find( + (item: CompletionItem) => item.label === '$bottom' + ); + + expect(completion).to.have.property('kind', CompletionItemKind.Keyword); + }); + + test('provide aggregation accumulator direction completion for the $group stage', async () => { + const result = await testMongoDBService.provideCompletionItems( + 'db.collection.aggregate([{ $group: { _id: "$author", avgCopies: { $b} } }]);', + { line: 0, character: 68 } + ); + const completion = result.find( + (item: CompletionItem) => item.label === '$bottom' + ); + + expect(completion).to.have.property('kind', CompletionItemKind.Keyword); + }); + + test('do not provide aggregation accumulator direction completion for the $match stage', async () => { + const result = await testMongoDBService.provideCompletionItems( + 'db.collection.aggregate([{ $match: { $b } }]);', + { line: 0, character: 39 } + ); + const completion = result.find( + (item: CompletionItem) => item.label === '$bottom' + ); + + expect(completion).to.be.undefined; + }); + + test('do not provide aggregation accumulator direction completion for the $documents stage', async () => { + const result = await testMongoDBService.provideCompletionItems( + 'db.collection.aggregate([{ $documents: { $b } }]);', + { line: 0, character: 43 } + ); + const completion = result.find( + (item: CompletionItem) => item.label === '$bottom' + ); + + expect(completion).to.be.undefined; + }); + + test('provide aggregation accumulator window completion for the $setWindowFields stage', async () => { + const result = await testMongoDBService.provideCompletionItems( + 'db.collection.aggregate([{ $setWindowFields: { partitionBy: "$state", output: { documentNumberForState: { $d} } } }]);', + { line: 0, character: 108 } + ); + const completion = result.find( + (item: CompletionItem) => item.label === '$documentNumber' + ); + + expect(completion).to.have.property('kind', CompletionItemKind.Keyword); + }); + + test('do not provide aggregation accumulator window completion for the $group stage', async () => { + const result = await testMongoDBService.provideCompletionItems( + 'db.collection.aggregate([{ $group: { $d } }]);', + { line: 0, character: 39 } + ); + const completion = result.find( + (item: CompletionItem) => item.label === '$documentNumber' + ); + + expect(completion).to.be.undefined; }); test('provide db and use identifier completion', async () => { @@ -666,7 +957,82 @@ suite('MongoDBService Test Suite', () => { ); }); - test('provide collection names and shell db symbol completion for db symbol', async () => { + test('provide collection names completion for db symbol with bracket notation', async () => { + const textFromEditor = "use('berlin'); db['']"; + const position = { line: 0, character: 19 }; + + testMongoDBService._cacheCollectionCompletionItems( + textFromEditor, + position, + 'berlin', + [{ name: 'coll-name' }] + ); + + const result = await testMongoDBService.provideCompletionItems( + textFromEditor, + position + ); + const findCollectionCompletion = result.find( + (item: CompletionItem) => item.label === 'coll-name' + ); + + expect(findCollectionCompletion).to.have.property( + 'kind', + CompletionItemKind.Folder + ); + }); + + test('provide collection names completion for getCollection as a simple string', async () => { + const textFromEditor = "use('berlin'); db.getCollection('')"; + const position = { line: 0, character: 33 }; + + testMongoDBService._cacheCollectionCompletionItems( + textFromEditor, + position, + 'berlin', + [{ name: 'coll-name' }] + ); + + const result = await testMongoDBService.provideCompletionItems( + textFromEditor, + position + ); + const findCollectionCompletion = result.find( + (item: CompletionItem) => item.label === 'coll-name' + ); + + expect(findCollectionCompletion).to.have.property( + 'kind', + CompletionItemKind.Folder + ); + }); + + test('provide collection names completion for getCollection as a string template', async () => { + const textFromEditor = "use('berlin'); db.getCollection(``)"; + const position = { line: 0, character: 33 }; + + testMongoDBService._cacheCollectionCompletionItems( + textFromEditor, + position, + 'berlin', + [{ name: 'coll-name' }] + ); + + const result = await testMongoDBService.provideCompletionItems( + textFromEditor, + position + ); + const findCollectionCompletion = result.find( + (item: CompletionItem) => item.label === 'coll-name' + ); + + expect(findCollectionCompletion).to.have.property( + 'kind', + CompletionItemKind.Folder + ); + }); + + test('provide collection names and shell db symbol completion for db symbol with dot notation', async () => { const textFromEditor = "use('berlin'); db."; const position = { line: 0, character: 18 }; diff --git a/src/test/suite/snippets/stageAutocompleter.test.ts b/src/test/suite/snippets/stageAutocompleter.test.ts deleted file mode 100644 index 06b476165..000000000 --- a/src/test/suite/snippets/stageAutocompleter.test.ts +++ /dev/null @@ -1,66 +0,0 @@ -import * as chai from 'chai'; -import chaiFs from 'chai-fs'; -import chaiJsonSchema from 'chai-json-schema'; - -chai.use(chaiFs); -chai.use(chaiJsonSchema); - -const expect = chai.expect; -const SNIPPETS_DIR = `${__dirname}/../../../../snippets/`; -const SNIPPETS_FILE = `${SNIPPETS_DIR}stage-autocompleter.json`; - -const STAGE_LABELS = [ - 'MongoDB Aggregations $addFields', - 'MongoDB Aggregations $bucket', - 'MongoDB Aggregations $bucketAuto', - 'MongoDB Aggregations $collStats', - 'MongoDB Aggregations $count', - 'MongoDB Aggregations $densify', - 'MongoDB Aggregations $documents', - 'MongoDB Aggregations $facet', - 'MongoDB Aggregations $geoNear', - 'MongoDB Aggregations $graphLookup', - 'MongoDB Aggregations $group', - 'MongoDB Aggregations $indexStats', - 'MongoDB Aggregations $limit', - 'MongoDB Aggregations $lookup', - 'MongoDB Aggregations $match', - 'MongoDB Aggregations $merge', - 'MongoDB Aggregations $metaSearch', - 'MongoDB Aggregations $out', - 'MongoDB Aggregations $project', - 'MongoDB Aggregations $redact', - 'MongoDB Aggregations $replaceWith', - 'MongoDB Aggregations $replaceRoot', - 'MongoDB Aggregations $sample', - 'MongoDB Aggregations $searchBeta', - 'MongoDB Aggregations $set', - 'MongoDB Aggregations $skip', - 'MongoDB Aggregations $sort', - 'MongoDB Aggregations $sortByCount', - 'MongoDB Aggregations $unset', - 'MongoDB Aggregations $unwind' -]; - -suite('Stage Autocompleter Test Suite', () => { - test('checks that stage-autocompleter.json exists and includes JSON with prefix, body and description', () => { - const properties: { prefix: string, body: string, description: string } | {} = {}; - - STAGE_LABELS.forEach((prop: string) => { - properties[prop] = { - type: 'object', - properties: { prefix: 'string', body: 'array', description: 'string' } - }; - }); - - const jsonSchema = { - type: 'object', - properties - }; - - expect(SNIPPETS_DIR).to.be.a.path(); - expect(SNIPPETS_FILE) - .to.be.a.file() - .with.json.using.schema(jsonSchema); - }); -});