diff --git a/.gitignore b/.gitignore index 66fd13c..f27bea8 100644 --- a/.gitignore +++ b/.gitignore @@ -11,5 +11,13 @@ # Output of the go coverage tool, specifically when used with LiteIDE *.out +# Output of nodejs and typescript +out +node_modules + +# Other +.vscode-test +.DS_Store + # Dependency directories (remove the comment below to include it) # vendor/ diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..9bafcf0 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,11 @@ +language: node_js + +node_js: + - "8.11.1" + +install: + - npm install -g typescript + - npm install + +script: + - tsc -p ./ \ No newline at end of file diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..77efddb --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,32 @@ +{ + "version": "0.2.0", + "configurations": [ + { + "name": "Run Extension", + "type": "extensionHost", + "request": "launch", + "runtimeExecutable": "${execPath}", + "args": [ + "--extensionDevelopmentPath=${workspaceFolder}" + ], + "outFiles": [ + "${workspaceFolder}/out/**/*.js" + ], + "preLaunchTask": "${defaultBuildTask}" + }, + { + "name": "Extension Tests", + "type": "extensionHost", + "request": "launch", + "runtimeExecutable": "${execPath}", + "args": [ + "--extensionDevelopmentPath=${workspaceFolder}", + "--extensionTestsPath=${workspaceFolder}/out/test/suite/index" + ], + "outFiles": [ + "${workspaceFolder}/out/test/**/*.js" + ], + "preLaunchTask": "${defaultBuildTask}" + } + ] +} \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..3b66410 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "git.ignoreLimitWarning": true +} \ No newline at end of file diff --git a/.vscodeignore b/.vscodeignore new file mode 100644 index 0000000..38a1b9a --- /dev/null +++ b/.vscodeignore @@ -0,0 +1,19 @@ +src/**/* +test/ +third_party/ +typings/**/* +.vscode/**/* +tsconfig.json +.gitignore +**/*.map +**/tslint.json +build/**/* +docs/ +*.md.nightly +.github/**/* +.prettierrc.json +out/test/** +.vscode-test/** +SECURITY.md +node_modules/* +out/* \ No newline at end of file diff --git a/README.md b/README.md index ade10c2..67593d9 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,32 @@ -# vscode-gop -GoPlus (Go+) Plugin for vscode +![logo](./images/icon.png) + +[![Build Status](https://travis-ci.org/gopcode/vscode-goplus.svg?branch=master)](https://travis-ci.org/github/gopcode/vscode-goplus) +[![License](https://img.shields.io/badge/license-Apache-blue.svg)](https://raw.githubusercontent.com/gopcode/vscode-goplus/master/LICENSE) + +# GoPlus (Go+) Plugin for vscode + +This extension provides rich language support for the Go+ programming language in VS Code. + +## Example + +![example](https://github.com/gopcode/vscode-goplus/blob/master/images/example3.gif) + +## Install Go +Before you start coding, make sure that you have already installed Go, as explained in the Go installation guide. + +If you are unsure whether you have installed Go, open the Command Palette in VS Code (Ctrl+Shift+P) and run the goplus + +## Feature Support + +- [x] Syntax Highlight +- [x] Auto Snippet +- [x] Format Source Code +- [ ] Semantic Highlight +- [x] Auto Completion +- [x] Hover function Display +- [X] Auto import +- [ ] Code Diagnostics +- [ ] Help With Function and Method Signatures +- [x] Show Definitions of a Symbol +- [ ] Find All References to a Symbol +- [ ] Highlight All Occurrences of a Symbol in a Document diff --git a/fileicons/vs_goplus-icon-theme.json b/fileicons/vs_goplus-icon-theme.json new file mode 100644 index 0000000..6ca4f93 --- /dev/null +++ b/fileicons/vs_goplus-icon-theme.json @@ -0,0 +1,14 @@ +{ + "iconDefinitions": { + "_goplus_": { + "iconPath": "../images/icon.png" + } + }, + + "fileExtensions": { + "gop": "_goplus_" + }, + "languageIds": { + "gop": "_goplus_" + } +} \ No newline at end of file diff --git a/images/example1.gif b/images/example1.gif new file mode 100644 index 0000000..9d28ca1 Binary files /dev/null and b/images/example1.gif differ diff --git a/images/example2.gif b/images/example2.gif new file mode 100644 index 0000000..f0f428f Binary files /dev/null and b/images/example2.gif differ diff --git a/images/example3.gif b/images/example3.gif new file mode 100644 index 0000000..38b9591 Binary files /dev/null and b/images/example3.gif differ diff --git a/images/icon.png b/images/icon.png new file mode 100644 index 0000000..bce25fd Binary files /dev/null and b/images/icon.png differ diff --git a/language-configuration.json b/language-configuration.json new file mode 100644 index 0000000..8f162a0 --- /dev/null +++ b/language-configuration.json @@ -0,0 +1,30 @@ +{ + "comments": { + // symbol used for single line comment. Remove this entry if your language does not support line comments + "lineComment": "//", + // symbols used for start and end a block comment. Remove this entry if your language does not support block comments + "blockComment": [ "/*", "*/" ] + }, + // symbols used as brackets + "brackets": [ + ["{", "}"], + ["[", "]"], + ["(", ")"] + ], + // symbols that are auto closed when typing + "autoClosingPairs": [ + ["{", "}"], + ["[", "]"], + ["(", ")"], + ["\"", "\""], + ["'", "'"] + ], + // symbols that can be used to surround a selection + "surroundingPairs": [ + ["{", "}"], + ["[", "]"], + ["(", ")"], + ["\"", "\""], + ["'", "'"] + ] +} \ No newline at end of file diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..d4954f3 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,2066 @@ +{ + "name": "goplus", + "version": "0.0.5", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "@babel/code-frame": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.10.4.tgz", + "integrity": "sha512-vG6SvB6oYEhvgisZNFRmRCUkLz11c7rp+tbNTynGqc6mS1d5ATd/sGyV6W0KZZnXRKMTzZDRgQT3Ou9jhpAfUg==", + "dev": true, + "requires": { + "@babel/highlight": "7.10.4" + } + }, + "@babel/helper-validator-identifier": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.10.4.tgz", + "integrity": "sha512-3U9y+43hz7ZM+rzG24Qe2mufW5KhvFg/NhnNph+i9mgCtdTCtMJuI1TMkrIUiK7Ix4PYlRF9I5dhqaLYA/ADXw==", + "dev": true + }, + "@babel/highlight": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.10.4.tgz", + "integrity": "sha512-i6rgnR/YgPEQzZZnbTHHuZdlE8qyoBNalD6F+q4vAFlcMEcqmkoG+mPqJYJCo63qPf74+Y1UZsl3l6f7/RIkmA==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "7.10.4", + "chalk": "2.4.2", + "js-tokens": "4.0.0" + } + }, + "@types/color-name": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@types/color-name/-/color-name-1.1.1.tgz", + "integrity": "sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ==", + "dev": true + }, + "@types/eslint-visitor-keys": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@types/eslint-visitor-keys/-/eslint-visitor-keys-1.0.0.tgz", + "integrity": "sha512-OCutwjDZ4aFS6PB1UZ988C4YgwlBHJd6wCeQqaLdmadZ/7e+w79+hbMUFC1QXDNCmdyoRfAFdm0RypzwR+Qpag==", + "dev": true + }, + "@types/glob": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/@types/glob/-/glob-7.1.2.tgz", + "integrity": "sha512-VgNIkxK+j7Nz5P7jvUZlRvhuPSmsEfS03b0alKcq5V/STUKAa3Plemsn5mrQUO7am6OErJ4rhGEGJbACclrtRA==", + "dev": true, + "requires": { + "@types/minimatch": "3.0.3", + "@types/node": "13.13.12" + } + }, + "@types/json-schema": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.5.tgz", + "integrity": "sha512-7+2BITlgjgDhH0vvwZU/HZJVyk+2XUlvxXe8dFMedNX/aMkaOq++rMAFXc0tM7ij15QaWlbdQASBR9dihi+bDQ==", + "dev": true + }, + "@types/minimatch": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.3.tgz", + "integrity": "sha512-tHq6qdbT9U1IRSGf14CL0pUlULksvY9OZ+5eEgl1N7t+OA3tGvNpxJCzuKQlsNgCVwbAs670L1vcVQi8j9HjnA==", + "dev": true + }, + "@types/mocha": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-7.0.2.tgz", + "integrity": "sha512-ZvO2tAcjmMi8V/5Z3JsyofMe3hasRcaw88cto5etSVMwVQfeivGAlEYmaQgceUSVYFofVjT+ioHsATjdWcFt1w==", + "dev": true + }, + "@types/node": { + "version": "13.13.12", + "resolved": "https://registry.npmjs.org/@types/node/-/node-13.13.12.tgz", + "integrity": "sha512-zWz/8NEPxoXNT9YyF2osqyA9WjssZukYpgI4UYZpOjcyqwIUqWGkcCionaEb9Ki+FULyPyvNFpg/329Kd2/pbw==", + "dev": true + }, + "@types/semver": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.3.1.tgz", + "integrity": "sha512-ooD/FJ8EuwlDKOI6D9HWxgIgJjMg2cuziXm/42npDC8y4NjxplBUn9loewZiBNCt44450lHAU0OSb51/UqXeag==", + "dev": true, + "requires": { + "@types/node": "13.13.12" + } + }, + "@types/vscode": { + "version": "1.46.0", + "resolved": "https://registry.npmjs.org/@types/vscode/-/vscode-1.46.0.tgz", + "integrity": "sha512-8m9wPEB2mcRqTWNKs9A9Eqs8DrQZt0qNFO8GkxBOnyW6xR//3s77SoMgb/nY1ctzACsZXwZj3YRTDsn4bAoaUw==", + "dev": true + }, + "@typescript-eslint/eslint-plugin": { + "version": "2.34.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-2.34.0.tgz", + "integrity": "sha512-4zY3Z88rEE99+CNvTbXSyovv2z9PNOVffTWD2W8QF5s2prBQtwN2zadqERcrHpcR7O/+KMI3fcTAmUUhK/iQcQ==", + "dev": true, + "requires": { + "@typescript-eslint/experimental-utils": "2.34.0", + "functional-red-black-tree": "1.0.1", + "regexpp": "3.1.0", + "tsutils": "3.17.1" + } + }, + "@typescript-eslint/experimental-utils": { + "version": "2.34.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-2.34.0.tgz", + "integrity": "sha512-eS6FTkq+wuMJ+sgtuNTtcqavWXqsflWcfBnlYhg/nS4aZ1leewkXGbvBhaapn1q6qf4M71bsR1tez5JTRMuqwA==", + "dev": true, + "requires": { + "@types/json-schema": "7.0.5", + "@typescript-eslint/typescript-estree": "2.34.0", + "eslint-scope": "5.1.0", + "eslint-utils": "2.1.0" + } + }, + "@typescript-eslint/parser": { + "version": "2.34.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-2.34.0.tgz", + "integrity": "sha512-03ilO0ucSD0EPTw2X4PntSIRFtDPWjrVq7C3/Z3VQHRC7+13YB55rcJI3Jt+YgeHbjUdJPcPa7b23rXCBokuyA==", + "dev": true, + "requires": { + "@types/eslint-visitor-keys": "1.0.0", + "@typescript-eslint/experimental-utils": "2.34.0", + "@typescript-eslint/typescript-estree": "2.34.0", + "eslint-visitor-keys": "1.3.0" + } + }, + "@typescript-eslint/typescript-estree": { + "version": "2.34.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-2.34.0.tgz", + "integrity": "sha512-OMAr+nJWKdlVM9LOqCqh3pQQPwxHAN7Du8DR6dmwCrAmxtiXQnhHJ6tBNtf+cggqfo51SG/FCwnKhXCIM7hnVg==", + "dev": true, + "requires": { + "debug": "4.1.1", + "eslint-visitor-keys": "1.3.0", + "glob": "7.1.6", + "is-glob": "4.0.1", + "lodash": "4.17.15", + "semver": "7.3.2", + "tsutils": "3.17.1" + }, + "dependencies": { + "semver": { + "version": "7.3.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.2.tgz", + "integrity": "sha512-OrOb32TeeambH6UrhtShmF7CRDqhL6/5XpPNp2DuRH6+9QLw/orhp72j87v8Qa1ScDkvrrBNpZcDejAirJmfXQ==", + "dev": true + } + } + }, + "acorn": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.3.1.tgz", + "integrity": "sha512-tLc0wSnatxAQHVHUapaHdz72pi9KUyHjq5KyHjGg9Y8Ifdc79pTh2XvI6I1/chZbnM7QtNKzh66ooDogPZSleA==", + "dev": true + }, + "acorn-jsx": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.2.0.tgz", + "integrity": "sha512-HiUX/+K2YpkpJ+SzBffkM/AQ2YE03S0U1kjTLVpoJdhZMOWy8qvXVN9JdLqv2QsaQ6MPYQIuNmwD8zOiYUofLQ==", + "dev": true + }, + "agent-base": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-4.3.0.tgz", + "integrity": "sha512-salcGninV0nPrwpGNn4VTXBb1SOuXQBiqbrNXoeizJsHrsL6ERFM2Ne3JUSBWRE6aeNJI2ROP/WEEIDUiDe3cg==", + "dev": true, + "requires": { + "es6-promisify": "5.0.0" + } + }, + "ajv": { + "version": "6.12.2", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.2.tgz", + "integrity": "sha512-k+V+hzjm5q/Mr8ef/1Y9goCmlsK4I6Sm74teeyGvFk1XrOsbsKLjEdrvny42CZ+a8sXbk8KWpY/bDwS+FLL2UQ==", + "dev": true, + "requires": { + "fast-deep-equal": "3.1.3", + "fast-json-stable-stringify": "2.1.0", + "json-schema-traverse": "0.4.1", + "uri-js": "4.2.2" + } + }, + "ansi-colors": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-3.2.3.tgz", + "integrity": "sha512-LEHHyuhlPY3TmuUYMh2oz89lTShfvgbmzaBcxve9t/9Wuy7Dwf4yoAKcND7KFT1HAQfqZ12qtc+DUrBMeKF9nw==", + "dev": true + }, + "ansi-escapes": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.1.tgz", + "integrity": "sha512-JWF7ocqNrp8u9oqpgV+wH5ftbt+cfvv+PTjOvKLT3AdYly/LmORARfEVT1iyjwN+4MqE5UmVKoAdIBqeoCHgLA==", + "dev": true, + "requires": { + "type-fest": "0.11.0" + }, + "dependencies": { + "type-fest": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.11.0.tgz", + "integrity": "sha512-OdjXJxnCN1AvyLSzeKIgXTXxV+99ZuXl3Hpo9XpJAv9MBcHrrJOQ5kV7ypXOuQie+AmWG25hLbiKdwYTifzcfQ==", + "dev": true + } + } + }, + "ansi-regex": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", + "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", + "dev": true + }, + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "1.9.3" + } + }, + "anymatch": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.1.tgz", + "integrity": "sha512-mM8522psRCqzV+6LhomX5wgp25YVibjh8Wj23I5RPkPppSVSjyKD2A2mBJmWGa+KN7f2D6LNh9jkBCeyLktzjg==", + "dev": true, + "requires": { + "normalize-path": "3.0.0", + "picomatch": "2.2.2" + } + }, + "argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "requires": { + "sprintf-js": "1.0.3" + } + }, + "astral-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-1.0.0.tgz", + "integrity": "sha512-+Ryf6g3BKoRc7jfp7ad8tM4TtMiaWvbF/1/sQcZPkkS7ag3D5nMBCe2UfOTONtAkaG0tO0ij3C5Lwmf1EiyjHg==", + "dev": true + }, + "balanced-match": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", + "dev": true + }, + "binary-extensions": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.1.0.tgz", + "integrity": "sha512-1Yj8h9Q+QDF5FzhMs/c9+6UntbD5MkRfRwac8DoEm9ZfUBZ7tZ55YcGVAzEe4bXsdQHEk+s9S5wsOKVdZrw0tQ==", + "dev": true + }, + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "requires": { + "balanced-match": "1.0.0", + "concat-map": "0.0.1" + } + }, + "braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "requires": { + "fill-range": "7.0.1" + } + }, + "browser-stdout": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", + "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", + "dev": true + }, + "callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true + }, + "camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "requires": { + "ansi-styles": "3.2.1", + "escape-string-regexp": "1.0.5", + "supports-color": "5.5.0" + } + }, + "chardet": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz", + "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==", + "dev": true + }, + "chokidar": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.3.0.tgz", + "integrity": "sha512-dGmKLDdT3Gdl7fBUe8XK+gAtGmzy5Fn0XkkWQuYxGIgWVPPse2CxFA5mtrlD0TOHaHjEUqkWNyP1XdHoJES/4A==", + "dev": true, + "requires": { + "anymatch": "3.1.1", + "braces": "3.0.2", + "fsevents": "2.1.3", + "glob-parent": "5.1.1", + "is-binary-path": "2.1.0", + "is-glob": "4.0.1", + "normalize-path": "3.0.0", + "readdirp": "3.2.0" + } + }, + "cli-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", + "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", + "dev": true, + "requires": { + "restore-cursor": "3.1.0" + } + }, + "cli-width": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-2.2.1.tgz", + "integrity": "sha512-GRMWDxpOB6Dgk2E5Uo+3eEBvtOOlimMmpbFiKuLFnQzYDavtLFY3K5ona41jgN/WdRZtG7utuVSVTL4HbZHGkw==", + "dev": true + }, + "cliui": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-5.0.0.tgz", + "integrity": "sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA==", + "dev": true, + "requires": { + "string-width": "3.1.0", + "strip-ansi": "5.2.0", + "wrap-ansi": "5.1.0" + }, + "dependencies": { + "emoji-regex": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", + "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", + "dev": true + }, + "string-width": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", + "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", + "dev": true, + "requires": { + "emoji-regex": "7.0.3", + "is-fullwidth-code-point": "2.0.0", + "strip-ansi": "5.2.0" + } + } + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", + "dev": true + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", + "dev": true + }, + "cross-spawn": { + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", + "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", + "dev": true, + "requires": { + "nice-try": "1.0.5", + "path-key": "2.0.1", + "semver": "5.7.1", + "shebang-command": "1.2.0", + "which": "1.3.1" + }, + "dependencies": { + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true + } + } + }, + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "dev": true, + "requires": { + "ms": "2.1.2" + } + }, + "decamelize": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", + "dev": true + }, + "deep-is": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz", + "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=", + "dev": true + }, + "define-properties": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", + "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==", + "dev": true, + "requires": { + "object-keys": "1.1.1" + } + }, + "diff": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz", + "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==", + "dev": true + }, + "doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "dev": true, + "requires": { + "esutils": "2.0.3" + } + }, + "emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "es-abstract": { + "version": "1.17.6", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.6.tgz", + "integrity": "sha512-Fr89bON3WFyUi5EvAeI48QTWX0AyekGgLA8H+c+7fbfCkJwRWRMLd8CQedNEyJuoYYhmtEqY92pgte1FAhBlhw==", + "dev": true, + "requires": { + "es-to-primitive": "1.2.1", + "function-bind": "1.1.1", + "has": "1.0.3", + "has-symbols": "1.0.1", + "is-callable": "1.2.0", + "is-regex": "1.1.0", + "object-inspect": "1.8.0", + "object-keys": "1.1.1", + "object.assign": "4.1.0", + "string.prototype.trimend": "1.0.1", + "string.prototype.trimstart": "1.0.1" + } + }, + "es-to-primitive": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", + "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", + "dev": true, + "requires": { + "is-callable": "1.2.0", + "is-date-object": "1.0.2", + "is-symbol": "1.0.3" + } + }, + "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-promisify": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/es6-promisify/-/es6-promisify-5.0.0.tgz", + "integrity": "sha1-UQnWLz5W6pZ8S2NQWu8IKRyKUgM=", + "dev": true, + "requires": { + "es6-promise": "4.2.8" + } + }, + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", + "dev": true + }, + "eslint": { + "version": "6.8.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-6.8.0.tgz", + "integrity": "sha512-K+Iayyo2LtyYhDSYwz5D5QdWw0hCacNzyq1Y821Xna2xSJj7cijoLLYmLxTQgcgZ9mC61nryMy9S7GRbYpI5Ig==", + "dev": true, + "requires": { + "@babel/code-frame": "7.10.4", + "ajv": "6.12.2", + "chalk": "2.4.2", + "cross-spawn": "6.0.5", + "debug": "4.1.1", + "doctrine": "3.0.0", + "eslint-scope": "5.1.0", + "eslint-utils": "1.4.3", + "eslint-visitor-keys": "1.3.0", + "espree": "6.2.1", + "esquery": "1.3.1", + "esutils": "2.0.3", + "file-entry-cache": "5.0.1", + "functional-red-black-tree": "1.0.1", + "glob-parent": "5.1.1", + "globals": "12.4.0", + "ignore": "4.0.6", + "import-fresh": "3.2.1", + "imurmurhash": "0.1.4", + "inquirer": "7.2.0", + "is-glob": "4.0.1", + "js-yaml": "3.14.0", + "json-stable-stringify-without-jsonify": "1.0.1", + "levn": "0.3.0", + "lodash": "4.17.15", + "minimatch": "3.0.4", + "mkdirp": "0.5.5", + "natural-compare": "1.4.0", + "optionator": "0.8.3", + "progress": "2.0.3", + "regexpp": "2.0.1", + "semver": "6.3.0", + "strip-ansi": "5.2.0", + "strip-json-comments": "3.1.0", + "table": "5.4.6", + "text-table": "0.2.0", + "v8-compile-cache": "2.1.1" + }, + "dependencies": { + "eslint-utils": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-1.4.3.tgz", + "integrity": "sha512-fbBN5W2xdY45KulGXmLHZ3c3FHfVYmKg0IrAKGOkT/464PQsx2UeIzfz1RmEci+KLm1bBaAzZAh8+/E+XAeZ8Q==", + "dev": true, + "requires": { + "eslint-visitor-keys": "1.3.0" + } + }, + "regexpp": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-2.0.1.tgz", + "integrity": "sha512-lv0M6+TkDVniA3aD1Eg0DVpfU/booSu7Eev3TDO/mZKHBfVjgCGTV4t4buppESEYDtkArYFOxTJWv6S5C+iaNw==", + "dev": true + }, + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true + } + } + }, + "eslint-scope": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.0.tgz", + "integrity": "sha512-iiGRvtxWqgtx5m8EyQUJihBloE4EnYeGE/bz1wSPwJE6tZuJUtHlhqDM4Xj2ukE8Dyy1+HCZ4hE0fzIVMzb58w==", + "dev": true, + "requires": { + "esrecurse": "4.2.1", + "estraverse": "4.3.0" + } + }, + "eslint-utils": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-2.1.0.tgz", + "integrity": "sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg==", + "dev": true, + "requires": { + "eslint-visitor-keys": "1.3.0" + } + }, + "eslint-visitor-keys": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", + "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", + "dev": true + }, + "espree": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-6.2.1.tgz", + "integrity": "sha512-ysCxRQY3WaXJz9tdbWOwuWr5Y/XrPTGX9Kiz3yoUXwW0VZ4w30HTkQLaGx/+ttFjF8i+ACbArnB4ce68a9m5hw==", + "dev": true, + "requires": { + "acorn": "7.3.1", + "acorn-jsx": "5.2.0", + "eslint-visitor-keys": "1.3.0" + } + }, + "esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true + }, + "esquery": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.3.1.tgz", + "integrity": "sha512-olpvt9QG0vniUBZspVRN6lwB7hOZoTRtT+jzR+tS4ffYx2mzbw+z0XCOk44aaLYKApNX5nMm+E+P6o25ip/DHQ==", + "dev": true, + "requires": { + "estraverse": "5.1.0" + }, + "dependencies": { + "estraverse": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.1.0.tgz", + "integrity": "sha512-FyohXK+R0vE+y1nHLoBM7ZTyqRpqAlhdZHCWIWEviFLiGB8b04H6bQs8G+XTthacvT8VuwvteiP7RJSxMs8UEw==", + "dev": true + } + } + }, + "esrecurse": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.2.1.tgz", + "integrity": "sha512-64RBB++fIOAXPw3P9cy89qfMlvZEXZkqqJkjqqXIvzP5ezRZjW+lPWjw35UX/3EhUPFYbg5ER4JYgDw4007/DQ==", + "dev": true, + "requires": { + "estraverse": "4.3.0" + } + }, + "estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "dev": true + }, + "esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true + }, + "external-editor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.1.0.tgz", + "integrity": "sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==", + "dev": true, + "requires": { + "chardet": "0.7.0", + "iconv-lite": "0.4.24", + "tmp": "0.0.33" + } + }, + "fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true + }, + "fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true + }, + "fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=", + "dev": true + }, + "figures": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz", + "integrity": "sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==", + "dev": true, + "requires": { + "escape-string-regexp": "1.0.5" + } + }, + "file-entry-cache": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-5.0.1.tgz", + "integrity": "sha512-bCg29ictuBaKUwwArK4ouCaqDgLZcysCFLmM/Yn/FDoqndh/9vNuQfXRDvTuXKLxfD/JtZQGKFT8MGcJBK644g==", + "dev": true, + "requires": { + "flat-cache": "2.0.1" + } + }, + "fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, + "requires": { + "to-regex-range": "5.0.1" + } + }, + "find-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "dev": true, + "requires": { + "locate-path": "3.0.0" + } + }, + "flat": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/flat/-/flat-4.1.0.tgz", + "integrity": "sha512-Px/TiLIznH7gEDlPXcUD4KnBusa6kR6ayRUVcnEAbreRIuhkqow/mun59BuRXwoYk7ZQOLW1ZM05ilIvK38hFw==", + "dev": true, + "requires": { + "is-buffer": "2.0.4" + } + }, + "flat-cache": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-2.0.1.tgz", + "integrity": "sha512-LoQe6yDuUMDzQAEH8sgmh4Md6oZnc/7PjtwjNFSzveXqSHt6ka9fPBuso7IGf9Rz4uqnSnWiFH2B/zj24a5ReA==", + "dev": true, + "requires": { + "flatted": "2.0.2", + "rimraf": "2.6.3", + "write": "1.0.3" + } + }, + "flatted": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-2.0.2.tgz", + "integrity": "sha512-r5wGx7YeOwNWNlCA0wQ86zKyDLMQr+/RB8xy74M4hTphfmjlijTSSXGuH8rnvKZnfT9i+75zmd8jcKdMR4O6jA==", + "dev": true + }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", + "dev": true + }, + "fsevents": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.1.3.tgz", + "integrity": "sha512-Auw9a4AxqWpa9GUfj370BMPzzyncfBABW8Mab7BGWBYDj4Isgq+cDKtx0i6u9jcX9pQDnswsaaOTgTmA5pEjuQ==", + "dev": true, + "optional": true + }, + "function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", + "dev": true + }, + "functional-red-black-tree": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", + "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=", + "dev": true + }, + "get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true + }, + "glob": { + "version": "7.1.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", + "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", + "dev": true, + "requires": { + "fs.realpath": "1.0.0", + "inflight": "1.0.6", + "inherits": "2.0.4", + "minimatch": "3.0.4", + "once": "1.4.0", + "path-is-absolute": "1.0.1" + } + }, + "glob-parent": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.1.tgz", + "integrity": "sha512-FnI+VGOpnlGHWZxthPGR+QhR78fuiK0sNLkHQv+bL9fQi57lNNdquIbna/WrfROrolq8GK5Ek6BiMwqL/voRYQ==", + "dev": true, + "requires": { + "is-glob": "4.0.1" + } + }, + "globals": { + "version": "12.4.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-12.4.0.tgz", + "integrity": "sha512-BWICuzzDvDoH54NHKCseDanAhE3CeDorgDL5MT6LMXXj2WCnd9UC2szdk4AWLfjdgNBCXLUanXYcpBBKOSWGwg==", + "dev": true, + "requires": { + "type-fest": "0.8.1" + } + }, + "growl": { + "version": "1.10.5", + "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.5.tgz", + "integrity": "sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==", + "dev": true + }, + "has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "dev": true, + "requires": { + "function-bind": "1.1.1" + } + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true + }, + "has-symbols": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.1.tgz", + "integrity": "sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg==", + "dev": true + }, + "he": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", + "dev": true + }, + "http-proxy-agent": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-2.1.0.tgz", + "integrity": "sha512-qwHbBLV7WviBl0rQsOzH6o5lwyOIvwp/BdFnvVxXORldu5TmjFfjzBcWUWS5kWAZhmv+JtiDhSuQCp4sBfbIgg==", + "dev": true, + "requires": { + "agent-base": "4.3.0", + "debug": "3.1.0" + }, + "dependencies": { + "debug": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", + "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + } + } + }, + "https-proxy-agent": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-2.2.4.tgz", + "integrity": "sha512-OmvfoQ53WLjtA9HeYP9RNrWMJzzAz1JGaSFr1nijg0PVR1JaD/xbJq1mdEIIlxGpXp9eSe/O2LgU9DJmTPd0Eg==", + "dev": true, + "requires": { + "agent-base": "4.3.0", + "debug": "3.2.6" + }, + "dependencies": { + "debug": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", + "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", + "dev": true, + "requires": { + "ms": "2.1.2" + } + } + } + }, + "iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dev": true, + "requires": { + "safer-buffer": "2.1.2" + } + }, + "ignore": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", + "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", + "dev": true + }, + "import-fresh": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.2.1.tgz", + "integrity": "sha512-6e1q1cnWP2RXD9/keSkxHScg508CdXqXWgWBaETNhyuBFz+kUZlKboh+ISK+bU++DmbHimVBrOz/zzPe0sZ3sQ==", + "dev": true, + "requires": { + "parent-module": "1.0.1", + "resolve-from": "4.0.0" + } + }, + "imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", + "dev": true + }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "dev": true, + "requires": { + "once": "1.4.0", + "wrappy": "1.0.2" + } + }, + "inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + }, + "inquirer": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-7.2.0.tgz", + "integrity": "sha512-E0c4rPwr9ByePfNlTIB8z51kK1s2n6jrHuJeEHENl/sbq2G/S1auvibgEwNR4uSyiU+PiYHqSwsgGiXjG8p5ZQ==", + "dev": true, + "requires": { + "ansi-escapes": "4.3.1", + "chalk": "3.0.0", + "cli-cursor": "3.1.0", + "cli-width": "2.2.1", + "external-editor": "3.1.0", + "figures": "3.2.0", + "lodash": "4.17.15", + "mute-stream": "0.0.8", + "run-async": "2.4.1", + "rxjs": "6.5.5", + "string-width": "4.2.0", + "strip-ansi": "6.0.0", + "through": "2.3.8" + }, + "dependencies": { + "ansi-styles": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", + "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", + "dev": true, + "requires": { + "@types/color-name": "1.1.1", + "color-convert": "2.0.1" + } + }, + "chalk": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", + "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", + "dev": true, + "requires": { + "ansi-styles": "4.2.1", + "supports-color": "7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "strip-ansi": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", + "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", + "dev": true, + "requires": { + "ansi-regex": "5.0.0" + } + }, + "supports-color": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", + "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", + "dev": true, + "requires": { + "has-flag": "4.0.0" + } + } + } + }, + "is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "requires": { + "binary-extensions": "2.1.0" + } + }, + "is-buffer": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.4.tgz", + "integrity": "sha512-Kq1rokWXOPXWuaMAqZiJW4XxsmD9zGx9q4aePabbn3qCRGedtH7Cm+zV8WETitMfu1wdh+Rvd6w5egwSngUX2A==", + "dev": true + }, + "is-callable": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.0.tgz", + "integrity": "sha512-pyVD9AaGLxtg6srb2Ng6ynWJqkHU9bEM087AKck0w8QwDarTfNcpIYoU8x8Hv2Icm8u6kFJM18Dag8lyqGkviw==", + "dev": true + }, + "is-date-object": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.2.tgz", + "integrity": "sha512-USlDT524woQ08aoZFzh3/Z6ch9Y/EWXEHQ/AaRN0SkKq4t2Jw2R2339tSXmwuVoY7LLlBCbOIlx2myP/L5zk0g==", + "dev": true + }, + "is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true + }, + "is-glob": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz", + "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==", + "dev": true, + "requires": { + "is-extglob": "2.1.1" + } + }, + "is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true + }, + "is-regex": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.0.tgz", + "integrity": "sha512-iI97M8KTWID2la5uYXlkbSDQIg4F6o1sYboZKKTDpnDQMLtUL86zxhgDet3Q2SriaYsyGqZ6Mn2SjbRKeLHdqw==", + "dev": true, + "requires": { + "has-symbols": "1.0.1" + } + }, + "is-symbol": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.3.tgz", + "integrity": "sha512-OwijhaRSgqvhm/0ZdAcXNZt9lYdKFpcRDT5ULUuYXPoT794UNOdU+gpT6Rzo7b4V2HUl/op6GqY894AZwv9faQ==", + "dev": true, + "requires": { + "has-symbols": "1.0.1" + } + }, + "isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", + "dev": true + }, + "js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true + }, + "js-yaml": { + "version": "3.14.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.0.tgz", + "integrity": "sha512-/4IbIeHcD9VMHFqDR/gQ7EdZdLimOvW2DdcxFjdyyZ9NsbS+ccrXqVWDtab/lRl5AlUqmpBx8EhPaWR+OtY17A==", + "dev": true, + "requires": { + "argparse": "1.0.10", + "esprima": "4.0.1" + } + }, + "json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, + "json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=", + "dev": true + }, + "levn": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", + "integrity": "sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4=", + "dev": true, + "requires": { + "prelude-ls": "1.1.2", + "type-check": "0.3.2" + } + }, + "locate-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", + "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "dev": true, + "requires": { + "p-locate": "3.0.0", + "path-exists": "3.0.0" + } + }, + "lodash": { + "version": "4.17.15", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz", + "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==", + "dev": true + }, + "log-symbols": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-3.0.0.tgz", + "integrity": "sha512-dSkNGuI7iG3mfvDzUuYZyvk5dD9ocYCYzNU6CYDE6+Xqd+gwme6Z00NS3dUh8mq/73HaEtT7m6W+yUPtU6BZnQ==", + "dev": true, + "requires": { + "chalk": "2.4.2" + } + }, + "mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "dev": true + }, + "minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "dev": true, + "requires": { + "brace-expansion": "1.1.11" + } + }, + "minimist": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", + "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==" + }, + "mkdirp": { + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", + "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", + "requires": { + "minimist": "1.2.5" + } + }, + "mocha": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-7.2.0.tgz", + "integrity": "sha512-O9CIypScywTVpNaRrCAgoUnJgozpIofjKUYmJhiCIJMiuYnLI6otcb1/kpW9/n/tJODHGZ7i8aLQoDVsMtOKQQ==", + "dev": true, + "requires": { + "ansi-colors": "3.2.3", + "browser-stdout": "1.3.1", + "chokidar": "3.3.0", + "debug": "3.2.6", + "diff": "3.5.0", + "escape-string-regexp": "1.0.5", + "find-up": "3.0.0", + "glob": "7.1.3", + "growl": "1.10.5", + "he": "1.2.0", + "js-yaml": "3.13.1", + "log-symbols": "3.0.0", + "minimatch": "3.0.4", + "mkdirp": "0.5.5", + "ms": "2.1.1", + "node-environment-flags": "1.0.6", + "object.assign": "4.1.0", + "strip-json-comments": "2.0.1", + "supports-color": "6.0.0", + "which": "1.3.1", + "wide-align": "1.1.3", + "yargs": "13.3.2", + "yargs-parser": "13.1.2", + "yargs-unparser": "1.6.0" + }, + "dependencies": { + "debug": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", + "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", + "dev": true, + "requires": { + "ms": "2.1.1" + } + }, + "glob": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.3.tgz", + "integrity": "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==", + "dev": true, + "requires": { + "fs.realpath": "1.0.0", + "inflight": "1.0.6", + "inherits": "2.0.4", + "minimatch": "3.0.4", + "once": "1.4.0", + "path-is-absolute": "1.0.1" + } + }, + "js-yaml": { + "version": "3.13.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.13.1.tgz", + "integrity": "sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw==", + "dev": true, + "requires": { + "argparse": "1.0.10", + "esprima": "4.0.1" + } + }, + "ms": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", + "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", + "dev": true + }, + "strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=", + "dev": true + }, + "supports-color": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.0.0.tgz", + "integrity": "sha512-on9Kwidc1IUQo+bQdhi8+Tijpo0e1SS6RoGo2guUwn5vdaxw8RXOF9Vb2ws+ihWOmh4JnCJOvaziZWP1VABaLg==", + "dev": true, + "requires": { + "has-flag": "3.0.0" + } + } + } + }, + "moment": { + "version": "2.27.0", + "resolved": "https://registry.npmjs.org/moment/-/moment-2.27.0.tgz", + "integrity": "sha512-al0MUK7cpIcglMv3YF13qSgdAIqxHTO7brRtaz3DlSULbqfazqkc5kEjNrLDOM7fsjshoFIihnU8snrP7zUvhQ==" + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "mute-stream": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz", + "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==", + "dev": true + }, + "natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=", + "dev": true + }, + "nice-try": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", + "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==", + "dev": true + }, + "node-environment-flags": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/node-environment-flags/-/node-environment-flags-1.0.6.tgz", + "integrity": "sha512-5Evy2epuL+6TM0lCQGpFIj6KwiEsGh1SrHUhTbNX+sLbBtjidPZFAnVK9y5yU1+h//RitLbRHTIMyxQPtxMdHw==", + "dev": true, + "requires": { + "object.getownpropertydescriptors": "2.1.0", + "semver": "5.7.1" + }, + "dependencies": { + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true + } + } + }, + "normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true + }, + "object-inspect": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.8.0.tgz", + "integrity": "sha512-jLdtEOB112fORuypAyl/50VRVIBIdVQOSUUGQHzJ4xBSbit81zRarz7GThkEFZy1RceYrWYcPcBFPQwHyAc1gA==", + "dev": true + }, + "object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "dev": true + }, + "object.assign": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.0.tgz", + "integrity": "sha512-exHJeq6kBKj58mqGyTQ9DFvrZC/eR6OwxzoM9YRoGBqrXYonaFyGiFMuc9VZrXf7DarreEwMpurG3dd+CNyW5w==", + "dev": true, + "requires": { + "define-properties": "1.1.3", + "function-bind": "1.1.1", + "has-symbols": "1.0.1", + "object-keys": "1.1.1" + } + }, + "object.getownpropertydescriptors": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.1.0.tgz", + "integrity": "sha512-Z53Oah9A3TdLoblT7VKJaTDdXdT+lQO+cNpKVnya5JDe9uLvzu1YyY1yFDFrcxrlRgWrEFH0jJtD/IbuwjcEVg==", + "dev": true, + "requires": { + "define-properties": "1.1.3", + "es-abstract": "1.17.6" + } + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "dev": true, + "requires": { + "wrappy": "1.0.2" + } + }, + "onetime": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.0.tgz", + "integrity": "sha512-5NcSkPHhwTVFIQN+TUqXoS5+dlElHXdpAWu9I0HP20YOtIi+aZ0Ct82jdlILDxjLEAWwvm+qj1m6aEtsDVmm6Q==", + "dev": true, + "requires": { + "mimic-fn": "2.1.0" + } + }, + "optionator": { + "version": "0.8.3", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz", + "integrity": "sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==", + "dev": true, + "requires": { + "deep-is": "0.1.3", + "fast-levenshtein": "2.0.6", + "levn": "0.3.0", + "prelude-ls": "1.1.2", + "type-check": "0.3.2", + "word-wrap": "1.2.3" + } + }, + "os-tmpdir": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", + "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=", + "dev": true + }, + "p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "requires": { + "p-try": "2.2.0" + } + }, + "p-locate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", + "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "dev": true, + "requires": { + "p-limit": "2.3.0" + } + }, + "p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true + }, + "parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "requires": { + "callsites": "3.1.0" + } + }, + "path-exists": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", + "dev": true + }, + "path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", + "dev": true + }, + "path-key": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", + "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=", + "dev": true + }, + "picomatch": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.2.2.tgz", + "integrity": "sha512-q0M/9eZHzmr0AulXyPwNfZjtwZ/RBZlbN3K3CErVrk50T2ASYI7Bye0EvekFY3IP1Nt2DHu0re+V2ZHIpMkuWg==", + "dev": true + }, + "prelude-ls": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", + "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=", + "dev": true + }, + "progress": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", + "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", + "dev": true + }, + "punycode": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", + "dev": true + }, + "readdirp": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.2.0.tgz", + "integrity": "sha512-crk4Qu3pmXwgxdSgGhgA/eXiJAPQiX4GMOZZMXnqKxHX7TaoL+3gQVo/WeuAiogr07DpnfjIMpXXa+PAIvwPGQ==", + "dev": true, + "requires": { + "picomatch": "2.2.2" + } + }, + "regexpp": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.1.0.tgz", + "integrity": "sha512-ZOIzd8yVsQQA7j8GCSlPGXwg5PfmA1mrq0JP4nGhh54LaKN3xdai/vHUDu74pKwV8OxseMS65u2NImosQcSD0Q==", + "dev": true + }, + "require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", + "dev": true + }, + "require-main-filename": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", + "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", + "dev": true + }, + "resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true + }, + "restore-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", + "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==", + "dev": true, + "requires": { + "onetime": "5.1.0", + "signal-exit": "3.0.3" + } + }, + "rimraf": { + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz", + "integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==", + "dev": true, + "requires": { + "glob": "7.1.6" + } + }, + "run-async": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.4.1.tgz", + "integrity": "sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ==", + "dev": true + }, + "rxjs": { + "version": "6.5.5", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.5.5.tgz", + "integrity": "sha512-WfQI+1gohdf0Dai/Bbmk5L5ItH5tYqm3ki2c5GdWhKjalzjg93N3avFjVStyZZz+A2Em+ZxKH5bNghw9UeylGQ==", + "dev": true, + "requires": { + "tslib": "1.13.0" + } + }, + "safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "dev": true + }, + "semver": { + "version": "7.3.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.2.tgz", + "integrity": "sha512-OrOb32TeeambH6UrhtShmF7CRDqhL6/5XpPNp2DuRH6+9QLw/orhp72j87v8Qa1ScDkvrrBNpZcDejAirJmfXQ==" + }, + "set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", + "dev": true + }, + "shebang-command": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", + "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=", + "dev": true, + "requires": { + "shebang-regex": "1.0.0" + } + }, + "shebang-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", + "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=", + "dev": true + }, + "signal-exit": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.3.tgz", + "integrity": "sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA==", + "dev": true + }, + "slice-ansi": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-2.1.0.tgz", + "integrity": "sha512-Qu+VC3EwYLldKa1fCxuuvULvSJOKEgk9pi8dZeCVK7TqBfUNTH4sFkk4joj8afVSfAYgJoSOetjx9QWOJ5mYoQ==", + "dev": true, + "requires": { + "ansi-styles": "3.2.1", + "astral-regex": "1.0.0", + "is-fullwidth-code-point": "2.0.0" + }, + "dependencies": { + "is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", + "dev": true + } + } + }, + "sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", + "dev": true + }, + "string-width": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.0.tgz", + "integrity": "sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg==", + "dev": true, + "requires": { + "emoji-regex": "8.0.0", + "is-fullwidth-code-point": "3.0.0", + "strip-ansi": "6.0.0" + }, + "dependencies": { + "strip-ansi": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", + "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", + "dev": true, + "requires": { + "ansi-regex": "5.0.0" + } + } + } + }, + "string.prototype.trimend": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.1.tgz", + "integrity": "sha512-LRPxFUaTtpqYsTeNKaFOw3R4bxIzWOnbQ837QfBylo8jIxtcbK/A/sMV7Q+OAV/vWo+7s25pOE10KYSjaSO06g==", + "dev": true, + "requires": { + "define-properties": "1.1.3", + "es-abstract": "1.17.6" + } + }, + "string.prototype.trimstart": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.1.tgz", + "integrity": "sha512-XxZn+QpvrBI1FOcg6dIpxUPgWCPuNXvMD72aaRaUQv1eD4e/Qy8i/hFTe0BUmD60p/QA6bh1avmuPTfNjqVWRw==", + "dev": true, + "requires": { + "define-properties": "1.1.3", + "es-abstract": "1.17.6" + } + }, + "strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "dev": true, + "requires": { + "ansi-regex": "4.1.0" + }, + "dependencies": { + "ansi-regex": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", + "dev": true + } + } + }, + "strip-json-comments": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.0.tgz", + "integrity": "sha512-e6/d0eBu7gHtdCqFt0xJr642LdToM5/cN4Qb9DbHjVx1CP5RyeM+zH7pbecEmDv/lBqb0QH+6Uqq75rxFPkM0w==", + "dev": true + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "3.0.0" + } + }, + "table": { + "version": "5.4.6", + "resolved": "https://registry.npmjs.org/table/-/table-5.4.6.tgz", + "integrity": "sha512-wmEc8m4fjnob4gt5riFRtTu/6+4rSe12TpAELNSqHMfF3IqnA+CH37USM6/YR3qRZv7e56kAEAtd6nKZaxe0Ug==", + "dev": true, + "requires": { + "ajv": "6.12.2", + "lodash": "4.17.15", + "slice-ansi": "2.1.0", + "string-width": "3.1.0" + }, + "dependencies": { + "emoji-regex": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", + "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", + "dev": true + }, + "string-width": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", + "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", + "dev": true, + "requires": { + "emoji-regex": "7.0.3", + "is-fullwidth-code-point": "2.0.0", + "strip-ansi": "5.2.0" + } + } + } + }, + "text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=", + "dev": true + }, + "through": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", + "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=", + "dev": true + }, + "tmp": { + "version": "0.0.33", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", + "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", + "dev": true, + "requires": { + "os-tmpdir": "1.0.2" + } + }, + "to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "requires": { + "is-number": "7.0.0" + } + }, + "tree-kill": { + "version": "file:third_party/tree-kill", + "integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==" + }, + "tslib": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.13.0.tgz", + "integrity": "sha512-i/6DQjL8Xf3be4K/E6Wgpekn5Qasl1usyw++dAA35Ue5orEn65VIxOA+YvNNl9HV3qv70T7CNwjODHZrLwvd1Q==", + "dev": true + }, + "tsutils": { + "version": "3.17.1", + "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.17.1.tgz", + "integrity": "sha512-kzeQ5B8H3w60nFY2g8cJIuH7JDpsALXySGtwGJ0p2LSjLgay3NdIpqq5SoOBe46bKDW2iq25irHCr8wjomUS2g==", + "dev": true, + "requires": { + "tslib": "1.13.0" + } + }, + "type-check": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", + "integrity": "sha1-WITKtRLPHTVeP7eE8wgEsrUg23I=", + "dev": true, + "requires": { + "prelude-ls": "1.1.2" + } + }, + "type-fest": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", + "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", + "dev": true + }, + "typescript": { + "version": "3.9.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.9.5.tgz", + "integrity": "sha512-hSAifV3k+i6lEoCJ2k6R2Z/rp/H3+8sdmcn5NrS3/3kE7+RyZXm9aqvxWqjEXHAd8b0pShatpcdMTvEdvAJltQ==", + "dev": true + }, + "uri-js": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz", + "integrity": "sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ==", + "dev": true, + "requires": { + "punycode": "2.1.1" + } + }, + "v8-compile-cache": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.1.1.tgz", + "integrity": "sha512-8OQ9CL+VWyt3JStj7HX7/ciTL2V3Rl1Wf5OL+SNTm0yK1KvtReVulksyeRnCANHHuUxHlQig+JJDlUhBt1NQDQ==", + "dev": true + }, + "vscode-debugadapter": { + "version": "1.41.0", + "resolved": "https://registry.npmjs.org/vscode-debugadapter/-/vscode-debugadapter-1.41.0.tgz", + "integrity": "sha512-b+J8wmsa3NCxJ+L9DAMpRfPM+8bmp4gFBoFp9lhkpwqn3UMs3sYvdcwugQr/T4lDaCCEr807HKMppRsD1EHhPQ==", + "requires": { + "mkdirp": "0.5.5", + "vscode-debugprotocol": "1.41.0" + } + }, + "vscode-debugprotocol": { + "version": "1.41.0", + "resolved": "https://registry.npmjs.org/vscode-debugprotocol/-/vscode-debugprotocol-1.41.0.tgz", + "integrity": "sha512-Sxp7kDDuhpEZiDaIfhM0jLF3RtMqvc6CpoESANE77t351uezsd/oDoqALLcOnmmsDzTgQ3W0sCvM4gErnjDFpA==" + }, + "vscode-jsonrpc": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/vscode-jsonrpc/-/vscode-jsonrpc-5.0.1.tgz", + "integrity": "sha512-JvONPptw3GAQGXlVV2utDcHx0BiY34FupW/kI6mZ5x06ER5DdPG/tXWMVHjTNULF5uKPOUUD0SaXg5QaubJL0A==" + }, + "vscode-languageclient": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/vscode-languageclient/-/vscode-languageclient-6.1.0.tgz", + "integrity": "sha512-Tcp0VoOaa0YzxL4nEfK9tsmcy76Eo8jNLvFQZwh2c8oMm02luL8uGYPLQNAiZ3XGgegfcwiQFZMqbW7DNV0vxA==", + "requires": { + "semver": "6.3.0", + "vscode-languageserver-protocol": "3.15.3" + }, + "dependencies": { + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==" + } + } + }, + "vscode-languageserver-protocol": { + "version": "3.15.3", + "resolved": "https://registry.npmjs.org/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.15.3.tgz", + "integrity": "sha512-zrMuwHOAQRhjDSnflWdJG+O2ztMWss8GqUUB8dXLR/FPenwkiBNkMIJJYfSN6sgskvsF0rHAoBowNQfbyZnnvw==", + "requires": { + "vscode-jsonrpc": "5.0.1", + "vscode-languageserver-types": "3.15.1" + } + }, + "vscode-languageserver-types": { + "version": "3.15.1", + "resolved": "https://registry.npmjs.org/vscode-languageserver-types/-/vscode-languageserver-types-3.15.1.tgz", + "integrity": "sha512-+a9MPUQrNGRrGU630OGbYVQ+11iOIovjCkqxajPa9w57Sd5ruK8WQNsslzpa0x/QJqC8kRc2DUxWjIFwoNm4ZQ==" + }, + "vscode-test": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/vscode-test/-/vscode-test-1.4.0.tgz", + "integrity": "sha512-Jt7HNGvSE0+++Tvtq5wc4hiXLIr2OjDShz/gbAfM/mahQpy4rKBnmOK33D+MR67ATWviQhl+vpmU3p/qwSH/Pg==", + "dev": true, + "requires": { + "http-proxy-agent": "2.1.0", + "https-proxy-agent": "2.2.4", + "rimraf": "2.6.3" + } + }, + "which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "dev": true, + "requires": { + "isexe": "2.0.0" + } + }, + "which-module": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", + "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=", + "dev": true + }, + "wide-align": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz", + "integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==", + "dev": true, + "requires": { + "string-width": "2.1.1" + }, + "dependencies": { + "ansi-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", + "dev": true + }, + "string-width": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", + "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", + "dev": true, + "requires": { + "is-fullwidth-code-point": "2.0.0", + "strip-ansi": "4.0.0" + } + }, + "strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "dev": true, + "requires": { + "ansi-regex": "3.0.0" + } + } + } + }, + "word-wrap": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", + "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", + "dev": true + }, + "wrap-ansi": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-5.1.0.tgz", + "integrity": "sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q==", + "dev": true, + "requires": { + "ansi-styles": "3.2.1", + "string-width": "3.1.0", + "strip-ansi": "5.2.0" + }, + "dependencies": { + "emoji-regex": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", + "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", + "dev": true + }, + "string-width": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", + "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", + "dev": true, + "requires": { + "emoji-regex": "7.0.3", + "is-fullwidth-code-point": "2.0.0", + "strip-ansi": "5.2.0" + } + } + } + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", + "dev": true + }, + "write": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/write/-/write-1.0.3.tgz", + "integrity": "sha512-/lg70HAjtkUgWPVZhZcm+T4hkL8Zbtp1nFNOn3lRrxnlv50SRBv7cR7RqR+GMsd3hUXy9hWBo4CHTbFTcOYwig==", + "dev": true, + "requires": { + "mkdirp": "0.5.5" + } + }, + "y18n": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.0.tgz", + "integrity": "sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w==", + "dev": true + }, + "yargs": { + "version": "13.3.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-13.3.2.tgz", + "integrity": "sha512-AX3Zw5iPruN5ie6xGRIDgqkT+ZhnRlZMLMHAs8tg7nRruy2Nb+i5o9bwghAogtM08q1dpr2LVoS8KSTMYpWXUw==", + "dev": true, + "requires": { + "cliui": "5.0.0", + "find-up": "3.0.0", + "get-caller-file": "2.0.5", + "require-directory": "2.1.1", + "require-main-filename": "2.0.0", + "set-blocking": "2.0.0", + "string-width": "3.1.0", + "which-module": "2.0.0", + "y18n": "4.0.0", + "yargs-parser": "13.1.2" + }, + "dependencies": { + "emoji-regex": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", + "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", + "dev": true + }, + "string-width": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", + "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", + "dev": true, + "requires": { + "emoji-regex": "7.0.3", + "is-fullwidth-code-point": "2.0.0", + "strip-ansi": "5.2.0" + } + } + } + }, + "yargs-parser": { + "version": "13.1.2", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-13.1.2.tgz", + "integrity": "sha512-3lbsNRf/j+A4QuSZfDRA7HRSfWrzO0YjqTJd5kjAq37Zep1CEgaYmrH9Q3GwPiB9cHyd1Y1UwggGhJGoxipbzg==", + "dev": true, + "requires": { + "camelcase": "5.3.1", + "decamelize": "1.2.0" + } + }, + "yargs-unparser": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-1.6.0.tgz", + "integrity": "sha512-W9tKgmSn0DpSatfri0nx52Joq5hVXgeLiqR/5G0sZNDoLZFOr/xjBUDcShCOGNsBnEMNo1KAMBkTej1Hm62HTw==", + "dev": true, + "requires": { + "flat": "4.1.0", + "lodash": "4.17.15", + "yargs": "13.3.2" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..f7f27b9 --- /dev/null +++ b/package.json @@ -0,0 +1,167 @@ +{ + "name": "goplus", + "displayName": "goplus", + "description": "GoPlus", + "version": "0.0.10", + "engines": { + "vscode": "^1.46.0" + }, + "publisher": "wyvern", + "activationEvents": [ + "onLanguage:gop" + ], + "license": "MIT", + "icon": "images/icon.png", + "main": "./out/gopMain", + "categories": [ + "Programming Languages", + "Themes", + "Formatters" + ], + "repository": { + "type": "git", + "url": "https://github.com/gopcode/vscode-goplus" + }, + "scripts": { + "vscode:prepublish": "npm run compile", + "compile": "tsc -p ./", + "watch": "tsc -watch -p ./" + }, + "contributes": { + "languages": [ + { + "id": "gop", + "aliases": [ + "goplus", + "gop" + ], + "extensions": [ + ".gop" + ], + "configuration": "./language-configuration.json" + } + ], + "grammars": [ + { + "language": "gop", + "scopeName": "source.gop", + "path": "./syntaxes/gop.tmLanguage.json" + } + ], + "snippets": [ + { + "language": "gop", + "path": "./snippets/goplus.json" + } + ], + "configurationDefaults": { + "[gop]": { + "editor.insertSpaces": false, + "editor.formatOnSave": true, + "editor.codeActionsOnSave": { + "source.organizeImports": true + } + } + }, + "configuration": { + "type": "object", + "title": "GoPlus", + "properties": { + "goplus.formatTool": { + "type": "string", + "default": "qfmt", + "description": "Pick 'qfmt' to run on format.", + "scope": "resource", + "enum": [ + "qfmt" + ] + }, + "goplus.formatFlags": { + "type": "array", + "items": { + "type": "string" + }, + "default": [], + "description": "Flags to pass to format tool (e.g. [\"-w\"])", + "scope": "resource" + }, + "goplus.toolsEnvVars": { + "type": "object", + "default": {}, + "description": "Environment variables that will passed to the processes that run the Go tools (e.g. CGO_CFLAGS)", + "scope": "resource" + }, + "goplus.alternateTools": { + "type": "object", + "default": {}, + "description": "Alternate tools or alternate paths for the same tools used by the Go extension. Provide either absolute path or the name of the binary in GOPATH/bin, GOROOT/bin or PATH. Useful when you want to use wrapper script for the Go tools or versioned tools from https://gopkg.in.", + "scope": "resource", + "properties": { + "qfmt": { + "type": "string", + "default": "qfmt", + "description": "Alternate tool to use instead of the qfmt binary or alternate path to use for the qfmt binary." + } + } + }, + "goplus.docsTool": { + "type": "string", + "default": "godoc", + "description": "Pick 'godoc' or 'gogetdoc' to get documentation. Not applicable when using the language server.", + "scope": "resource", + "enum": [ + "godoc" + ] + }, + "goplus.gocodeFlags": { + "type": "array", + "items": { + "type": "string" + }, + "default": [ + "-builtin", + "-ignore-case" + ], + "description": "Additional flags to pass to gocode. Not applicable when using the language server.", + "scope": "resource" + } + } + }, + "commands": [ + { + "command": "goplus.import.add", + "title": "GoPlus: Add Import", + "description": "Add an import declaration" + } + ], + "iconThemes": [ + { + "id": "vs-goplus", + "label": "GoPlus (Visual Studio Code)", + "path": "./fileicons/vs_goplus-icons-theme.json" + } + ] + }, + "dependencies": { + "vscode-debugadapter": "^1.40.0", + "vscode-debugprotocol": "^1.40.0", + "vscode-languageclient": "6.1.0", + "moment": "^2.24.0", + "semver": "^7.3.2", + "tree-kill": "file:third_party/tree-kill" + }, + "devDependencies": { + "@types/vscode": "^1.46.0", + "@types/glob": "^7.1.1", + "@types/mocha": "^7.0.2", + "@types/node": "^13.11.0", + "@types/semver": "^7.1.0", + "eslint": "^6.8.0", + "@typescript-eslint/parser": "^2.30.0", + "@typescript-eslint/eslint-plugin": "^2.30.0", + "glob": "^7.1.6", + "mocha": "^7.1.2", + "typescript": "^3.8.3", + "vscode-test": "^1.3.0" + } +} \ No newline at end of file diff --git a/snippets/goplus.json b/snippets/goplus.json new file mode 100644 index 0000000..bc9c6af --- /dev/null +++ b/snippets/goplus.json @@ -0,0 +1,269 @@ +{ + ".source.go": { + "single import": { + "prefix": "im", + "body": "import \"${1:package}\"", + "description": "Snippet for import statement" + }, + "multiple imports": { + "prefix": "ims", + "body": "import (\n\t\"${1:package}\"\n)", + "description": "Snippet for a import block" + }, + "single constant": { + "prefix": "co", + "body": "const ${1:name} = ${2:value}", + "description": "Snippet for a constant" + }, + "multiple constants": { + "prefix": "cos", + "body": "const (\n\t${1:name} = ${2:value}\n)", + "description": "Snippet for a constant block" + }, + "type interface declaration": { + "prefix": "tyi", + "body": "type ${1:name} interface {\n\t$0\n}", + "description": "Snippet for a type interface" + }, + "type struct declaration": { + "prefix": "tys", + "body": "type ${1:name} struct {\n\t$0\n}", + "description": "Snippet for a struct declaration" + }, + "package main and main function": { + "prefix": "pkgm", + "body": "package main\n\nfunc main() {\n\t$0\n}", + "description": "Snippet for main package & function" + }, + "function declaration": { + "prefix": "func", + "body": "func $1($2) $3 {\n\t$0\n}", + "description": "Snippet for function declaration" + }, + "variable declaration": { + "prefix": "var", + "body": "var ${1:name} ${2:type}", + "description": "Snippet for a variable" + }, + "switch statement": { + "prefix": "switch", + "body": "switch ${1:expression} {\ncase ${2:condition}:\n\t$0\n}", + "description": "Snippet for switch statement" + }, + "select statement": { + "prefix": "sel", + "body": "select {\ncase ${1:condition}:\n\t$0\n}", + "description": "Snippet for select statement" + }, + "case clause": { + "prefix": "cs", + "body": "case ${1:condition}:$0", + "description": "Snippet for case clause" + }, + "for statement": { + "prefix": "for", + "body": "for ${1:i} := 0; $1 < ${2:count}; $1${3:++} {\n\t$0\n}", + "description": "Snippet for a for loop" + }, + "for range statement": { + "prefix": "forr", + "body": "for ${1:_, }${2:var} := range ${3:var} {\n\t$0\n}", + "description": "Snippet for a for range loop" + }, + "for arrow statement": { + "prefix": "for-", + "body": "for ${1:i}, ${2:j} <- ${3:k} {\n\t$0\n}", + "description": "Snippet for a for arrow loop" + }, + "channel declaration": { + "prefix": "ch", + "body": "chan ${1:type}", + "description": "Snippet for a channel" + }, + "map declaration": { + "prefix": "map", + "body": "map[${1:type}]${2:type}", + "description": "Snippet for a map" + }, + "empty interface": { + "prefix": "in", + "body": "interface{}", + "description": "Snippet for empty interface" + }, + "if statement": { + "prefix": "if", + "body": "if ${1:condition} {\n\t$0\n}", + "description": "Snippet for if statement" + }, + "else branch": { + "prefix": "el", + "body": "else {\n\t$0\n}", + "description": "Snippet for else branch" + }, + "if else statement": { + "prefix": "ie", + "body": "if ${1:condition} {\n\t$2\n} else {\n\t$0\n}", + "description": "Snippet for if else" + }, + "if err != nil": { + "prefix": "iferr", + "body": "if err != nil {\n\t${1:return ${2:nil, }${3:err}}\n}", + "description": "Snippet for if err != nil" + }, + "println": { + "prefix": "pr", + "body": "println(\"$1\")", + "description": "Snippet for println()" + }, + "fmt.Println": { + "prefix": "fp", + "body": "fmt.Println(\"$1\")", + "description": "Snippet for fmt.Println()" + }, + "fmt.Printf": { + "prefix": "ff", + "body": "fmt.Printf(\"$1\", ${2:var})", + "description": "Snippet for fmt.Printf()" + }, + "log.Println": { + "prefix": "lp", + "body": "log.Println(\"$1\")", + "description": "Snippet for log.Println()" + }, + "log.Printf": { + "prefix": "lf", + "body": "log.Printf(\"$1\", ${2:var})", + "description": "Snippet for log.Printf()" + }, + "log variable content": { + "prefix": "lv", + "body": "log.Printf(\"${1:var}: %#+v\\\\n\", ${1:var})", + "description": "Snippet for log.Printf() with variable content" + }, + "t.Log": { + "prefix": "tl", + "body": "t.Log(\"$1\")", + "description": "Snippet for t.Log()" + }, + "t.Logf": { + "prefix": "tlf", + "body": "t.Logf(\"$1\", ${2:var})", + "description": "Snippet for t.Logf()" + }, + "t.Logf variable content": { + "prefix": "tlv", + "body": "t.Logf(\"${1:var}: %#+v\\\\n\", ${1:var})", + "description": "Snippet for t.Logf() with variable content" + }, + "make(...)": { + "prefix": "make", + "body": "make(${1:type}, ${2:0})", + "description": "Snippet for make statement" + }, + "new(...)": { + "prefix": "new", + "body": "new(${1:type})", + "description": "Snippet for new statement" + }, + "panic(...)": { + "prefix": "pn", + "body": "panic(\"$0\")", + "description": "Snippet for panic" + }, + "http ResponseWriter *Request": { + "prefix": "wr", + "body": "${1:w} http.ResponseWriter, ${2:r} *http.Request", + "description": "Snippet for http Response" + }, + "http.HandleFunc": { + "prefix": "hf", + "body": "${1:http}.HandleFunc(\"${2:/}\", ${3:handler})", + "description": "Snippet for http.HandleFunc()" + }, + "http handler declaration": { + "prefix": "hand", + "body": "func $1(${2:w} http.ResponseWriter, ${3:r} *http.Request) {\n\t$0\n}", + "description": "Snippet for http handler declaration" + }, + "http.Redirect": { + "prefix": "rd", + "body": "http.Redirect(${1:w}, ${2:r}, \"${3:/}\", ${4:http.StatusFound})", + "description": "Snippet for http.Redirect()" + }, + "http.Error": { + "prefix": "herr", + "body": "http.Error(${1:w}, ${2:err}.Error(), ${3:http.StatusInternalServerError})", + "description": "Snippet for http.Error()" + }, + "http.ListenAndServe": { + "prefix": "las", + "body": "http.ListenAndServe(\"${1::8080}\", ${2:nil})", + "description": "Snippet for http.ListenAndServe" + }, + "http.Serve": { + "prefix": "sv", + "body": "http.Serve(\"${1::8080}\", ${2:nil})", + "description": "Snippet for http.Serve" + }, + "goroutine anonymous function": { + "prefix": "go", + "body": "go func($1) {\n\t$0\n}($2)", + "description": "Snippet for anonymous goroutine declaration" + }, + "goroutine function": { + "prefix": "gf", + "body": "go ${1:func}($0)", + "description": "Snippet for goroutine declaration" + }, + "defer statement": { + "prefix": "df", + "body": "defer ${1:func}($0)", + "description": "Snippet for defer statement" + }, + "test function": { + "prefix": "tf", + "body": "func Test$1(t *testing.T) {\n\t$0\n}", + "description": "Snippet for Test function" + }, + "benchmark function": { + "prefix": "bf", + "body": "func Benchmark$1(b *testing.B) {\n\tfor ${2:i} := 0; ${2:i} < b.N; ${2:i}++ {\n\t\t$0\n\t}\n}", + "description": "Snippet for Benchmark function" + }, + "example function": { + "prefix": "ef", + "body": "func Example$1() {\n\t$2\n\t//Output:\n\t$3\n}", + "description": "Snippet for Example function" + }, + "table driven test": { + "prefix": "tdt", + "body": "func Test$1(t *testing.T) {\n\ttestCases := []struct {\n\t\tdesc\tstring\n\t\t$2\n\t}{\n\t\t{\n\t\t\tdesc: \"$3\",\n\t\t\t$4\n\t\t},\n\t}\n\tfor _, tC := range testCases {\n\t\tt.Run(tC.desc, func(t *testing.T) {\n\t\t\t$0\n\t\t})\n\t}\n}", + "description": "Snippet for table driven test" + }, + "init function": { + "prefix": "finit", + "body": "func init() {\n\t$1\n}", + "description": "Snippet for init function" + }, + "main function": { + "prefix": "fmain", + "body": "func main() {\n\t$1\n}", + "description": "Snippet for main function" + }, + "method declaration": { + "prefix": "meth", + "body": "func (${1:receiver} ${2:type}) ${3:method}($4) $5 {\n\t$0\n}", + "description": "Snippet for method declaration" + }, + "hello world web app": { + "prefix": "helloweb", + "body": "package main\n\nimport (\n\t\"fmt\"\n\t\"net/http\"\n\t\"time\"\n)\n\nfunc greet(w http.ResponseWriter, r *http.Request) {\n\tfmt.Fprintf(w, \"Hello World! %s\", time.Now())\n}\n\nfunc main() {\n\thttp.HandleFunc(\"/\", greet)\n\thttp.ListenAndServe(\":8080\", nil)\n}", + "description": "Snippet for sample hello world webapp" + }, + "sort implementation": { + "prefix": "sort", + "body": "type ${1:SortBy} []${2:Type}\n\nfunc (a $1) Len() int { return len(a) }\nfunc (a $1) Swap(i, j int) { a[i], a[j] = a[j], a[i] }\nfunc (a $1) Less(i, j int) bool { ${3:return a[i] < a[j]} }", + "description": "Snippet for a custom sort.Sort interface implementation, for a given slice type." + } + } +} \ No newline at end of file diff --git a/src/avlTree.ts b/src/avlTree.ts new file mode 100644 index 0000000..b1654e7 --- /dev/null +++ b/src/avlTree.ts @@ -0,0 +1,233 @@ +export class Node { + public left: Node | null = null; + public right: Node | null = null; + public height: number = 0; + + /** + * Creates a new AVL Tree node. + * @param key The key of the new node. + * @param value The value of the new node. + */ + constructor(public key: K, public value: V | undefined) {} + + /** + * Convenience function to get the height of the left child of the node, + * returning -1 if the node is null. + * @return The height of the left child, or -1 if it doesn't exist. + */ + public get leftHeight(): number { + if (!this.left) { + return -1; + } + return this.left.height; + } + + /** + * Convenience function to get the height of the right child of the node, + * returning -1 if the node is null. + * @return The height of the right child, or -1 if it doesn't exist. + */ + public get rightHeight(): number { + if (!this.right) { + return -1; + } + return this.right.height; + } + + /** + * Performs a right rotate on this node. + * @return The root of the sub-tree; the node where this node used to be. + */ + public rotateRight(): Node { + // b a + // / \ / \ + // a e -> b.rotateRight() -> c b + // / \ / \ + // c d d e + const other = >this.left; + this.left = other.right; + other.right = this; + this.height = Math.max(this.leftHeight, this.rightHeight) + 1; + other.height = Math.max(other.leftHeight, this.height) + 1; + return other; + } + + /** + * Performs a left rotate on this node. + * @return The root of the sub-tree; the node where this node used to be. + */ + public rotateLeft(): Node { + // a b + // / \ / \ + // c b -> a.rotateLeft() -> a e + // / \ / \ + // d e c d + const other = >this.right; + this.right = other.left; + other.left = this; + this.height = Math.max(this.leftHeight, this.rightHeight) + 1; + other.height = Math.max(other.rightHeight, this.height) + 1; + return other; + } +} + +export type DistanceFunction = (a: K, b: K) => number; +export type CompareFunction = (a: K, b: K) => number; + +/** + * Represents how balanced a node's left and right children are. + */ +const enum BalanceState { + /** Right child's height is 2+ greater than left child's height */ + UNBALANCED_RIGHT, + /** Right child's height is 1 greater than left child's height */ + SLIGHTLY_UNBALANCED_RIGHT, + /** Left and right children have the same height */ + BALANCED, + /** Left child's height is 1 greater than right child's height */ + SLIGHTLY_UNBALANCED_LEFT, + /** Left child's height is 2+ greater than right child's height */ + UNBALANCED_LEFT +} + +export class NearestNeighborDict { + public static NUMERIC_DISTANCE_FUNCTION = (a: number, b: number) => (a > b ? a - b : b - a); + public static DEFAULT_COMPARE_FUNCTION = (a: any, b: any) => (a > b ? 1 : a < b ? -1 : 0); + + protected root: Node|null = null; + + /** + * Creates a new AVL Tree. + */ + constructor( + start: Node, + private distance: DistanceFunction, + private compare: CompareFunction = NearestNeighborDict.DEFAULT_COMPARE_FUNCTION + ) { + this.insert(start.key, start.value); + } + + public height() { + return this.root ? this.root.height : 0; + } + + /** + * Inserts a new node with a specific key into the tree. + * @param key The key being inserted. + * @param value The value being inserted. + */ + public insert(key: K, value?: V): void { + this.root = this._insert(key, value, this.root); + } + + /** + * Gets a node within the tree with a specific key, or the nearest neighbor to that node if it does not exist. + * @param key The key being searched for. + * @return The (key, value) pair of the node with key nearest the given key in value. + */ + public getNearest(key: K): Node { + return this._getNearest(key, this.root!, this.root!); + } + + /** + * Inserts a new node with a specific key into the tree. + * @param key The key being inserted. + * @param root The root of the tree to insert in. + * @return The new tree root. + */ + private _insert(key: K, value: V | undefined, root: Node | null): Node { + // Perform regular BST insertion + if (root === null) { + return new Node(key, value); + } + + if (this.compare(key, root.key) < 0) { + root.left = this._insert(key, value, root.left); + } else if (this.compare(key, root.key) > 0) { + root.right = this._insert(key, value, root.right); + } else { + return root; + } + + // Update height and rebalance tree + root.height = Math.max(root.leftHeight, root.rightHeight) + 1; + const balanceState = this._getBalanceState(root); + + if (balanceState === BalanceState.UNBALANCED_LEFT) { + if (this.compare(key, root.left!.key) < 0) { + // Left left case + root = root.rotateRight(); + } else { + // Left right case + root.left = root.left!.rotateLeft(); + return root.rotateRight(); + } + } + + if (balanceState === BalanceState.UNBALANCED_RIGHT) { + if (this.compare(key, root.right!.key) > 0) { + // Right right case + root = root.rotateLeft(); + } else { + // Right left case + root.right = root.right!.rotateRight(); + return root.rotateLeft(); + } + } + + return root; + } + + /** + * Gets a node within the tree with a specific key, or the node closest (as measured by this._distance) + * to that node if the key is not present + * @param key The key being searched for. + * @param root The root of the tree to search in. + * @param closest The current best estimate of the node closest to the node being searched for, + * as measured by this._distance + * @return The (key, value) pair of the node with key nearest the given key in value. + */ + private _getNearest(key: K, root: Node, closest: Node): Node { + const result = this.compare(key, root.key); + if (result === 0) { + return root; + } + + closest = this.distance(key, root.key) < this.distance(key, closest.key) ? root : closest; + + if (result < 0) { + return root.left ? this._getNearest(key, root.left, closest) : closest; + } else { + return root.right ? this._getNearest(key, root.right, closest) : closest; + } + } + + /** + * Gets the balance state of a node, indicating whether the left or right + * sub-trees are unbalanced. + * @param node The node to get the difference from. + * @return The BalanceState of the node. + */ + private _getBalanceState(node: Node): BalanceState { + const heightDifference = node.leftHeight - node.rightHeight; + switch (heightDifference) { + case -2: + return BalanceState.UNBALANCED_RIGHT; + case -1: + return BalanceState.SLIGHTLY_UNBALANCED_RIGHT; + case 1: + return BalanceState.SLIGHTLY_UNBALANCED_LEFT; + case 2: + return BalanceState.UNBALANCED_LEFT; + case 0: + return BalanceState.BALANCED; + default: { + console.error('Internal error: Avl tree should never be more than two levels unbalanced'); + if (heightDifference > 0) { + return BalanceState.UNBALANCED_LEFT; + } + return BalanceState.UNBALANCED_RIGHT; // heightDifference can't be 0 + } + } + } +} diff --git a/src/goDeclaration.ts b/src/goDeclaration.ts new file mode 100644 index 0000000..19a123d --- /dev/null +++ b/src/goDeclaration.ts @@ -0,0 +1,251 @@ +/*--------------------------------------------------------- + * Copyright (C) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See LICENSE in the project root for license information. + *--------------------------------------------------------*/ + +'use strict'; + +import cp = require('child_process'); +import path = require('path'); +import vscode = require('vscode'); +import { toolExecutionEnvironment } from './goEnv'; +import { promptForMissingTool, promptForUpdatingTool } from './gopInstallTools'; +import { getModFolderPath, promptToUpdateToolForModules } from './goModules'; +import { + byteOffsetAt, + getBinPath, + getGoPlusConfig, + getModuleCache, + getWorkspaceFolderPath, + goKeywords, + isPositionInString, + killTree, + runGodoc +} from './util'; + + + +export interface GoDefinitionInformation { + file: string; + line: number; + column: number; + doc: string; + declarationlines: string[]; + name: string; + toolUsed: string; +} + +interface GoDefinitionInput { + document: vscode.TextDocument; + position: vscode.Position; + word: string; + includeDocs: boolean; + isMod: boolean; + cwd: string; +} + +export function definitionLocation( + document: vscode.TextDocument, + position: vscode.Position, + goPlusConfig: vscode.WorkspaceConfiguration, + includeDocs: boolean, + token: vscode.CancellationToken +): Promise { + const adjustedPos = adjustWordPosition(document, position); + if (!adjustedPos[0]) { + return Promise.resolve(null); + } + const word = adjustedPos[1]; + position = adjustedPos[2]; + + if (!goPlusConfig) { + goPlusConfig = getGoPlusConfig(document.uri); + } + const toolForDocs = goPlusConfig['docsTool'] || 'godoc'; + return getModFolderPath(document.uri).then((modFolderPath) => { + const input: GoDefinitionInput = { + document, + position, + word, + includeDocs, + isMod: !!modFolderPath, + cwd: + modFolderPath && modFolderPath !== getModuleCache() + ? modFolderPath + : getWorkspaceFolderPath(document.uri) || path.dirname(document.fileName) + }; + return definitionLocation_godef(input, token); + }); +} + +const missingToolMsg = 'Missing tool: '; + +export function adjustWordPosition( + document: vscode.TextDocument, + position: vscode.Position +): [boolean, string, vscode.Position] { + const wordRange = document.getWordRangeAtPosition(position); + const lineText = document.lineAt(position.line).text; + const word = wordRange ? document.getText(wordRange) : ''; + if ( + !wordRange || + lineText.startsWith('//') || + isPositionInString(document, position) || + word.match(/^\d+.?\d+$/) || + goKeywords.indexOf(word) > 0 + ) { + return [false, null, null]; + } + if (position.isEqual(wordRange.end) && position.isAfter(wordRange.start)) { + position = position.translate(0, -1); + } + + return [true, word, position]; +} + +const godefImportDefinitionRegex = /^import \(.* ".*"\)$/; +function definitionLocation_godef( + input: GoDefinitionInput, + token: vscode.CancellationToken, + useReceivers: boolean = true +): Promise { + const godefTool = 'godef'; + const godefPath = getBinPath(godefTool); + if (!path.isAbsolute(godefPath)) { + return Promise.reject(missingToolMsg + godefTool); + } + let offset = byteOffsetAt(input.document, input.position); + let inputText = input.document.getText(); + + if (!inputText.match(/package\s+(\w+)/)) { + let addtText = "package main\r\n\r\n" + offset = offset +addtText.length + inputText = addtText + inputText + } + const env = toolExecutionEnvironment(); + let p: cp.ChildProcess; + if (token) { + token.onCancellationRequested(() => killTree(p.pid)); + } + + return new Promise((resolve, reject) => { + // Spawn `godef` process + const args = ['-t', '-i', '-f', input.document.fileName, '-o', offset.toString()]; + // if (useReceivers) { + // args.push('-r'); + // } + + console.log(godefPath,args); + p = cp.execFile(godefPath, args, { env, cwd: input.cwd }, (err, stdout, stderr) => { + try { + if (err && (err).code === 'ENOENT') { + return reject(missingToolMsg + godefTool); + } + if (err) { + if ( + input.isMod && + !input.includeDocs && + stderr && + stderr.startsWith(`godef: no declaration found for`) + ) { + promptToUpdateToolForModules( + 'godef', + `To get the Go to Definition feature when using Go modules, please update your version of the "godef" tool.` + ); + return reject(stderr); + } + if (stderr.indexOf('flag provided but not defined: -r') !== -1) { + promptForUpdatingTool('godef'); + p = null; + return definitionLocation_godef(input, token, false).then(resolve, reject); + } + return reject(err.message || stderr); + } + const result = stdout.toString(); + const lines = result.split('\n'); + let match = /(.*):(\d+):(\d+)/.exec(lines[0]); + if (!match) { + // TODO: Gotodef on pkg name: + // /usr/local/go/src/html/template\n + console.log("not match") + return resolve(null); + } + const [_, file, line, col] = match; + const pkgPath = path.dirname(file); + const definitionInformation: GoDefinitionInformation = { + file, + line: +line - 1, + column: +col - 1, + declarationlines: lines.slice(1), + toolUsed: 'godef', + doc: null, + name: null + }; + if (!input.includeDocs || godefImportDefinitionRegex.test(definitionInformation.declarationlines[0])) { + return resolve(definitionInformation); + } + match = /^\w+ \(\*?(\w+)\)/.exec(lines[1]); + runGodoc(input.cwd, pkgPath, match ? match[1] : '', input.word, token) + .then((doc) => { + if (doc) { + definitionInformation.doc = doc; + } + resolve(definitionInformation); + }) + .catch((runGoDocErr) => { + resolve(definitionInformation); + }); + } catch (e) { + reject(e); + } + }); + if (p.pid) { + p.stdin.end(inputText); + } + }); +} + +export class GoDefinitionProvider implements vscode.DefinitionProvider { + private goConfig: vscode.WorkspaceConfiguration = null; + + constructor(goConfig?: vscode.WorkspaceConfiguration) { + this.goConfig = goConfig; + } + + public provideDefinition( + document: vscode.TextDocument, + position: vscode.Position, + token: vscode.CancellationToken + ): Thenable { + return definitionLocation(document, position, this.goConfig, false, token).then( + (definitionInfo) => { + if (definitionInfo == null || definitionInfo.file == null) { + return null; + } + const definitionResource = vscode.Uri.file(definitionInfo.file); + const pos = new vscode.Position(definitionInfo.line, definitionInfo.column); + return new vscode.Location(definitionResource, pos); + }, + (err) => { + const miss = parseMissingError(err); + if (miss[0]) { + promptForMissingTool(miss[1]); + } else if (err) { + return Promise.reject(err); + } + return Promise.resolve(null); + } + ); + } +} + +export function parseMissingError(err: any): [boolean, string] { + if (err) { + // Prompt for missing tool is located here so that the + // prompts dont show up on hover or signature help + if (typeof err === 'string' && err.startsWith(missingToolMsg)) { + return [true, err.substr(missingToolMsg.length)]; + } + } + return [false, null]; +} \ No newline at end of file diff --git a/src/goEnv.ts b/src/goEnv.ts new file mode 100644 index 0000000..a2be1ac --- /dev/null +++ b/src/goEnv.ts @@ -0,0 +1,69 @@ +/*--------------------------------------------------------- + * Copyright (C) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See LICENSE in the project root for license information. + *--------------------------------------------------------*/ + +'use strict'; + +import path = require('path'); +import vscode = require('vscode'); +import { getCurrentGoPath, getGoPlusConfig, getToolsGopath } from './util'; + +// toolInstallationEnvironment returns the environment in which tools should +// be installed. It always returns a new object. +export function toolInstallationEnvironment(): NodeJS.Dict { + const env = newEnvironment(); + + // If the go.toolsGopath is set, use its value as the GOPATH for `go` processes. + // Else use the Current Gopath + let toolsGopath = getToolsGopath(); + if (toolsGopath) { + // User has explicitly chosen to use toolsGopath, so ignore GOBIN. + env['GOBIN'] = ''; + } else { + toolsGopath = getCurrentGoPath(); + } + if (!toolsGopath) { + const msg = 'Cannot install Go tools. Set either go.gopath or go.toolsGopath in settings.'; + vscode.window.showInformationMessage(msg, 'Open User Settings', 'Open Workspace Settings').then((selected) => { + switch (selected) { + case 'Open User Settings': + vscode.commands.executeCommand('workbench.action.openGlobalSettings'); + break; + case 'Open Workspace Settings': + vscode.commands.executeCommand('workbench.action.openWorkspaceSettings'); + break; + } + }); + return; + } + env['GOPATH'] = toolsGopath; + + return env; +} + +// toolExecutionEnvironment returns the environment in which tools should +// be executed. It always returns a new object. +export function toolExecutionEnvironment(): NodeJS.Dict { + const env = newEnvironment(); + const gopath = getCurrentGoPath(); + if (gopath) { + env['GOPATH'] = gopath; + } + return env; +} + +function newEnvironment(): NodeJS.Dict { + const toolsEnvVars = getGoPlusConfig()['toolsEnvVars']; + const env = Object.assign({}, process.env, toolsEnvVars); + + // The http.proxy setting takes precedence over environment variables. + const httpProxy = vscode.workspace.getConfiguration('http', null).get('proxy'); + if (httpProxy && typeof httpProxy === 'string') { + env['http_proxy'] = httpProxy; + env['HTTP_PROXY'] = httpProxy; + env['https_proxy'] = httpProxy; + env['HTTPS_PROXY'] = httpProxy; + } + return env; +} \ No newline at end of file diff --git a/src/goModules.ts b/src/goModules.ts new file mode 100644 index 0000000..fbd118c --- /dev/null +++ b/src/goModules.ts @@ -0,0 +1,170 @@ +import cp = require('child_process'); +import path = require('path'); +import vscode = require('vscode'); +import { toolExecutionEnvironment } from './goEnv'; +import { envPath, fixDriveCasingInWindows, getCurrentGoRoot } from './goPath'; +import { getTool } from './goTools'; +import { installTools } from './gopInstallTools'; +import { + getFromGlobalState, getFromWorkspaceState, setGlobalState, setWorkspaceState, updateGlobalState, + updateWorkspaceState +} from './stateUtils'; +import { getBinPath, getGoConfig, getGoVersion, getModuleCache } from './util'; + +export let GO111MODULE: string; +const folderToPackageMapping: { [key: string]: string } = {}; +export async function getCurrentPackage(cwd: string): Promise { + if (folderToPackageMapping[cwd]) { + return folderToPackageMapping[cwd]; + } + + const moduleCache = getModuleCache(); + if (cwd.startsWith(moduleCache)) { + let importPath = cwd.substr(moduleCache.length + 1); + const matches = /@v\d+(\.\d+)?(\.\d+)?/.exec(importPath); + if (matches) { + importPath = importPath.substr(0, matches.index); + } + + folderToPackageMapping[cwd] = importPath; + return importPath; + } + + const goRuntimePath = getBinPath('go'); + if (!goRuntimePath) { + console.warn( + `Failed to run "go list" to find current package as the "go" binary cannot be found in either GOROOT(${getCurrentGoRoot()}) or PATH(${envPath})` + ); + return; + } + return new Promise((resolve) => { + const childProcess = cp.spawn(goRuntimePath, ['list'], { cwd, env: toolExecutionEnvironment() }); + const chunks: any[] = []; + childProcess.stdout.on('data', (stdout) => { + chunks.push(stdout); + }); + + childProcess.on('close', () => { + // Ignore lines that are empty or those that have logs about updating the module cache + const pkgs = chunks + .join('') + .toString() + .split('\n') + .filter((line) => line && line.indexOf(' ') === -1); + if (pkgs.length !== 1) { + resolve(); + return; + } + folderToPackageMapping[cwd] = pkgs[0]; + resolve(pkgs[0]); + }); + }); +} + +async function runGoModEnv(folderPath: string): Promise { + const goExecutable = getBinPath('go'); + if (!goExecutable) { + console.warn( + `Failed to run "go env GOMOD" to find mod file as the "go" binary cannot be found in either GOROOT(${getCurrentGoRoot()}) or PATH(${envPath})` + ); + return; + } + const env = toolExecutionEnvironment(); + GO111MODULE = env['GO111MODULE']; + return new Promise((resolve) => { + cp.execFile(goExecutable, ['env', 'GOMOD'], { cwd: folderPath, env }, (err, stdout) => { + if (err) { + console.warn(`Error when running go env GOMOD: ${err}`); + return resolve(); + } + const [goMod] = stdout.split('\n'); + resolve(goMod); + }); + }); +} + +let moduleUsageLogged = false; +function logModuleUsage() { + if (moduleUsageLogged) { + return; + } + moduleUsageLogged = true; +} + +export const packagePathToGoModPathMap: { [key: string]: string } = {}; + +export async function getModFolderPath(fileuri: vscode.Uri): Promise { + const pkgPath = path.dirname(fileuri.fsPath); + if (packagePathToGoModPathMap[pkgPath]) { + return packagePathToGoModPathMap[pkgPath]; + } + + // We never would be using the path under module cache for anything + // So, dont bother finding where exactly is the go.mod file + const moduleCache = getModuleCache(); + if (fixDriveCasingInWindows(fileuri.fsPath).startsWith(moduleCache)) { + return moduleCache; + } + const goVersion = await getGoVersion(); + if (goVersion.lt('1.11')) { + return; + } + + let goModEnvResult = await runGoModEnv(pkgPath); + if (goModEnvResult) { + logModuleUsage(); + goModEnvResult = path.dirname(goModEnvResult); + const goConfig = getGoConfig(fileuri); + + if (goConfig['inferGopath'] === true) { + goConfig.update('inferGopath', false, vscode.ConfigurationTarget.WorkspaceFolder); + vscode.window.showInformationMessage( + 'The "inferGopath" setting is disabled for this workspace because Go modules are being used.' + ); + } + } + packagePathToGoModPathMap[pkgPath] = goModEnvResult; + return goModEnvResult; +} + +const promptedToolsForCurrentSession = new Set(); +export async function promptToUpdateToolForModules( + tool: string, + promptMsg: string, + goConfig?: vscode.WorkspaceConfiguration +): Promise { + if (promptedToolsForCurrentSession.has(tool)) { + return false; + } + const promptedToolsForModules = getFromGlobalState('promptedToolsForModules', {}); + if (promptedToolsForModules[tool]) { + return false; + } + const goVersion = await getGoVersion(); + const selected = await vscode.window.showInformationMessage(promptMsg, 'Update', 'Later', `Don't show again`); + let choseToUpdate = false; + switch (selected) { + case 'Update': + choseToUpdate = true; + if (!goConfig) { + goConfig = getGoConfig(); + } + if (tool === 'switchFormatToolToGoimports') { + goConfig.update('formatTool', 'goimports', vscode.ConfigurationTarget.Global); + } else { + await installTools([getTool(tool)], goVersion); + } + promptedToolsForModules[tool] = true; + updateGlobalState('promptedToolsForModules', promptedToolsForModules); + break; + case `Don't show again`: + promptedToolsForModules[tool] = true; + updateGlobalState('promptedToolsForModules', promptedToolsForModules); + break; + case 'Later': + default: + promptedToolsForCurrentSession.add(tool); + break; + } + return choseToUpdate; +} \ No newline at end of file diff --git a/src/goOutline.ts b/src/goOutline.ts new file mode 100644 index 0000000..d248a4a --- /dev/null +++ b/src/goOutline.ts @@ -0,0 +1,193 @@ +/*--------------------------------------------------------- + * Copyright (C) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See LICENSE in the project root for license information. + *--------------------------------------------------------*/ + +'use strict'; + +import cp = require('child_process'); +import vscode = require('vscode'); +import { toolExecutionEnvironment } from './goEnv'; +import { promptForMissingTool, promptForUpdatingTool } from './gopInstallTools'; +import { + getBinPath, + getFileArchive, + getGoConfig, + killProcess, + makeMemoizedByteOffsetConverter +} from './util'; + +// Keep in sync with https://github.com/ramya-rao-a/go-outline +export interface GoOutlineRange { + start: number; + end: number; +} + +export interface GoOutlineDeclaration { + label: string; + type: string; + receiverType?: string; + icon?: string; // icon class or null to use the default images based on the type + start: number; + end: number; + children?: GoOutlineDeclaration[]; + signature?: GoOutlineRange; + comment?: GoOutlineRange; +} + +export enum GoOutlineImportsOptions { + Include, + Exclude, + Only +} + +export interface GoOutlineOptions { + /** + * Path of the file for which outline is needed + */ + fileName: string; + + /** + * Option to decide if the output includes, excludes or only includes imports + * If the option is to only include imports, then the file will be parsed only till imports are collected + */ + importsOption: GoOutlineImportsOptions; + + /** + * Document to be parsed. If not provided, saved contents of the given fileName is used + */ + document?: vscode.TextDocument; +} + +export async function documentSymbols( + options: GoOutlineOptions, + token: vscode.CancellationToken +): Promise { + const decls = await runGoOutline(options, token); + return convertToCodeSymbols( + options.document, + decls, + options.importsOption !== GoOutlineImportsOptions.Exclude, + makeMemoizedByteOffsetConverter(Buffer.from(options.document.getText())) + ); +} + +export function runGoOutline( + options: GoOutlineOptions, + token: vscode.CancellationToken +): Promise { + return new Promise((resolve, reject) => { + const gooutline = getBinPath('go-outline'); + const gooutlineFlags = ['-f', options.fileName]; + if (options.importsOption === GoOutlineImportsOptions.Only) { + gooutlineFlags.push('-imports-only'); + } + if (options.document) { + gooutlineFlags.push('-modified'); + } + + let p: cp.ChildProcess; + if (token) { + token.onCancellationRequested(() => killProcess(p)); + } + + // Spawn `go-outline` process + p = cp.execFile(gooutline, gooutlineFlags, { env: toolExecutionEnvironment() }, (err, stdout, stderr) => { + try { + if (err && (err).code === 'ENOENT') { + promptForMissingTool('go-outline'); + } + if (stderr && stderr.startsWith('flag provided but not defined: ')) { + promptForUpdatingTool('go-outline'); + if (stderr.startsWith('flag provided but not defined: -imports-only')) { + options.importsOption = GoOutlineImportsOptions.Include; + } + if (stderr.startsWith('flag provided but not defined: -modified')) { + options.document = null; + } + p = null; + return runGoOutline(options, token).then((results) => { + return resolve(results); + }); + } + if (err) { + return resolve(null); + } + const result = stdout.toString(); + const decls = JSON.parse(result); + return resolve(decls); + } catch (e) { + reject(e); + } + }); + if (options.document && p.pid) { + p.stdin.end(getFileArchive(options.document)); + } + }); +} + +const goKindToCodeKind: { [key: string]: vscode.SymbolKind } = { + package: vscode.SymbolKind.Package, + import: vscode.SymbolKind.Namespace, + variable: vscode.SymbolKind.Variable, + constant: vscode.SymbolKind.Constant, + type: vscode.SymbolKind.TypeParameter, + function: vscode.SymbolKind.Function, + struct: vscode.SymbolKind.Struct, + interface: vscode.SymbolKind.Interface +}; + +function convertToCodeSymbols( + document: vscode.TextDocument, + decls: GoOutlineDeclaration[], + includeImports: boolean, + byteOffsetToDocumentOffset: (byteOffset: number) => number +): vscode.DocumentSymbol[] { + const symbols: vscode.DocumentSymbol[] = []; + (decls || []).forEach((decl) => { + if (!includeImports && decl.type === 'import') { + return; + } + if (decl.label === '_' && decl.type === 'variable') { + return; + } + + const label = decl.receiverType ? `(${decl.receiverType}).${decl.label}` : decl.label; + + const start = byteOffsetToDocumentOffset(decl.start - 1); + const end = byteOffsetToDocumentOffset(decl.end - 1); + const startPosition = document.positionAt(start); + const endPosition = document.positionAt(end); + const symbolRange = new vscode.Range(startPosition, endPosition); + const selectionRange = + startPosition.line === endPosition.line + ? symbolRange + : new vscode.Range(startPosition, document.lineAt(startPosition.line).range.end); + + if (decl.type === 'type') { + const line = document.lineAt(document.positionAt(start)); + const regexStruct = new RegExp(`^\\s*type\\s+${decl.label}\\s+struct\\b`); + const regexInterface = new RegExp(`^\\s*type\\s+${decl.label}\\s+interface\\b`); + decl.type = regexStruct.test(line.text) ? 'struct' : regexInterface.test(line.text) ? 'interface' : 'type'; + } + + const symbolInfo = new vscode.DocumentSymbol( + label, + decl.type, + goKindToCodeKind[decl.type], + symbolRange, + selectionRange + ); + + symbols.push(symbolInfo); + if (decl.children) { + symbolInfo.children = convertToCodeSymbols( + document, + decl.children, + includeImports, + byteOffsetToDocumentOffset + ); + } + }); + return symbols; +} \ No newline at end of file diff --git a/src/goPath.ts b/src/goPath.ts new file mode 100644 index 0000000..4dc0519 --- /dev/null +++ b/src/goPath.ts @@ -0,0 +1,250 @@ +/*--------------------------------------------------------- + * Copyright (C) Microsoft Corporation. All rights reserved. + * Modification copyright 2020 The Go Authors. All rights reserved. + * Licensed under the MIT License. See LICENSE in the project root for license information. + *--------------------------------------------------------*/ + +'use strict'; + +/** + * This file is loaded by both the extension and debug adapter, so it cannot import 'vscode' + */ +import fs = require('fs'); +import os = require('os'); +import path = require('path'); + +let binPathCache: { [bin: string]: string } = {}; + +export const envPath = process.env['PATH'] || (process.platform === 'win32' ? process.env['Path'] : null); + +export function getBinPathFromEnvVar(toolName: string, envVarValue: string, appendBinToPath: boolean): string { + toolName = correctBinname(toolName); + if (envVarValue) { + const paths = envVarValue.split(path.delimiter); + for (const p of paths) { + const binpath = path.join(p, appendBinToPath ? 'bin' : '', toolName); + if (executableFileExists(binpath)) { + return binpath; + } + } + } + return null; +} + +export function getBinPathWithPreferredGopathGoroot( + toolName: string, + preferredGopaths: string[], + preferredGoroot?: string, + alternateTool?: string, + useCache = true, +) { + if (alternateTool && path.isAbsolute(alternateTool) && executableFileExists(alternateTool)) { + binPathCache[toolName] = alternateTool; + return alternateTool; + } + + // FIXIT: this cache needs to be invalidated when go.goroot or go.alternateTool is changed. + if (useCache && binPathCache[toolName]) { + return binPathCache[toolName]; + } + + const binname = alternateTool && !path.isAbsolute(alternateTool) ? alternateTool : toolName; + const pathFromGoBin = getBinPathFromEnvVar(binname, process.env['GOBIN'], false); + if (pathFromGoBin) { + binPathCache[toolName] = pathFromGoBin; + return pathFromGoBin; + } + + for (const preferred of preferredGopaths) { + if (typeof preferred === 'string') { + // Search in the preferred GOPATH workspace's bin folder + const pathFrompreferredGoPath = getBinPathFromEnvVar(binname, preferred, true); + if (pathFrompreferredGoPath) { + binPathCache[toolName] = pathFrompreferredGoPath; + return pathFrompreferredGoPath; + } + } + } + + // Check GOROOT (go, gofmt, godoc would be found here) + const pathFromGoRoot = getBinPathFromEnvVar(binname, preferredGoroot || getCurrentGoRoot(), true); + if (pathFromGoRoot) { + binPathCache[toolName] = pathFromGoRoot; + return pathFromGoRoot; + } + + // Finally search PATH parts + const pathFromPath = getBinPathFromEnvVar(binname, envPath, false); + if (pathFromPath) { + binPathCache[toolName] = pathFromPath; + return pathFromPath; + } + + // Check default path for go + if (toolName === 'go') { + const defaultPathForGo = process.platform === 'win32' ? 'C:\\Go\\bin\\go.exe' : '/usr/local/go/bin/go'; + if (executableFileExists(defaultPathForGo)) { + binPathCache[toolName] = defaultPathForGo; + return defaultPathForGo; + } + return; + } + + // Else return the binary name directly (this will likely always fail downstream) + return toolName; +} + +/** + * Returns the goroot path if it exists, otherwise returns an empty string + */ +let currentGoRoot = ''; +export function getCurrentGoRoot(): string { + return currentGoRoot || process.env['GOROOT'] || ''; +} + +export function setCurrentGoRoot(goroot: string) { + currentGoRoot = goroot; +} + +function correctBinname(toolName: string) { + if (process.platform === 'win32') { + return toolName + '.exe'; + } + return toolName; +} + +function executableFileExists(filePath: string): boolean { + let exists = true; + try { + exists = fs.statSync(filePath).isFile(); + if (exists) { + fs.accessSync(filePath, fs.constants.F_OK | fs.constants.X_OK); + } + } catch (e) { + exists = false; + } + return exists; +} + +export function fileExists(filePath: string): boolean { + try { + return fs.statSync(filePath).isFile(); + } catch (e) { + return false; + } +} + +export function clearCacheForTools() { + binPathCache = {}; +} + +/** + * Exapnds ~ to homedir in non-Windows platform + */ +export function resolveHomeDir(inputPath: string): string { + if (!inputPath || !inputPath.trim()) { + return inputPath; + } + return inputPath.startsWith('~') ? path.join(os.homedir(), inputPath.substr(1)) : inputPath; +} + +export function stripBOM(s: string): string { + if (s && s[0] === '\uFEFF') { + s = s.substr(1); + } + return s; +} + +export function parseEnvFile(envFilePath: string): { [key: string]: string } { + const env: { [key: string]: any } = {}; + if (!envFilePath) { + return env; + } + + try { + const buffer = stripBOM(fs.readFileSync(envFilePath, 'utf8')); + buffer.split('\n').forEach((line) => { + const r = line.match(/^\s*([\w\.\-]+)\s*=\s*(.*)?\s*$/); + if (r !== null) { + let value = r[2] || ''; + if (value.length > 0 && value.charAt(0) === '"' && value.charAt(value.length - 1) === '"') { + value = value.replace(/\\n/gm, '\n'); + } + env[r[1]] = value.replace(/(^['"]|['"]$)/g, ''); + } + }); + return env; + } catch (e) { + throw new Error(`Cannot load environment variables from file ${envFilePath}`); + } +} + +// Walks up given folder path to return the closest ancestor that has `src` as a child +export function getInferredGopath(folderPath: string): string { + if (!folderPath) { + return; + } + + const dirs = folderPath.toLowerCase().split(path.sep); + + // find src directory closest to given folder path + const srcIdx = dirs.lastIndexOf('src'); + if (srcIdx > 0) { + return folderPath.substr(0, dirs.slice(0, srcIdx).join(path.sep).length); + } +} + +/** + * Returns the workspace in the given Gopath to which given directory path belongs to + * @param gopath string Current Gopath. Can be ; or : separated (as per os) to support multiple paths + * @param currentFileDirPath string + */ +export function getCurrentGoWorkspaceFromGOPATH(gopath: string, currentFileDirPath: string): string { + if (!gopath) { + return; + } + const workspaces: string[] = gopath.split(path.delimiter); + let currentWorkspace = ''; + currentFileDirPath = fixDriveCasingInWindows(currentFileDirPath); + + // Find current workspace by checking if current file is + // under any of the workspaces in $GOPATH + for (const workspace of workspaces) { + const possibleCurrentWorkspace = path.join(workspace, 'src'); + if ( + currentFileDirPath.startsWith(possibleCurrentWorkspace) || + (process.platform === 'win32' && + currentFileDirPath.toLowerCase().startsWith(possibleCurrentWorkspace.toLowerCase())) + ) { + // In case of nested workspaces, (example: both /Users/me and /Users/me/src/a/b/c are in $GOPATH) + // both parent & child workspace in the nested workspaces pair can make it inside the above if block + // Therefore, the below check will take longer (more specific to current file) of the two + if (possibleCurrentWorkspace.length > currentWorkspace.length) { + currentWorkspace = currentFileDirPath.substr(0, possibleCurrentWorkspace.length); + } + } + } + return currentWorkspace; +} + +// Workaround for issue in https://github.com/Microsoft/vscode/issues/9448#issuecomment-244804026 +export function fixDriveCasingInWindows(pathToFix: string): string { + return process.platform === 'win32' && pathToFix + ? pathToFix.substr(0, 1).toUpperCase() + pathToFix.substr(1) + : pathToFix; +} + +/** + * Returns the tool name from the given path to the tool + * @param toolPath + */ +export function getToolFromToolPath(toolPath: string): string | undefined { + if (!toolPath) { + return; + } + let tool = path.basename(toolPath); + if (process.platform === 'win32' && tool.endsWith('.exe')) { + tool = tool.substr(0, tool.length - 4); + } + return tool; +} \ No newline at end of file diff --git a/src/goTools.ts b/src/goTools.ts new file mode 100644 index 0000000..acaf9e2 --- /dev/null +++ b/src/goTools.ts @@ -0,0 +1,131 @@ +/*--------------------------------------------------------- + * Copyright (C) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See LICENSE in the project root for license information. + *--------------------------------------------------------*/ + +'use strict'; + +import cp = require('child_process'); +import fs = require('fs'); +import moment = require('moment'); +import path = require('path'); +import semver = require('semver'); +import util = require('util'); +import { getBinPath, getGoPlusConfig,GoVersion} from './util'; + +export interface Tool { + name: string; + importPath: string; + isImportant: boolean; + description: string; + + // latestVersion and latestVersionTimestamp are hardcoded default values + // for the last known version of the given tool. We also hardcode values + // for the latest known pre-release of the tool for the Nightly extension. + latestVersion?: semver.SemVer; + latestVersionTimestamp?: moment.Moment; + latestPrereleaseVersion?: semver.SemVer; + latestPrereleaseVersionTimestamp?: moment.Moment; + + // minimumGoVersion and maximumGoVersion set the range for the versions of + // Go with which this tool can be used. + minimumGoVersion?: semver.SemVer; + maximumGoVersion?: semver.SemVer; + + // close performs any shutdown tasks that a tool must execute before a new + // version is installed. It returns a string containing an error message on + // failure. + close?: () => Promise; +} + +/** + * Returns the import path for a given tool, at a given Go version. + * @param tool Object of type `Tool` for the Go tool. + * @param goVersion The current Go version. + */ +export function getImportPath(tool: Tool): string { + return tool.importPath; +} + +export function getImportPathWithVersion(tool: Tool, version: semver.SemVer): string { + const importPath = getImportPath(tool); + if (version) { + return importPath + '@v' + version; + } + return importPath; +} + +/** + * Returns boolean denoting if the import path for the given tool ends with `/...` + * and if the version of Go supports installing wildcard paths in module mode. + * @param tool Object of type `Tool` for the Go tool. + * @param goVersion The current Go version. + */ +export function disableModulesForWildcard(tool: Tool, goVersion: GoVersion): boolean { + const importPath = getImportPath(tool); + const isWildcard = importPath.endsWith('...'); + + // Only Go >= 1.13 supports installing wildcards in module mode. + return isWildcard && goVersion.lt('1.13'); +} + + +export function containsTool(tools: Tool[], tool: Tool): boolean { + return tools.indexOf(tool) > -1; +} + +export function containsString(tools: Tool[], toolName: string): boolean { + return tools.some((tool) => tool.name === toolName); +} + +export function getTool(name: string): Tool { + return allToolsInformation[name]; +} + +// hasModSuffix returns true if the given tool has a different, module-specific +// name to avoid conflicts. +export function hasModSuffix(tool: Tool): boolean { + return tool.name.endsWith('-gomod'); +} + +export function isGocode(tool: Tool): boolean { + return tool.name === 'gocode' || tool.name === 'gocode-gomod'; +} + +export function getConfiguredTools(): Tool[] { + const tools: Tool[] = []; + function maybeAddTool(name: string) { + const tool = allToolsInformation[name]; + if (tool) { + tools.push(tool); + } + } + + const goPlusConfig = getGoPlusConfig(); + + + // Add the format tool that was chosen by the user. + maybeAddTool(goPlusConfig['formatTool']); + return tools; +} + +export const allToolsInformation: { [key: string]: Tool } = { + 'qfmt': { + name: 'qfmt', + importPath: 'github.com/qiniu/goplus/cmd/qfmt', + isImportant: false, + description: 'Formatter' + } +}; + +/** + * ToolAtVersion is a Tool at a specific version. + * Lack of version implies the latest version. + */ +export interface ToolAtVersion extends Tool { + version?: semver.SemVer; +} + +export function getToolAtVersion(name: string, version?: semver.SemVer): ToolAtVersion { + return { ...allToolsInformation[name], version }; +} \ No newline at end of file diff --git a/src/gopExtraInfo.ts b/src/gopExtraInfo.ts new file mode 100644 index 0000000..7c9d0a6 --- /dev/null +++ b/src/gopExtraInfo.ts @@ -0,0 +1,53 @@ +/*--------------------------------------------------------- + * Copyright (C) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See LICENSE in the project root for license information. + *--------------------------------------------------------*/ + +'use strict'; + +import vscode = require('vscode'); +import { CancellationToken, Hover, HoverProvider, Position, TextDocument, WorkspaceConfiguration } from 'vscode'; +import { definitionLocation } from './goDeclaration'; +import { getGoPlusConfig } from './util'; + +export class GoHoverProvider implements HoverProvider { + private goPlusConfig: WorkspaceConfiguration | undefined; + + constructor(goPlusConfig?: WorkspaceConfiguration) { + this.goPlusConfig = goPlusConfig; + } + + public provideHover(document: TextDocument, position: Position, token: CancellationToken): Thenable { + if (!this.goPlusConfig) { + this.goPlusConfig = getGoPlusConfig(document.uri); + } + let goPlusConfig = this.goPlusConfig; + + // Temporary fix to fall back to godoc if guru is the set docsTool + if (goPlusConfig['docsTool'] === 'guru') { + goPlusConfig = Object.assign({}, goPlusConfig, { docsTool: 'godoc' }); + } + return definitionLocation(document, position, goPlusConfig, true, token).then( + (definitionInfo) => { + if (definitionInfo == null) { + return null; + } + const lines = definitionInfo.declarationlines + .filter((line) => line !== '') + .map((line) => line.replace(/\t/g, ' ')); + let text; + text = lines.join('\n').replace(/\n+$/, ''); + const hoverTexts = new vscode.MarkdownString(); + hoverTexts.appendCodeblock(text, 'go'); + if (definitionInfo.doc != null) { + hoverTexts.appendMarkdown(definitionInfo.doc); + } + const hover = new Hover(hoverTexts); + return hover; + }, + () => { + return null; + } + ); + } +} diff --git a/src/gopFormat.ts b/src/gopFormat.ts new file mode 100644 index 0000000..d9a4889 --- /dev/null +++ b/src/gopFormat.ts @@ -0,0 +1,103 @@ +/*--------------------------------------------------------- + * Copyright (C) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See LICENSE in the project root for license information. + *--------------------------------------------------------*/ + +'use strict'; + +import cp = require('child_process'); +import path = require('path'); +import vscode = require('vscode'); +import { toolExecutionEnvironment } from './goEnv'; +import { promptForMissingTool, promptForUpdatingTool } from './gopInstallTools'; +import { getBinPath, getGoPlusConfig, killTree } from './util'; + +export class GoPlusDocumentFormattingEditProvider implements vscode.DocumentFormattingEditProvider { + public provideDocumentFormattingEdits( + document: vscode.TextDocument, + options: vscode.FormattingOptions, + token: vscode.CancellationToken + ): vscode.ProviderResult { + if (vscode.window.visibleTextEditors.every((e) => e.document.fileName !== document.fileName)) { + return []; + } + + const filename = document.fileName; + const goPlusConfig = getGoPlusConfig(document.uri); + const formatTool = goPlusConfig['formatTool'] || 'qfmt'; + const formatFlags = goPlusConfig['formatFlags'].slice() || []; + + // We ignore the -w flag that updates file on disk because that would break undo feature + if (formatFlags.indexOf('-w') > -1) { + formatFlags.splice(formatFlags.indexOf('-w'), 1); + } + + return this.runFormatter(formatTool, formatFlags, document, token).then( + (edits) => edits, + (err) => { + if (typeof err === 'string' && err.startsWith('flag provided but not defined: -srcdir')) { + promptForUpdatingTool(formatTool); + return Promise.resolve([]); + } + if (err) { + console.log(err); + return Promise.reject('Check the console in dev tools to find errors when formatting.'); + } + } + ); + } + + private runFormatter( + formatTool: string, + formatFlags: string[], + document: vscode.TextDocument, + token: vscode.CancellationToken + ): Thenable { + const formatCommandBinPath = getBinPath(formatTool); + return new Promise((resolve, reject) => { + if (!path.isAbsolute(formatCommandBinPath)) { + promptForMissingTool(formatTool); + return reject(); + } + + const env = toolExecutionEnvironment(); + const cwd = path.dirname(document.fileName); + let stdout = ''; + let stderr = ''; + + // Use spawn instead of exec to avoid maxBufferExceeded error + const p = cp.spawn(formatCommandBinPath, formatFlags, { env, cwd }); + token.onCancellationRequested(() => !p.killed && killTree(p.pid)); + p.stdout.setEncoding('utf8'); + p.stdout.on('data', (data) => { + (stdout += data); + }); + p.stderr.on('data', (data) => { + (stderr += data); + }); + p.on('error', (err) => { + if (err && (err).code === 'ENOENT') { + promptForMissingTool(formatTool); + return reject(); + } + }); + p.on('close', (code) => { + if (code !== 0) { + return reject(stderr); + } + + // Return the complete file content in the edit. + // VS Code will calculate minimal edits to be applied + const fileStart = new vscode.Position(0, 0); + const fileEnd = document.lineAt(document.lineCount - 1).range.end; + const textEdits: vscode.TextEdit[] = [ + new vscode.TextEdit(new vscode.Range(fileStart, fileEnd), stdout) + ]; + return resolve(textEdits); + }); + if (p.pid) { + p.stdin.end(document.getText()); + } + }); + } +} \ No newline at end of file diff --git a/src/gopImport.ts b/src/gopImport.ts new file mode 100644 index 0000000..81707c3 --- /dev/null +++ b/src/gopImport.ts @@ -0,0 +1,136 @@ +'use strict'; + +import vscode = require('vscode'); +import { getImportablePackages } from './gopPackages'; +import { promptForMissingTool } from './gopInstallTools'; +import { documentSymbols, GoOutlineImportsOptions } from './goOutline'; +import {parseFilePrelude } from './util'; + +export function getTextEditForAddImport(inputText: string, arg: string): vscode.TextEdit[] { + // Import name wasn't provided + if (arg === undefined) { + return null; + } + + + const { imports, pkg } = parseFilePrelude(inputText); + if (imports.some((block) => block.pkgs.some((pkgpath) => pkgpath === arg))) { + return []; + } + + const multis = imports.filter((x) => x.kind === 'multi'); + const minusCgo = imports.filter((x) => x.kind !== 'pseudo'); + + if (multis.length > 0) { + // There is a multiple import declaration, add to the last one + const lastImportSection = multis[multis.length - 1]; + if (lastImportSection.end === -1) { + // For some reason there was an empty import section like `import ()` + return [vscode.TextEdit.insert(new vscode.Position(lastImportSection.start + 1, 0), `import "${arg}"\n`)]; + } + // Add import at the start of the block so that goimports/goreturns can order them correctly + return [vscode.TextEdit.insert(new vscode.Position(lastImportSection.start + 1, 0), '\t"' + arg + '"\n')]; + } else if (minusCgo.length > 0) { + // There are some number of single line imports, which can just be collapsed into a block import. + const edits = []; + + edits.push(vscode.TextEdit.insert(new vscode.Position(minusCgo[0].start-1, 0), 'import (\n\t"' + arg + '"\n')); + minusCgo.forEach((element) => { + const currentLine = vscode.window.activeTextEditor.document.lineAt(element.start-1).text; + const updatedLine = currentLine.replace(/^\s*import\s*/, '\t'); + edits.push( + vscode.TextEdit.replace( + new vscode.Range(element.start-1, 0, element.start-1, currentLine.length), + updatedLine + ) + ); + }); + edits.push(vscode.TextEdit.insert(new vscode.Position(minusCgo[minusCgo.length - 1].end-1, 0), ')\n')); + + return edits; + } else if (pkg && pkg.start >= 0) { + // There are no import declarations, but there is a package declaration + return [vscode.TextEdit.insert(new vscode.Position(0, 0), '\nimport (\n\t"' + arg + '"\n)\n')]; + } else { + // There are no imports and no package declaration - give up + return []; + } +} + +const missingToolMsg = 'Missing tool: '; + +export async function listPackages(excludeImportedPkgs: boolean = false): Promise { + const importedPkgs = + excludeImportedPkgs && vscode.window.activeTextEditor + ? await getImports(vscode.window.activeTextEditor.document) + : []; + const pkgMap = await getImportablePackages(vscode.window.activeTextEditor.document.fileName, true); + const stdLibs: string[] = []; + const nonStdLibs: string[] = []; + pkgMap.forEach((value, key) => { + if (importedPkgs.some((imported) => imported === key)) { + return; + } + if (value.isStd) { + stdLibs.push(key); + } else { + nonStdLibs.push(key); + } + }); + return [...stdLibs.sort(), ...nonStdLibs.sort()]; +} + +/** + * Returns the imported packages in the given file + * + * @param document TextDocument whose imports need to be returned + * @returns Array of imported package paths wrapped in a promise + */ +async function getImports(document: vscode.TextDocument): Promise { + const options = { + fileName: document.fileName, + importsOption: GoOutlineImportsOptions.Only, + document + }; + + const symbols = await documentSymbols(options, null); + if (!symbols || !symbols.length) { + return []; + } + // import names will be of the form "math", so strip the quotes in the beginning and the end + const imports = symbols[0].children + .filter((x: any) => x.kind === vscode.SymbolKind.Namespace) + .map((x: any) => x.name.substr(1, x.name.length - 2)); + return imports; +} + +async function askUserForImport(): Promise { + try { + const packages = await listPackages(true); + return vscode.window.showQuickPick(packages); + } catch (err) { + if (typeof err === 'string' && err.startsWith(missingToolMsg)) { + promptForMissingTool(err.substr(missingToolMsg.length)); + } + } +} + +export function addImport(arg: { importPath: string; from: string }) { + const editor = vscode.window.activeTextEditor; + if (!editor) { + vscode.window.showErrorMessage('No active editor found to add imports.'); + return; + } + const p = arg && arg.importPath ? Promise.resolve(arg.importPath) : askUserForImport(); + p.then((imp) => { + if (!imp) { + return; + } + const edits = getTextEditForAddImport(vscode.window.activeTextEditor.document.getText(),imp); + if (edits && edits.length > 0) { + const edit = new vscode.WorkspaceEdit(); + edit.set(editor.document.uri, edits); + vscode.workspace.applyEdit(edit); + } + }); +} \ No newline at end of file diff --git a/src/gopInstallTools.ts b/src/gopInstallTools.ts new file mode 100644 index 0000000..379692b --- /dev/null +++ b/src/gopInstallTools.ts @@ -0,0 +1,464 @@ +'use strict'; + +import cp = require('child_process'); +import fs = require('fs'); +import path = require('path'); +import { SemVer } from 'semver'; +import util = require('util'); +import vscode = require('vscode'); +import { toolInstallationEnvironment } from './goEnv'; +import { envPath, getCurrentGoRoot, getToolFromToolPath, setCurrentGoRoot } from './goPath'; +import { outputChannel } from './gopStatus'; +import { + containsTool, + disableModulesForWildcard, + getConfiguredTools, + getImportPath, + getImportPathWithVersion, + getTool, + hasModSuffix, + Tool, + ToolAtVersion, +} from './goTools'; +import { + getBinPath, + getGoPlusConfig, + getGoVersion, + getTempFilePath, + GoVersion, + rmdirRecursive, +} from './util'; + +// declinedUpdates tracks the tools that the user has declined to update. +const declinedUpdates: Tool[] = []; + +// declinedUpdates tracks the tools that the user has declined to install. +const declinedInstalls: Tool[] = []; + +export async function installAllTools(updateExistingToolsOnly: boolean = false) { + const goVersion = await getGoVersion(); + let allTools = getConfiguredTools(); + + // exclude tools replaced by alternateTools. + const alternateTools: { [key: string]: string } = getGoPlusConfig().get('alternateTools'); + allTools = allTools.filter((tool) => { + return !alternateTools[tool.name]; + }); + + // Update existing tools by finding all tools the user has already installed. + if (updateExistingToolsOnly) { + await installTools( + allTools.filter((tool) => { + const toolPath = getBinPath(tool.name); + return toolPath && path.isAbsolute(toolPath); + }), + goVersion + ); + return; + } + + // Otherwise, allow the user to select which tools to install or update. + const selected = await vscode.window.showQuickPick( + allTools.map((x) => { + const item: vscode.QuickPickItem = { + label: x.name, + description: x.description + }; + return item; + }), + { + canPickMany: true, + placeHolder: 'Select the tools to install/update.' + } + ); + if (!selected) { + return; + } + await installTools(selected.map((x) => getTool(x.label)), goVersion); +} + +/** + * Installs given array of missing tools. If no input is given, the all tools are installed + * + * @param missing array of tool names and optionally, their versions to be installed. + * If a tool's version is not specified, it will install the latest. + * @param goVersion version of Go that affects how to install the tool. (e.g. modules vs legacy GOPATH mode) + */ +export async function installTools(missing: ToolAtVersion[], goVersion: GoVersion): Promise { + if (!missing) { + return; + } + + outputChannel.show(); + outputChannel.clear(); + + const envForTools = toolInstallationEnvironment(); + const toolsGopath = envForTools['GOPATH']; + let envMsg = `Tools environment: GOPATH=${toolsGopath}`; + if (envForTools['GOBIN']) { + envMsg += `, GOBIN=${envForTools['GOBIN']}`; + } + outputChannel.appendLine(envMsg); + + let installingMsg = `Installing ${missing.length} ${missing.length > 1 ? 'tools' : 'tool'} at `; + if (envForTools['GOBIN']) { + installingMsg += `the configured GOBIN: ${envForTools['GOBIN']}`; + } else { + installingMsg += `${toolsGopath}${path.sep}bin`; + } + + // If the user is on Go >= 1.11, tools should be installed with modules enabled. + // This ensures that users get the latest tagged version, rather than master, + // which may be unstable. + let modulesOff = true; + // if (goVersion.lt('1.11')) { + // modulesOff = true; + // } else { + // installingMsg += ' in module mode.'; + // } + + outputChannel.appendLine(installingMsg); + missing.forEach((missingTool) => { + let toolName = missingTool.name; + if (missingTool.version) { + toolName += '@' + missingTool.version; + } + outputChannel.appendLine(' ' + toolName); + }); + + outputChannel.appendLine(''); // Blank line for spacing. + + const toInstall: Promise<{ tool: Tool, reason: string }>[] = []; + for (const tool of missing) { + // Disable modules for tools which are installed with the "..." wildcard. + const modulesOffForTool = modulesOff || disableModulesForWildcard(tool, goVersion); + + const reason = installTool(tool, goVersion, envForTools, !modulesOffForTool); + toInstall.push(Promise.resolve({ tool, reason: await reason })); + } + + const results = await Promise.all(toInstall); + + const failures: { tool: ToolAtVersion, reason: string }[] = []; + for (const result of results) { + if (result.reason != "") { + failures.push(result); + } + } + + // Report detailed information about any failures. + outputChannel.appendLine(''); // blank line for spacing + if (failures.length === 0) { + outputChannel.appendLine('All tools successfully installed. You are ready to Go :).'); + } else { + outputChannel.appendLine(failures.length + ' tools failed to install.\n'); + for (const failure of failures) { + outputChannel.appendLine(`${failure.tool.name}: ${failure.reason} `); + } + } +} + +export async function installTool( + tool: ToolAtVersion, goVersion: GoVersion, + envForTools: NodeJS.Dict, modulesOn: boolean): Promise { + // Some tools may have to be closed before we reinstall them. + if (tool.close) { + const reason = await tool.close(); + if (reason) { + return reason; + } + } + // Install tools in a temporary directory, to avoid altering go.mod files. + const mkdtemp = util.promisify(fs.mkdtemp); + const toolsTmpDir = await mkdtemp(getTempFilePath('go-tools-')); + const env = Object.assign({}, envForTools); + let tmpGoModFile: string; + if (modulesOn) { + env['GO111MODULE'] = 'on'; + + // Write a temporary go.mod file to avoid version conflicts. + tmpGoModFile = path.join(toolsTmpDir, 'go.mod'); + const writeFile = util.promisify(fs.writeFile); + await writeFile(tmpGoModFile, 'module tools'); + } else { + envForTools['GO111MODULE'] = 'off'; + } + + // Build the arguments list for the tool installation. + const args = ['get', '-v']; + // Only get tools at master if we are not using modules. + if (!modulesOn) { + args.push('-u'); + } + // Tools with a "mod" suffix should not be installed, + // instead we run "go build -o" to rename them. + if (hasModSuffix(tool)) { + args.push('-d'); + } + let importPath: string; + if (!modulesOn) { + importPath = getImportPath(tool); + } else { + importPath = getImportPathWithVersion(tool, tool.version); + } + args.push(importPath); + + let output: string; + let result: string = ''; + try { + const opts = { + env, + cwd: toolsTmpDir, + }; + const execFile = util.promisify(cp.execFile); + const { stdout, stderr } = await execFile(goVersion.binaryPath, args, opts); + output = `${stdout} ${stderr}`; + + // TODO(rstambler): Figure out why this happens and maybe delete it. + if (stderr.indexOf('unexpected directory layout:') > -1) { + await execFile(goVersion.binaryPath, args, opts); + } else if (hasModSuffix(tool)) { + const gopath = env['GOPATH']; + if (!gopath) { + return `GOPATH not configured in environment`; + } + const outputFile = path.join(gopath, 'bin', process.platform === 'win32' ? `${tool.name}.exe` : tool.name); + await execFile(goVersion.binaryPath, ['build', '-o', outputFile, importPath], opts); + } + outputChannel.appendLine(`Installing ${importPath} SUCCEEDED`); + } catch (e) { + outputChannel.appendLine(`Installing ${importPath} FAILED`); + result = `failed to install ${tool}: ${e} ${output} `; + } + + // Delete the temporary installation directory. + rmdirRecursive(toolsTmpDir); + + return result; +} + +export async function promptForMissingTool(toolName: string) { + const tool = getTool(toolName); + + // If user has declined to install this tool, don't prompt for it. + if (containsTool(declinedInstalls, tool)) { + return; + } + + const goVersion = await getGoVersion(); + if (!goVersion) { + return; + } + + // Show error messages for outdated tools or outdated Go versions. + if (tool.minimumGoVersion && goVersion.lt(tool.minimumGoVersion.format())) { + vscode.window.showInformationMessage(`You are using go${goVersion.format()}, but ${tool.name} requires at least go${tool.minimumGoVersion.format()}.`); + return; + } + if (tool.maximumGoVersion && goVersion.gt(tool.maximumGoVersion.format())) { + vscode.window.showInformationMessage(`You are using go${goVersion.format()}, but ${tool.name} only supports go${tool.maximumGoVersion.format()} and below.`); + return; + } + + const installOptions = ['Install']; + let missing = await getMissingTools(); + if (!containsTool(missing, tool)) { + return; + } + missing = missing.filter((x) => x === tool || tool.isImportant); + if (missing.length > 1) { + // Offer the option to install all tools. + installOptions.push('Install All'); + } + const msg = `The "${tool.name}" command is not available. +Run "go get -v ${getImportPath(tool)}" to install.`; + const selected = await vscode.window.showInformationMessage(msg, ...installOptions); + switch (selected) { + case 'Install': + await installTools([tool], goVersion); + break; + case 'Install All': + await installTools(missing, goVersion); + break; + default: + // The user has declined to install this tool. + declinedInstalls.push(tool); + break; + } +} + +export async function promptForUpdatingTool(toolName: string, newVersion?: SemVer) { + const tool = getTool(toolName); + const toolVersion = { ...tool, version: newVersion }; // ToolWithVersion + + // If user has declined to update, then don't prompt. + if (containsTool(declinedUpdates, tool)) { + return; + } + const goVersion = await getGoVersion(); + let updateMsg = `Your version of ${tool.name} appears to be out of date. Please update for an improved experience.`; + const choices: string[] = ['Update']; + if (toolName === `gopls`) { + choices.push('Release Notes'); + } + if (newVersion) { + updateMsg = `A new version of ${tool.name} (v${newVersion}) is available. Please update for an improved experience.`; + } + const selected = await vscode.window.showInformationMessage(updateMsg, ...choices); + switch (selected) { + case 'Update': + await installTools([toolVersion], goVersion); + break; + case 'Release Notes': + vscode.commands.executeCommand( + 'vscode.open', + vscode.Uri.parse('https://github.com/golang/go/issues/33030#issuecomment-510151934') + ); + break; + default: + declinedUpdates.push(tool); + break; + } +} + +export function updateGoVarsFromConfig(): Promise { + // FIXIT: when user changes the environment variable settings or go.gopath, the following + // condition prevents from updating the process.env accordingly, so the extension will lie. + // Needs to clean up. + if (process.env['GOPATH'] && process.env['GOPROXY'] && process.env['GOBIN']) { + return Promise.resolve(); + } + + // FIXIT: if updateGoVarsFromConfig is called again after addGoRuntimeBaseToPATH sets PATH, + // the go chosen by getBinPath based on PATH will not change. + const goRuntimePath = getBinPath('go', false); + if (!goRuntimePath) { + vscode.window.showErrorMessage( + `Failed to run "go env" to find GOPATH as the "go" binary cannot be found in either GOROOT(${getCurrentGoRoot()}) or PATH(${envPath})` + ); + return; + } + + return new Promise((resolve, reject) => { + cp.execFile(goRuntimePath, ['env', 'GOPATH', 'GOROOT', 'GOPROXY', 'GOBIN'], (err, stdout, stderr) => { + if (err) { + return reject(); + } + const envOutput = stdout.split('\n'); + if (!process.env['GOPATH'] && envOutput[0].trim()) { + process.env['GOPATH'] = envOutput[0].trim(); + } + if (envOutput[1] && envOutput[1].trim()) { + setCurrentGoRoot(envOutput[1].trim()); + } + if (!process.env['GOPROXY'] && envOutput[2] && envOutput[2].trim()) { + process.env['GOPROXY'] = envOutput[2].trim(); + } + if (!process.env['GOBIN'] && envOutput[3] && envOutput[3].trim()) { + process.env['GOBIN'] = envOutput[3].trim(); + } + + // cgo, gopls, and other underlying tools will inherit the environment and attempt + // to locate 'go' from the PATH env var. + addGoRuntimeBaseToPATH(path.join(getCurrentGoRoot(), 'bin')); + return resolve(); + }); + }); +} + +// PATH value cached before addGoRuntimeBaseToPath modified. +let defaultPathEnv = ''; + +// addGoRuntimeBaseToPATH adds the given path to the front of the PATH environment variable. +// It removes duplicates. +// TODO: can we avoid changing PATH but utilize toolExecutionEnv? +function addGoRuntimeBaseToPATH(newGoRuntimeBase: string) { + if (!newGoRuntimeBase) { + return; + } + + let pathEnvVar: string; + if (process.env.hasOwnProperty('PATH')) { + pathEnvVar = 'PATH'; + } else if (process.platform === 'win32' && process.env.hasOwnProperty('Path')) { + pathEnvVar = 'Path'; + } else { + return; + } + + if (!defaultPathEnv) { // cache the default value + defaultPathEnv = process.env[pathEnvVar]; + } + + let pathVars = defaultPathEnv.split(path.delimiter); + pathVars = pathVars.filter((p) => p !== newGoRuntimeBase); + pathVars.unshift(newGoRuntimeBase); + process.env[pathEnvVar] = pathVars.join(path.delimiter); +} + +let alreadyOfferedToInstallTools = false; + +export async function offerToInstallTools() { + if (alreadyOfferedToInstallTools) { + return; + } + alreadyOfferedToInstallTools = true; + + const goVersion = await getGoVersion(); + let missing = await getMissingTools(); + missing = missing.filter((x) => x.isImportant); + if (missing.length > 0) { + vscode.commands.registerCommand('go.promptforinstall', () => { + const installItem = { + title: 'Install', + async command() { + await installTools(missing, goVersion); + } + }; + const showItem = { + title: 'Show', + command() { + outputChannel.clear(); + outputChannel.appendLine('Below tools are needed for the basic features of the Go extension.'); + missing.forEach((x) => outputChannel.appendLine(x.name)); + } + }; + vscode.window + .showInformationMessage( + 'Failed to find some of the Go analysis tools. Would you like to install them?', + installItem, + showItem + ) + .then((selection) => { + if (selection) { + selection.command(); + } + }); + }); + } +} + +function getMissingTools(): Promise { + const keys = getConfiguredTools(); + return Promise.all( + keys.map( + (tool) => + new Promise((resolve, reject) => { + const toolPath = getBinPath(tool.name); + resolve(path.isAbsolute(toolPath) ? null : tool); + }) + ) + ).then((res) => { + return res.filter((x) => x != null); + }); +} + +export async function getActiveGoRoot(): Promise { + // look for current current go binary + let goroot = getCurrentGoRoot(); + if (!goroot) { + await updateGoVarsFromConfig(); + goroot = getCurrentGoRoot(); + } + return goroot || undefined; +} \ No newline at end of file diff --git a/src/gopMain.ts b/src/gopMain.ts new file mode 100644 index 0000000..963c408 --- /dev/null +++ b/src/gopMain.ts @@ -0,0 +1,20 @@ +'use strict'; + +import vscode = require('vscode'); +import { GoPlusCompletionItemProvider } from './gopSuggest'; +import { GOP_MODE } from './gopMode'; +import { GoPlusDocumentFormattingEditProvider } from './gopFormat'; +import { GoHoverProvider } from './gopExtraInfo'; +import { GoDefinitionProvider } from './goDeclaration'; +import { addImport } from './gopImport'; + +export function activate(ctx: vscode.ExtensionContext): void { + vscode.languages.registerCompletionItemProvider(GOP_MODE, new GoPlusCompletionItemProvider(ctx.globalState), '.', '"') + vscode.languages.registerDocumentFormattingEditProvider(GOP_MODE, new GoPlusDocumentFormattingEditProvider()) + vscode.languages.registerHoverProvider(GOP_MODE, new GoHoverProvider()); + vscode.languages.registerDefinitionProvider(GOP_MODE, new GoDefinitionProvider()); + + vscode.commands.registerCommand('goplus.import.add', (arg) => { + return addImport(arg); + }) +} \ No newline at end of file diff --git a/src/gopMode.ts b/src/gopMode.ts new file mode 100644 index 0000000..07ca7cb --- /dev/null +++ b/src/gopMode.ts @@ -0,0 +1,10 @@ +/*--------------------------------------------------------- + * Copyright (C) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See LICENSE in the project root for license information. + *--------------------------------------------------------*/ + +'use strict'; + +import vscode = require('vscode'); + +export const GOP_MODE: vscode.DocumentFilter = { language: 'gop', scheme: 'file' }; \ No newline at end of file diff --git a/src/gopPackages.ts b/src/gopPackages.ts new file mode 100644 index 0000000..0f97f60 --- /dev/null +++ b/src/gopPackages.ts @@ -0,0 +1,137 @@ +/*--------------------------------------------------------- + * Copyright (C) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See LICENSE in the project root for license information. + *--------------------------------------------------------*/ + +import cp = require('child_process'); +import path = require('path'); +import vscode = require('vscode'); +import { toolExecutionEnvironment } from './goEnv'; +import { promptForMissingTool, promptForUpdatingTool } from './gopInstallTools'; +import { envPath, fixDriveCasingInWindows, getCurrentGoRoot, getCurrentGoWorkspaceFromGOPATH } from './goPath'; +import { getBinPath, getCurrentGoPath, getGoVersion } from './util'; + +type GopkgsDone = (res: Map) => void; +interface Cache { + entry: Map; + lastHit: number; +} + +export interface PackageInfo { + name: string; + isStd: boolean; +} + + +let gopkgsNotified: boolean = false; +const allPkgsCache: Map = new Map(); +const pkgRootDirs = new Map(); +let cacheTimeout: number = 5000; +const gopkgsSubscriptions: Map = new Map(); +const gopkgsRunning: Set = new Set(); + + +function gopkgs(workDir?: string): Promise> { + return new Promise>((resolve, reject) => { + const pkgs = new Map(); + var output = "errors;errors\nflag;flag\nfmt;fmt\nio;io\nnet;net\nos;os\nreflect;reflect\nstrconv;strconv\nstrings;strings\nsync;sync\n" + output.split('\n').forEach((pkgDetail) => { + const [pkgName, pkgPath] = pkgDetail.trim().split(';'); + pkgs.set(pkgPath, { + name: pkgName, + isStd: true + }); + }); + return resolve(pkgs); + }); +} +/** + * Returns mapping of import path and package name for packages that can be imported + * Possible to return empty if useCache options is used. + * @param filePath. Used to determine the right relative path for vendor pkgs + * @param useCache. Force to use cache + * @returns Map mapping between package import path and package name + */ +export function getImportablePackages(filePath: string, useCache: boolean = false): Promise> { + filePath = fixDriveCasingInWindows(filePath); + const fileDirPath = path.dirname(filePath); + + let foundPkgRootDir = pkgRootDirs.get(fileDirPath); + const workDir = foundPkgRootDir || fileDirPath; + const cache = allPkgsCache.get(workDir); + + const getAllPackagesPromise: Promise> = + useCache && cache ? Promise.race([getAllPackages(workDir), cache.entry]) : getAllPackages(workDir); + + return Promise.all([getAllPackagesPromise]).then(([pkgs]) => { + const pkgMap = new Map(); + if (!pkgs) { + return pkgMap; + } + + // const currentWorkspace = getCurrentGoWorkspaceFromGOPATH(getCurrentGoPath(), fileDirPath); + pkgs.forEach((info, pkgPath) => { + if (info.name === 'main') { + return; + } + pkgMap.set(pkgPath, info); + }); + return pkgMap; + }); +} + +function getAllPackagesNoCache(workDir: string): Promise> { + return new Promise>((resolve, reject) => { + // Use subscription style to guard costly/long running invocation + const callback = (pkgMap: Map) => { + resolve(pkgMap); + }; + + let subs = gopkgsSubscriptions.get(workDir); + if (!subs) { + subs = []; + gopkgsSubscriptions.set(workDir, subs); + } + subs.push(callback); + + // Ensure only single gokpgs running + if (!gopkgsRunning.has(workDir)) { + gopkgsRunning.add(workDir); + + gopkgs(workDir).then((pkgMap) => { + gopkgsRunning.delete(workDir); + gopkgsSubscriptions.delete(workDir); + subs.forEach((cb) => cb(pkgMap)); + }); + } + }); +} + +/** + * Runs gopkgs + * @argument workDir. The workspace directory of the project. + * @returns Map mapping between package import path and package name + */ +export async function getAllPackages(workDir: string): Promise> { + const cache = allPkgsCache.get(workDir); + const useCache = cache && new Date().getTime() - cache.lastHit < cacheTimeout; + if (useCache) { + cache.lastHit = new Date().getTime(); + return Promise.resolve(cache.entry); + } + + const pkgs = await getAllPackagesNoCache(workDir); + if (!pkgs || pkgs.size === 0) { + if (!gopkgsNotified) { + vscode.window.showInformationMessage( + 'Could not find packages. Ensure `gopkgs -format {{.Name}};{{.ImportPath}}` runs successfully.' + ); + gopkgsNotified = true; + } + } + allPkgsCache.set(workDir, { + entry: pkgs, + lastHit: new Date().getTime() + }); + return pkgs; +} \ No newline at end of file diff --git a/src/gopStatus.ts b/src/gopStatus.ts new file mode 100644 index 0000000..3ba1506 --- /dev/null +++ b/src/gopStatus.ts @@ -0,0 +1,10 @@ +/*--------------------------------------------------------- + * Copyright (C) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See LICENSE in the project root for license information. + *--------------------------------------------------------*/ + +'use strict'; + +import vscode = require('vscode'); + +export let outputChannel = vscode.window.createOutputChannel('GoPlus'); \ No newline at end of file diff --git a/src/gopSuggest.ts b/src/gopSuggest.ts new file mode 100644 index 0000000..e59ad74 --- /dev/null +++ b/src/gopSuggest.ts @@ -0,0 +1,640 @@ +'use strict'; + +import vscode = require('vscode'); +import cp = require('child_process'); +import path = require('path'); +import { toolExecutionEnvironment } from './goEnv'; +import { getTextEditForAddImport } from './gopImport'; +import { promptForMissingTool, promptForUpdatingTool } from './gopInstallTools'; +import { getImportablePackages, PackageInfo } from './gopPackages'; +import { getCurrentGoWorkspaceFromGOPATH } from './goPath'; +import { + getBinPath, + getCurrentGoPath, + goBuiltinTypes, + goKeywords, + isPositionInString, + isPositionInComment, + getParametersAndReturnType, + runGodoc, + getGoPlusConfig, + byteOffsetAt, + parseFilePrelude, +} from './util'; + + +// TODO FOR CodeSuggestion use gocode to support code completion??? + +function vscodeKindFromGoCodeClass(kind: string, type: string): vscode.CompletionItemKind { + switch (kind) { + case 'const': + return vscode.CompletionItemKind.Constant; + case 'package': + return vscode.CompletionItemKind.Module; + case 'type': + switch (type) { + case 'struct': + return vscode.CompletionItemKind.Class; + case 'interface': + return vscode.CompletionItemKind.Interface; + } + return vscode.CompletionItemKind.Struct; + case 'func': + return vscode.CompletionItemKind.Function; + case 'var': + return vscode.CompletionItemKind.Variable; + case 'import': + return vscode.CompletionItemKind.Module; + } + return vscode.CompletionItemKind.Property; // TODO@EG additional mappings needed? +} + +interface GoCodeSuggestion { + class: string; + package?: string; + name: string; + type: string; + receiver?: string; +} + +class ExtendedCompletionItem extends vscode.CompletionItem { + public package?: string; + public receiver?: string; + public fileName: string; +} + +const lineCommentFirstWordRegex = /^\s*\/\/\s+[\S]*$/; +const exportedMemberRegex = /(const|func|type|var)/; +const gocodeNoSupportForgbMsgKey = 'dontshowNoSupportForgb'; + +export class GoPlusCompletionItemProvider implements vscode.CompletionItemProvider, vscode.Disposable { + private pkgsList = new Map(); + private killMsgShown: boolean = false; + private setGocodeOptions: boolean = true; + private isGoMod: boolean = false; + private globalState: vscode.Memento; + private previousFile: string; + private previousFileDir: string; + private gocodeFlags: string[]; + private excludeDocs: boolean = false; + + constructor(globalState?: vscode.Memento) { + this.globalState = globalState; + } + + public provideCompletionItems( + document: vscode.TextDocument, + position: vscode.Position, + token: vscode.CancellationToken + ): Thenable { + return this.provideCompletionItemsInternal(document, position, token, getGoPlusConfig(document.uri)).then( + (result) => { + if (!result) { + return new vscode.CompletionList([], false); + } + if (Array.isArray(result)) { + return new vscode.CompletionList(result, false); + } + return result; + } + ); + } + + public async provideCompletionItemsInternal( + document: vscode.TextDocument, + position: vscode.Position, + token: vscode.CancellationToken, + config: vscode.WorkspaceConfiguration + ): Promise { + // Completions for the package statement based on the file name + // const pkgStatementCompletions = await getPackageStatementCompletions(document); + // if (pkgStatementCompletions && pkgStatementCompletions.length) { + // return pkgStatementCompletions; + // } + + this.excludeDocs = false; + this.gocodeFlags = ['-f=json']; + if (Array.isArray(config['gocodeFlags'])) { + this.gocodeFlags.push(...config['gocodeFlags']); + } + + return this.ensureGoCodeConfigured(document.uri, config).then(() => { + return new Promise((resolve, reject) => { + const filename = document.fileName; + const lineText = document.lineAt(position.line).text; + const lineTillCurrentPosition = lineText.substr(0, position.character); + const autocompleteUnimportedPackages = + config['autocompleteUnimportedPackages'] === true && !lineText.match(/^(\s)*(import|package)(\s)+/); + + + // triggering completions in comments on exported members + const commentCompletion = getCommentCompletion(document, position); + if (commentCompletion) { + return resolve([commentCompletion]); + } + + // prevent completion when typing in a line comment that doesnt start from the beginning of the line + if (isPositionInComment(document, position)) { + return resolve([]); + } + + const inString = isPositionInString(document, position); + if (!inString && lineTillCurrentPosition.endsWith('"')) { + return resolve([]); + } + + const currentWord = getCurrentWord(document, position); + if (currentWord.match(/^\d+$/)) { + return resolve([]); + } + + let offset = byteOffsetAt(document, position); + let inputText = document.getText(); + + if (!inputText.match(/package\s+(\w+)/)) { + let addtText = "package main\r\n\r\n" + offset = offset +addtText.length + inputText = addtText + inputText + } + + const includeUnimportedPkgs = autocompleteUnimportedPackages && !inString && currentWord.length > 0; + + + return this.runGoCode( + document, + filename, + inputText, + offset, + inString, + position, + lineText, + currentWord, + includeUnimportedPkgs, + config + ).then((suggestions) => { + // gocode does not suggest keywords, so we have to do it + suggestions.push(...getKeywordCompletions(currentWord)); + + // If no suggestions and cursor is at a dot, then check if preceeding word is a package name + // If yes, then import the package in the inputText and run gocode again to get suggestions + if ((!suggestions || suggestions.length === 0) && lineTillCurrentPosition.endsWith('.')) { + const pkgPath = this.getPackagePathFromLine(lineTillCurrentPosition); + if (pkgPath.length === 1) { + // Now that we have the package path, import it right after the "package" statement + const { imports, pkg } = parseFilePrelude( + inputText, + ); + let addtText = "package main\r\n\r\n" + + let lines = inputText.split("\n") + let posToAddImport = 0 + for (let index = 0; index < lines.length; index++) { + if (index > pkg.start+1) { + break + } + posToAddImport = posToAddImport + lines[index].length + } + posToAddImport = posToAddImport +1 + const textToAdd = `import "${pkgPath[0]}"\n`; + inputText = inputText.substr(0, posToAddImport) + textToAdd + inputText.substr(posToAddImport); + offset += textToAdd.length; + + // Now that we have the package imported in the inputText, run gocode again + return this.runGoCode( + document, + filename, + inputText, + offset, + inString, + position, + lineText, + currentWord, + false, + config + ).then((newsuggestions) => { + // Since the new suggestions are due to the package that we imported, + // add additionalTextEdits to do the same in the actual document in the editor + // We use additionalTextEdits instead of command so that 'useCodeSnippetsOnFunctionSuggest' + // feature continues to work + newsuggestions.forEach((item) => { + item.additionalTextEdits = getTextEditForAddImport(inputText,pkgPath[0]); + }); + resolve(newsuggestions); + }, reject); + } + if (pkgPath.length > 1) { + pkgPath.forEach((pkg) => { + const item = new vscode.CompletionItem( + `${lineTillCurrentPosition.replace('.', '').trim()} (${pkg})`, + vscode.CompletionItemKind.Module + ); + item.additionalTextEdits = getTextEditForAddImport(vscode.window.activeTextEditor.document.getText(),pkg); + item.insertText = ''; + item.detail = pkg; + item.command = { + title: 'Trigger Suggest', + command: 'editor.action.triggerSuggest' + }; + suggestions.push(item); + }); + resolve(new vscode.CompletionList(suggestions, true)); + } + } + resolve(suggestions) + },reject); + }); + }); + } + + public resolveCompletionItem( + item: vscode.CompletionItem, + token: vscode.CancellationToken + ): vscode.ProviderResult { + if ( + !(item instanceof ExtendedCompletionItem) || + item.kind === vscode.CompletionItemKind.Module || + this.excludeDocs + ) { + return; + } + + if (typeof item.package === 'undefined') { + promptForUpdatingTool('gocode'); + return; + } + + return runGodoc( + path.dirname(item.fileName), + item.package || path.dirname(item.fileName), + item.receiver, + item.label, + token + ) + .then((doc) => { + item.documentation = new vscode.MarkdownString(doc); + return item; + }) + .catch((err) => { + console.log(err); + return item; + }); + } + + private ensureGoCodeConfigured(fileuri: vscode.Uri, goConfig: vscode.WorkspaceConfiguration): Thenable { + const currentFile = fileuri.fsPath; + const setPkgsList = getImportablePackages(currentFile, true).then((pkgMap) => { + this.pkgsList = pkgMap; + }); + + return Promise.all([setPkgsList]).then(() => { + return; + }); + } + + + public dispose() { + + } + + // Given a line ending with dot, return the import paths of packages that match with the word preceeding the dot + private getPackagePathFromLine(line: string): string[] { + const pattern = /(\w+)\.$/g; + const wordmatches = pattern.exec(line); + if (!wordmatches) { + return []; + } + + const [_, pkgNameFromWord] = wordmatches; + // Word is isolated. Now check pkgsList for a match + return this.getPackageImportPath(pkgNameFromWord); + } + + private runGoCode( + document: vscode.TextDocument, + filename: string, + inputText: string, + offset: number, + inString: boolean, + position: vscode.Position, + lineText: string, + currentWord: string, + includeUnimportedPkgs: boolean, + config: vscode.WorkspaceConfiguration + ): Thenable { + return new Promise((resolve, reject) => { + const gocodeName = this.isGoMod ? 'gocode-gomod' : 'gocode'; + const gocode = getBinPath(gocodeName); + if (!path.isAbsolute(gocode)) { + promptForMissingTool(gocodeName); + return reject(); + } + + const env = toolExecutionEnvironment(); + let stdout = ''; + let stderr = ''; + + // stamblerre/gocode does not support -unimported-packages flags. + if (this.isGoMod) { + const unimportedPkgIndex = this.gocodeFlags.indexOf('-unimported-packages'); + if (unimportedPkgIndex >= 0) { + this.gocodeFlags.splice(unimportedPkgIndex, 1); + } + } + + // -exclude-docs is something we use internally and is not related to gocode + const excludeDocsIndex = this.gocodeFlags.indexOf('-exclude-docs'); + if (excludeDocsIndex >= 0) { + this.gocodeFlags.splice(excludeDocsIndex, 1); + this.excludeDocs = true; + } + + // Spawn `gocode` process + const p = cp.spawn(gocode, [...this.gocodeFlags, 'autocomplete', "test.go", '' + offset], { env }); + p.stdout.on('data', (data) => { + (stdout += data) + }); + p.stderr.on('data', (data) => { + (stderr += data)}); + p.on('error', (err) => { + if (err && (err).code === 'ENOENT') { + promptForMissingTool(gocodeName); + return reject(); + } + return reject(err); + }); + p.on('close', (code) => { + try { + if (code !== 0) { + if (stderr.indexOf(`rpc: can't find service Server.AutoComplete`) > -1 && !this.killMsgShown) { + vscode.window.showErrorMessage( + 'Auto-completion feature failed as an older gocode process is still running. Please kill the running process for gocode and try again.' + ); + this.killMsgShown = true; + } + if (stderr.startsWith('flag provided but not defined:')) { + promptForUpdatingTool(gocodeName); + } + return reject(); + } + const results = <[number, GoCodeSuggestion[]]>JSON.parse(stdout.toString()); + let suggestions: vscode.CompletionItem[] = []; + const packageSuggestions: string[] = []; + + const wordAtPosition = document.getWordRangeAtPosition(position); + let areCompletionsForPackageSymbols = false; + if (results && results[1]) { + for (const suggest of results[1]) { + if (inString && suggest.class !== 'import') { + continue; + } + const item = new ExtendedCompletionItem(suggest.name); + item.kind = vscodeKindFromGoCodeClass(suggest.class, suggest.type); + item.package = suggest.package; + item.receiver = suggest.receiver; + item.fileName = document.fileName; + item.detail = suggest.type; + if (!areCompletionsForPackageSymbols && item.package && item.package !== 'builtin') { + areCompletionsForPackageSymbols = true; + } + if (suggest.class === 'package') { + const possiblePackageImportPaths = this.getPackageImportPath(item.label); + if (possiblePackageImportPaths.length === 1) { + item.detail = possiblePackageImportPaths[0]; + } + packageSuggestions.push(suggest.name); + } + if (inString && suggest.class === 'import') { + item.textEdit = new vscode.TextEdit( + new vscode.Range( + position.line, + lineText.substring(0, position.character).lastIndexOf('"') + 1, + position.line, + position.character + ), + suggest.name + ); + } + if ( + (config['useCodeSnippetsOnFunctionSuggest'] || + config['useCodeSnippetsOnFunctionSuggestWithoutType']) && + ((suggest.class === 'func' && lineText.substr(position.character, 2) !== '()') || // Avoids met() -> method()() + (suggest.class === 'var' && + suggest.type.startsWith('func(') && + lineText.substr(position.character, 1) !== ')' && // Avoids snippets when typing params in a func call + lineText.substr(position.character, 1) !== ',')) // Avoids snippets when typing params in a func call + ) { + const { params, returnType } = getParametersAndReturnType(suggest.type.substring(4)); + const paramSnippets = []; + for (let i = 0; i < params.length; i++) { + let param = params[i].trim(); + if (param) { + param = param.replace('${', '\\${').replace('}', '\\}'); + if (config['useCodeSnippetsOnFunctionSuggestWithoutType']) { + if (param.includes(' ')) { + // Separate the variable name from the type + param = param.substr(0, param.indexOf(' ')); + } + } + paramSnippets.push('${' + (i + 1) + ':' + param + '}'); + } + } + item.insertText = new vscode.SnippetString( + suggest.name + '(' + paramSnippets.join(', ') + ')' + ); + } + if ( + config['useCodeSnippetsOnFunctionSuggest'] && + suggest.class === 'type' && + suggest.type.startsWith('func(') + ) { + const { params, returnType } = getParametersAndReturnType(suggest.type.substring(4)); + const paramSnippets = []; + for (let i = 0; i < params.length; i++) { + let param = params[i].trim(); + if (param) { + param = param.replace('${', '\\${').replace('}', '\\}'); + if (!param.includes(' ')) { + // If we don't have an argument name, we need to create one + param = 'arg' + (i + 1) + ' ' + param; + } + const arg = param.substr(0, param.indexOf(' ')); + paramSnippets.push( + '${' + + (i + 1) + + ':' + + arg + + '}' + + param.substr(param.indexOf(' '), param.length) + ); + } + } + item.insertText = new vscode.SnippetString( + suggest.name + + '(func(' + + paramSnippets.join(', ') + + ') {\n $' + + (params.length + 1) + + '\n})' + + returnType + ); + } + + if ( + wordAtPosition && + wordAtPosition.start.character === 0 && + suggest.class === 'type' && + !goBuiltinTypes.has(suggest.name) + ) { + const auxItem = new vscode.CompletionItem( + suggest.name + ' method', + vscode.CompletionItemKind.Snippet + ); + auxItem.label = 'func (*' + suggest.name + ')'; + auxItem.filterText = suggest.name; + auxItem.detail = 'Method snippet'; + auxItem.sortText = 'b'; + const prefix = 'func (' + suggest.name[0].toLowerCase() + ' *' + suggest.name + ')'; + const snippet = prefix + ' ${1:methodName}(${2}) ${3} {\n\t$0\n}'; + auxItem.insertText = new vscode.SnippetString(snippet); + suggestions.push(auxItem); + } + + // Add same sortText to all suggestions from gocode so that they appear before the unimported packages + item.sortText = 'a'; + suggestions.push(item); + } + } + + // Add importable packages matching currentword to suggestions + if (includeUnimportedPkgs && !this.isGoMod && !areCompletionsForPackageSymbols) { + suggestions = suggestions.concat( + getPackageCompletions(document, currentWord, this.pkgsList, packageSuggestions) + ); + } + + resolve(suggestions); + } catch (e) { + reject(e); + } + }); + if (p.pid) { + p.stdin.end(inputText); + } + }); + } + + /** + * Returns import path for given package. Since there can be multiple matches, + * this returns an array of matches + * @param input Package name + */ + private getPackageImportPath(input: string): string[] { + const matchingPackages: any[] = []; + this.pkgsList.forEach((info: PackageInfo, pkgPath: string) => { + if (input === info.name) { + matchingPackages.push(pkgPath); + } + }); + return matchingPackages; + } +} + +/** + * Provides completion item for the exported member in the next line if current line is a comment + * @param document The current document + * @param position The cursor position + */ +function getCommentCompletion(document: vscode.TextDocument, position: vscode.Position): vscode.CompletionItem { + const lineText = document.lineAt(position.line).text; + const lineTillCurrentPosition = lineText.substr(0, position.character); + // triggering completions in comments on exported members + if (lineCommentFirstWordRegex.test(lineTillCurrentPosition) && position.line + 1 < document.lineCount) { + const nextLine = document.lineAt(position.line + 1).text.trim(); + const memberType = nextLine.match(exportedMemberRegex); + let suggestionItem: vscode.CompletionItem; + if (memberType && memberType.length === 4) { + suggestionItem = new vscode.CompletionItem(memberType[3], vscodeKindFromGoCodeClass(memberType[1], '')); + } + return suggestionItem; + } +} + +function getCurrentWord(document: vscode.TextDocument, position: vscode.Position): string { + // get current word + const wordAtPosition = document.getWordRangeAtPosition(position); + let currentWord = ''; + if (wordAtPosition && wordAtPosition.start.character < position.character) { + const word = document.getText(wordAtPosition); + currentWord = word.substr(0, position.character - wordAtPosition.start.character); + } + + return currentWord; +} + +/** + * Return importable packages that match given word as Completion Items + * @param document Current document + * @param currentWord The word at the cursor + * @param allPkgMap Map of all available packages and their import paths + * @param importedPackages List of imported packages. Used to prune imported packages out of available packages + */ +function getPackageCompletions( + document: vscode.TextDocument, + currentWord: string, + allPkgMap: Map, + importedPackages: string[] = [] +): vscode.CompletionItem[] { + const cwd = path.dirname(document.fileName); + const goWorkSpace = getCurrentGoWorkspaceFromGOPATH(getCurrentGoPath(), cwd); + const workSpaceFolder = vscode.workspace.getWorkspaceFolder(document.uri); + const currentPkgRootPath = (workSpaceFolder ? workSpaceFolder.uri.path : cwd).slice(goWorkSpace.length + 1); + + const completionItems: any[] = []; + + allPkgMap.forEach((info: PackageInfo, pkgPath: string) => { + const pkgName = info.name; + if (pkgName.startsWith(currentWord) && importedPackages.indexOf(pkgName) === -1) { + const item = new vscode.CompletionItem(pkgName, vscode.CompletionItemKind.Keyword); + item.detail = pkgPath; + item.documentation = 'Imports the package'; + item.insertText = pkgName; + item.command = { + title: 'Import Package', + command: 'goplus.import.add', + arguments: [{ importPath: pkgPath, from: 'completion' }] + }; + item.kind = vscode.CompletionItemKind.Module; + + // Unimported packages should appear after the suggestions from gocode + const isStandardPackage = !item.detail.includes('.'); + item.sortText = isStandardPackage ? 'za' : pkgPath.startsWith(currentPkgRootPath) ? 'zb' : 'zc'; + completionItems.push(item); + } + }); + return completionItems; +} + +function getKeywordCompletions(currentWord: string): vscode.CompletionItem[] { + if (!currentWord.length) { + return []; + } + const completionItems: vscode.CompletionItem[] = []; + goKeywords.forEach((keyword) => { + if (keyword.startsWith(currentWord)) { + completionItems.push(new vscode.CompletionItem(keyword, vscode.CompletionItemKind.Keyword)); + } + }); + return completionItems; +} + +function getPackageStatement(inputText: string,offset: number): string { + // 'Smart Snippet' for package clause + if (inputText.match(/package\s+(\w+)/)) { + return inputText; + } + + let addtText = "package main\r\n\r\n" + addtText.length + inputText = addtText + inputText + return inputText; +} \ No newline at end of file diff --git a/src/stateUtils.ts b/src/stateUtils.ts new file mode 100644 index 0000000..c7b49c2 --- /dev/null +++ b/src/stateUtils.ts @@ -0,0 +1,45 @@ +/*--------------------------------------------------------- + * Copyright (C) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See LICENSE in the project root for license information. + *--------------------------------------------------------*/ + +import vscode = require('vscode'); + +let globalState: vscode.Memento; +let workspaceState: vscode.Memento; + +export function getFromGlobalState(key: string, defaultValue?: any): any { + if (!globalState) { + return defaultValue; + } + return globalState.get(key, defaultValue); +} + +export function updateGlobalState(key: string, value: any) { + if (!globalState) { + return; + } + return globalState.update(key, value); +} + +export function setGlobalState(state: vscode.Memento) { + globalState = state; +} + +export function getFromWorkspaceState(key: string, defaultValue?: any) { + if (!workspaceState) { + return defaultValue; + } + return workspaceState.get(key, defaultValue); +} + +export function updateWorkspaceState(key: string, value: any) { + if (!workspaceState) { + return; + } + return workspaceState.update(key, value); +} + +export function setWorkspaceState(state: vscode.Memento) { + workspaceState = state; +} diff --git a/src/util.ts b/src/util.ts new file mode 100644 index 0000000..5ab4529 --- /dev/null +++ b/src/util.ts @@ -0,0 +1,664 @@ +/*--------------------------------------------------------- + * Copyright (C) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See LICENSE in the project root for license information. + *--------------------------------------------------------*/ + +import cp = require('child_process'); +import vscode = require('vscode'); +import util = require('util'); +import kill = require('tree-kill'); +import { NearestNeighborDict, Node } from './avlTree'; + +import { + envPath, + fixDriveCasingInWindows, + getBinPathWithPreferredGopathGoroot, + getCurrentGoRoot, + getInferredGopath, + resolveHomeDir, +} from './goPath'; +import { toolExecutionEnvironment } from './goEnv'; +import { getCurrentPackage } from './goModules'; +import fs = require('fs'); +import os = require('os'); +import semver = require('semver'); +import path = require('path'); +import { outputChannel } from './gopStatus'; + +export const goKeywords: string[] = [ + 'break', + 'case', + 'chan', + 'const', + 'continue', + 'default', + 'defer', + 'else', + 'fallthrough', + 'for', + 'func', + 'go', + 'goto', + 'if', + 'import', + 'interface', + 'map', + 'package', + 'range', + 'return', + 'select', + 'struct', + 'switch', + 'type', + 'var' +]; + +export const goBuiltinTypes: Set = new Set([ + 'bool', + 'byte', + 'complex128', + 'complex64', + 'error', + 'float32', + 'float64', + 'int', + 'int16', + 'int32', + 'int64', + 'int8', + 'rune', + 'string', + 'uint', + 'uint16', + 'uint32', + 'uint64', + 'uint8', + 'uintptr' +]); + +let toolsGopath: string; +let cachedGoBinPath: string | undefined; +let cachedGoVersion: GoVersion | undefined; +let vendorSupport: boolean | undefined; + +export class GoVersion { + public sv?: semver.SemVer; + public isDevel?: boolean; + private commit?: string; + + constructor(public binaryPath: string, version: string) { + const matchesRelease = /go version go(\d.\d+).*/.exec(version); + const matchesDevel = /go version devel \+(.[a-zA-Z0-9]+).*/.exec(version); + if (matchesRelease) { + const sv = semver.coerce(matchesRelease[0]); + if (sv) { + this.sv = sv; + } + } else if (matchesDevel) { + this.isDevel = true; + this.commit = matchesDevel[0]; + } + } + + public isValid(): boolean { + return !!this.sv || !!this.isDevel; + } + + public format(): string { + if (this.sv) { + return this.sv.format(); + } + return `devel +${this.commit}`; + } + + public lt(version: string): boolean { + // Assume a developer version is always above any released version. + // This is not necessarily true. + if (this.isDevel || !this.sv) { + return false; + } + const v = semver.coerce(version); + if (!v) { + return false; + } + return semver.lt(this.sv, v); + } + + public gt(version: string): boolean { + // Assume a developer version is always above any released version. + // This is not necessarily true. + if (this.isDevel || !this.sv) { + return true; + } + const v = semver.coerce(version); + if (!v) { + return false; + } + return semver.gt(this.sv, v); + } +} + +/** + * Gets version of Go based on the output of the command `go version`. + * Returns null if go is being used from source/tip in which case `go version` will not return release tag like go1.6.3 + */ +export async function getGoVersion(): Promise { + const goRuntimePath = getBinPath('go'); + + const warn = (msg: string) => { + outputChannel.appendLine(msg); + console.warn(msg); + }; + + if (!goRuntimePath) { + warn(`unable to locate "go" binary in GOROOT (${getCurrentGoRoot()}) or PATH (${envPath})`); + return; + } + if (cachedGoBinPath === goRuntimePath && cachedGoVersion) { + if (cachedGoVersion.isValid()) { + return Promise.resolve(cachedGoVersion); + } + warn(`cached Go version (${cachedGoVersion}) is invalid, recomputing`); + } + try { + const execFile = util.promisify(cp.execFile); + const { stdout, stderr } = await execFile(goRuntimePath, ['version']); + if (stderr) { + warn(`failed to run "${goRuntimePath} version": stdout: ${stdout}, stderr: ${stderr}`); + return; + } + cachedGoBinPath = goRuntimePath; + cachedGoVersion = new GoVersion(goRuntimePath, stdout); + } catch (err) { + warn(`failed to run "${goRuntimePath} version": ${err}`); + return; + } + return cachedGoVersion; +} + +// getGoPlusConfig is declared as an exported const rather than a function, so it can be stubbbed in testing. +export const getGoPlusConfig = (uri?: vscode.Uri) => { + if (!uri) { + if (vscode.window.activeTextEditor) { + uri = vscode.window.activeTextEditor.document.uri; + } else { + uri = null; + } + } + return vscode.workspace.getConfiguration('goplus', uri); +}; + +// getGoConfig is declared as an exported const rather than a function, so it can be stubbbed in testing. +export const getGoConfig = (uri?: vscode.Uri) => { + if (!uri) { + if (vscode.window.activeTextEditor) { + uri = vscode.window.activeTextEditor.document.uri; + } else { + uri = null; + } + } + return vscode.workspace.getConfiguration('go', uri); +}; + +export function getBinPath(tool: string, useCache = true): string { + const cfg = getGoConfig(); + const alternateTools: { [key: string]: string } = cfg.get('alternateTools'); + const alternateToolPath: string = alternateTools[tool]; + + return getBinPathWithPreferredGopathGoroot( + tool, + tool === 'go' ? [] : [getToolsGopath(), getCurrentGoPath()], + tool === 'go' && cfg.get('goroot') ? cfg.get('goroot') : undefined, + resolvePath(alternateToolPath), + useCache + ); +} + +/** + * Expands ~ to homedir in non-Windows platform and resolves ${workspaceFolder} or ${workspaceRoot} + */ +export function resolvePath(inputPath: string, workspaceFolder?: string): string { + if (!inputPath || !inputPath.trim()) { + return inputPath; + } + + if (!workspaceFolder && vscode.workspace.workspaceFolders) { + workspaceFolder = getWorkspaceFolderPath( + vscode.window.activeTextEditor && vscode.window.activeTextEditor.document.uri + ); + } + + if (workspaceFolder) { + inputPath = inputPath.replace(/\${workspaceFolder}|\${workspaceRoot}/g, workspaceFolder); + } + return resolveHomeDir(inputPath); +} + +export function getWorkspaceFolderPath(fileUri?: vscode.Uri): string { + if (fileUri) { + const workspace = vscode.workspace.getWorkspaceFolder(fileUri); + if (workspace) { + return fixDriveCasingInWindows(workspace.uri.fsPath); + } + } + + // fall back to the first workspace + const folders = vscode.workspace.workspaceFolders; + if (folders && folders.length) { + return fixDriveCasingInWindows(folders[0].uri.fsPath); + } +} + +export function getToolsGopath(useCache: boolean = true): string { + if (!useCache || !toolsGopath) { + toolsGopath = resolveToolsGopath(); + } + return toolsGopath; +} + +function resolveToolsGopath(): string { + let toolsGopathForWorkspace = substituteEnv(getGoConfig()['toolsGopath'] || ''); + + // In case of single root + if (!vscode.workspace.workspaceFolders || vscode.workspace.workspaceFolders.length <= 1) { + return resolvePath(toolsGopathForWorkspace); + } + + // In case of multi-root, resolve ~ and ${workspaceFolder} + if (toolsGopathForWorkspace.startsWith('~')) { + toolsGopathForWorkspace = path.join(os.homedir(), toolsGopathForWorkspace.substr(1)); + } + if ( + toolsGopathForWorkspace && + toolsGopathForWorkspace.trim() && + !/\${workspaceFolder}|\${workspaceRoot}/.test(toolsGopathForWorkspace) + ) { + return toolsGopathForWorkspace; + } + + // If any of the folders in multi root have toolsGopath set, use it. + for (const folder of vscode.workspace.workspaceFolders) { + let toolsGopathFromConfig = getGoConfig(folder.uri).inspect('toolsGopath').workspaceFolderValue; + toolsGopathFromConfig = resolvePath(toolsGopathFromConfig, folder.uri.fsPath); + if (toolsGopathFromConfig) { + return toolsGopathFromConfig; + } + } +} + +let currentGopath = ''; +export function getCurrentGoPath(workspaceUri?: vscode.Uri): string { + const activeEditorUri = vscode.window.activeTextEditor && vscode.window.activeTextEditor.document.uri; + const currentFilePath = fixDriveCasingInWindows(activeEditorUri && activeEditorUri.fsPath); + const currentRoot = (workspaceUri && workspaceUri.fsPath) || getWorkspaceFolderPath(activeEditorUri); + const config = getGoConfig(workspaceUri || activeEditorUri); + + // Infer the GOPATH from the current root or the path of the file opened in current editor + // Last resort: Check for the common case where GOPATH itself is opened directly in VS Code + let inferredGopath: string; + if (config['inferGopath'] === true) { + inferredGopath = getInferredGopath(currentRoot) || getInferredGopath(currentFilePath); + if (!inferredGopath) { + try { + if (fs.statSync(path.join(currentRoot, 'src')).isDirectory()) { + inferredGopath = currentRoot; + } + } catch (e) { + // No op + } + } + if (inferredGopath && process.env['GOPATH'] && inferredGopath !== process.env['GOPATH']) { + inferredGopath += path.delimiter + process.env['GOPATH']; + } + } + + const configGopath = config['gopath'] ? resolvePath(substituteEnv(config['gopath']), currentRoot) : ''; + currentGopath = inferredGopath ? inferredGopath : configGopath || process.env['GOPATH']; + return currentGopath; +} + +export function substituteEnv(input: string): string { + return input.replace(/\${env:([^}]+)}/g, (match, capture) => { + return process.env[capture.trim()] || ''; + }); +} + +export const killTree = (processId: number): void => { + try { + kill(processId, (err) => { + if (err) { + console.log(`Error killing process tree: ${err}`); + } + }); + } catch (err) { + console.log(`Error killing process tree: ${err}`); + } +}; + +export function rmdirRecursive(dir: string) { + if (fs.existsSync(dir)) { + fs.readdirSync(dir).forEach((file) => { + const relPath = path.join(dir, file); + if (fs.lstatSync(relPath).isDirectory()) { + rmdirRecursive(relPath); + } else { + try { + fs.unlinkSync(relPath); + } catch (err) { + console.log(`failed to remove ${relPath}: ${err}`); + } + } + }); + fs.rmdirSync(dir); + } +} + +let tmpDir: string; +/** + * Returns file path for given name in temp dir + * @param name Name of the file + */ +export function getTempFilePath(name: string): string { + if (!tmpDir) { + tmpDir = fs.mkdtempSync(os.tmpdir() + path.sep + 'vscode-go'); + } + + if (!fs.existsSync(tmpDir)) { + fs.mkdirSync(tmpDir); + } + + return path.normalize(path.join(tmpDir, name)); +} + +/** + * Returns a boolean whether the current position lies within a comment or not + * @param document + * @param position + */ +export function isPositionInComment(document: vscode.TextDocument, position: vscode.Position): boolean { + const lineText = document.lineAt(position.line).text; + const commentIndex = lineText.indexOf('//'); + + if (commentIndex >= 0 && position.character > commentIndex) { + const commentPosition = new vscode.Position(position.line, commentIndex); + const isCommentInString = isPositionInString(document, commentPosition); + + return !isCommentInString; + } + return false; +} + +export function isPositionInString(document: vscode.TextDocument, position: vscode.Position): boolean { + const lineText = document.lineAt(position.line).text; + const lineTillCurrentPosition = lineText.substr(0, position.character); + + // Count the number of double quotes in the line till current position. Ignore escaped double quotes + let doubleQuotesCnt = (lineTillCurrentPosition.match(/\"/g) || []).length; + const escapedDoubleQuotesCnt = (lineTillCurrentPosition.match(/\\\"/g) || []).length; + + doubleQuotesCnt -= escapedDoubleQuotesCnt; + return doubleQuotesCnt % 2 === 1; +} + +export function byteOffsetAt(document: vscode.TextDocument, position: vscode.Position): number { + const offset = document.offsetAt(position); + const text = document.getText(); + return Buffer.byteLength(text.substr(0, offset)); +} + +// Takes a Go function signature like: +// (foo, bar string, baz number) (string, string) +// and returns an array of parameter strings: +// ["foo", "bar string", "baz string"] +// Takes care of balancing parens so to not get confused by signatures like: +// (pattern string, handler func(ResponseWriter, *Request)) { +export function getParametersAndReturnType(signature: string): { params: string[]; returnType: string } { + const params: string[] = []; + let parenCount = 0; + let lastStart = 1; + for (let i = 1; i < signature.length; i++) { + switch (signature[i]) { + case '(': + parenCount++; + break; + case ')': + parenCount--; + if (parenCount < 0) { + if (i > lastStart) { + params.push(signature.substring(lastStart, i)); + } + return { + params, + returnType: i < signature.length - 1 ? signature.substr(i + 1) : '' + }; + } + break; + case ',': + if (parenCount === 0) { + params.push(signature.substring(lastStart, i)); + lastStart = i + 2; + } + break; + } + } + return { params: [], returnType: '' }; +} + +export interface Prelude { + imports: Array<{ kind: string; start: number; end: number; pkgs: string[] }>; + pkg: { start: number; end: number; name: string }; +} + +export function parseFilePrelude(text: string): Prelude { + const lines = text.split('\n'); + const ret: Prelude = { imports: [], pkg: null }; + for (let i = 0; i < lines.length; i++) { + const line = lines[i]; + const pkgMatch = line.match(/^(\s)*package(\s)+(\w+)/); + if (pkgMatch) { + ret.pkg = { start: i, end: i, name: pkgMatch[3] }; + } + if (line.match(/^(\s)*import(\s)+\(/)) { + ret.imports.push({ kind: 'multi', start: i, end: -1, pkgs: [] }); + } else if (line.match(/^\s*import\s+"C"/)) { + ret.imports.push({ kind: 'pseudo', start: i, end: i, pkgs: [] }); + } else if (line.match(/^(\s)*import(\s)+[^\(]/)) { + ret.imports.push({ kind: 'single', start: i, end: i, pkgs: [] }); + } + if (line.match(/^(\s)*(\/\*.*\*\/)*\s*\)/)) { // /* comments */ + if (ret.imports[ret.imports.length - 1].end === -1) { + ret.imports[ret.imports.length - 1].end = i; + } + } else if (ret.imports.length) { + if (ret.imports[ret.imports.length - 1].end === -1) { + const importPkgMatch = line.match(/"([^"]+)"/); + if (importPkgMatch) { + ret.imports[ret.imports.length - 1].pkgs.push(importPkgMatch[1]); + } + } + } + + if (line.match(/^(\s)*(func|const|type|var)\s/)) { + break; + } + } + return ret; +} + + +/** + * Runs `go doc` to get documentation for given symbol + * @param cwd The cwd where the go doc process will be run + * @param packagePath Either the absolute path or import path of the package. + * @param symbol Symbol for which docs need to be found + * @param token Cancellation token + */ +export function runGodoc( + cwd: string, + packagePath: string, + receiver: string, + symbol: string, + token: vscode.CancellationToken +) { + if (!packagePath) { + return Promise.reject(new Error('Package Path not provided')); + } + if (!symbol) { + return Promise.reject(new Error('Symbol not provided')); + } + + + const goRuntimePath = getBinPath('go'); + if (!goRuntimePath) { + return Promise.reject(new Error('Cannot find "go" binary. Update PATH or GOROOT appropriately')); + } + + const getCurrentPackagePromise = path.isAbsolute(packagePath) + ? getCurrentPackage(packagePath) + : Promise.resolve(packagePath); + return getCurrentPackagePromise.then((packageImportPath) => { + return new Promise((resolve, reject) => { + if (receiver) { + receiver = receiver.replace(/^\*/, ''); + symbol = receiver + '.' + symbol; + } + + const env = toolExecutionEnvironment(); + const args = ['doc', '-c', '-cmd', '-u', packageImportPath, symbol]; + console.log(goRuntimePath,args) + const p = cp.execFile(goRuntimePath, args, { env, cwd }, (err, stdout, stderr) => { + if (err) { + return reject(err.message || stderr); + } + let doc = ''; + const godocLines = stdout.split('\n'); + if (!godocLines.length) { + return resolve(doc); + } + + // Recent versions of Go have started to include the package statement + // tht we dont need. + if (godocLines[0].startsWith('package ')) { + godocLines.splice(0, 1); + if (!godocLines[0].trim()) { + godocLines.splice(0, 1); + } + } + + // Skip trailing empty lines + let lastLine = godocLines.length - 1; + for (; lastLine > 1; lastLine--) { + if (godocLines[lastLine].trim()) { + break; + } + } + + for (let i = 1; i <= lastLine; i++) { + if (godocLines[i].startsWith(' ')) { + doc += godocLines[i].substring(4) + '\n'; + } else if (!godocLines[i].trim()) { + doc += '\n'; + } + } + return resolve(doc); + }); + + if (token) { + token.onCancellationRequested(() => { + killTree(p.pid); + }); + } + }); + }); +} + +/** + * Guess the package name based on parent directory name of the given file + * + * Cases: + * - dir 'go-i18n' -> 'i18n' + * - dir 'go-spew' -> 'spew' + * - dir 'kingpin' -> 'kingpin' + * - dir 'go-expand-tilde' -> 'tilde' + * - dir 'gax-go' -> 'gax' + * - dir 'go-difflib' -> 'difflib' + * - dir 'jwt-go' -> 'jwt' + * - dir 'go-radix' -> 'radix' + * + * @param {string} filePath. + */ +export function guessPackageNameFromFile(filePath: string): Promise { + return new Promise((resolve, reject) => { + const goFilename = path.basename(filePath); + if (goFilename === 'main.go') { + return resolve(['main']); + } + + const directoryPath = path.dirname(filePath); + const dirName = path.basename(directoryPath); + let segments = dirName.split(/[\.-]/); + segments = segments.filter((val) => val !== 'go'); + + if (segments.length === 0 || !/[a-zA-Z_]\w*/.test(segments[segments.length - 1])) { + return reject(); + } + + const proposedPkgName = segments[segments.length - 1]; + + fs.stat(path.join(directoryPath, 'main.go'), (err, stats) => { + if (stats && stats.isFile()) { + return resolve(['main']); + } + + if (goFilename.endsWith('_test.go')) { + return resolve([proposedPkgName, proposedPkgName + '_test']); + } + + return resolve([proposedPkgName]); + }); + }); +} + +export function getModuleCache(): string { + if (currentGopath) { + return path.join(currentGopath.split(path.delimiter)[0], 'pkg', 'mod'); + } +} + +export function getFileArchive(document: vscode.TextDocument): string { + const fileContents = document.getText(); + return document.fileName + '\n' + Buffer.byteLength(fileContents, 'utf8') + '\n' + fileContents; +} + +export function killProcess(p: cp.ChildProcess) { + if (p) { + try { + p.kill(); + } catch (e) { + console.log('Error killing process: ' + e); + } + } +} + +export function makeMemoizedByteOffsetConverter(buffer: Buffer): (byteOffset: number) => number { + const defaultValue = new Node(0, 0); // 0 bytes will always be 0 characters + const memo = new NearestNeighborDict(defaultValue, NearestNeighborDict.NUMERIC_DISTANCE_FUNCTION); + return (byteOffset: number) => { + const nearest = memo.getNearest(byteOffset); + const byteDelta = byteOffset - nearest.key; + + if (byteDelta === 0) { + return nearest.value; + } + + let charDelta: number; + if (byteDelta > 0) { + charDelta = buffer.toString('utf8', nearest.key, byteOffset).length; + } else { + charDelta = -buffer.toString('utf8', byteOffset, nearest.key).length; + } + + memo.insert(byteOffset, nearest.value + charDelta); + return nearest.value + charDelta; + }; +} \ No newline at end of file diff --git a/syntaxes/gop.tmLanguage.json b/syntaxes/gop.tmLanguage.json new file mode 100644 index 0000000..bc90e42 --- /dev/null +++ b/syntaxes/gop.tmLanguage.json @@ -0,0 +1,676 @@ +{ + "scopeName": "source.gop", + "comment": "GoPlus language", + "fileTypes": [ + "gop" + ], + "foldingStartMarker": "(?x)/\\*\\*(?!\\*)|^(?![^{]*?//|[^{]*?/\\*(?!.*?\\*/.*?\\{)).*?\\{\\s*($|//|/\\*(?!.*?\\*/.*\\S))", + "foldingStopMarker": "(?<!\\*)\\*\\*/|^\\s*\\}", + "keyEquivalent": "^~G", + "name": "GoPlus", + "patterns": [ + { + "include": "#comments" + }, + { + "comment": "Interpreted string literals", + "begin": "\"", + "beginCaptures": { + "0": { + "name": "punctuation.definition.string.begin.gop" + } + }, + "end": "\"", + "endCaptures": { + "0": { + "name": "punctuation.definition.string.end.gop" + } + }, + "name": "string.quoted.double.gop", + "patterns": [ + { + "include": "#string_escaped_char" + }, + { + "include": "#string_placeholder" + } + ] + }, + { + "comment": "Raw string literals", + "begin": "`", + "beginCaptures": { + "0": { + "name": "punctuation.definition.string.begin.gop" + } + }, + "end": "`", + "endCaptures": { + "0": { + "name": "punctuation.definition.string.end.gop" + } + }, + "name": "string.quoted.raw.gop", + "patterns": [ + { + "include": "#string_placeholder" + } + ] + }, + { + "comment": "Syntax error receiving channels", + "match": "<\\-([\\t ]+)chan\\b", + "captures": { + "1": { + "name": "invalid.illegal.receive-channel.gop" + } + } + }, + { + "comment": "Syntax error sending channels", + "match": "\\bchan([\\t ]+)<-", + "captures": { + "1": { + "name": "invalid.illegal.send-channel.gop" + } + } + }, + { + "comment": "Syntax error using slices", + "match": "\\[\\](\\s+)", + "captures": { + "1": { + "name": "invalid.illegal.slice.gop" + } + } + }, + { + "comment": "Syntax error numeric literals", + "match": "\\b0[0-7]*[89]\\d*\\b", + "name": "invalid.illegal.numeric.gop" + }, + { + "comment": "Built-in functions", + "match": "\\b(append|cap|close|complex|copy|delete|imag|len|make|new|panic|print|println|real|recover)\\b(?=\\()", + "name": "support.function.builtin.gop" + }, + { + "comment": "Function declarations", + "match": "^(\\bfunc\\b)(?:\\s+(\\([^\\)]+\\)\\s+)?(\\w+)(?=\\())?", + "captures": { + "1": { + "name": "keyword.function.gop" + }, + "2": { + "patterns": [ + { + "include": "#brackets" + }, + { + "include": "#operators" + } + ] + }, + "3": { + "patterns": [ + { + "match": "\\d\\w*", + "name": "invalid.illegal.identifier.gop" + }, + { + "match": "\\w+", + "name": "entity.name.function.gop" + } + ] + } + } + }, + { + "comment": "Functions", + "match": "(\\bfunc\\b)|(\\w+)(?=\\()", + "captures": { + "1": { + "name": "keyword.function.gop" + }, + "2": { + "patterns": [ + { + "match": "\\d\\w*", + "name": "invalid.illegal.identifier.gop" + }, + { + "match": "\\w+", + "name": "support.function.gop" + } + ] + } + } + }, + { + "comment": "Floating-point literals", + "match": "(\\.\\d+([Ee][-+]\\d+)?i?)\\b|\\b\\d+\\.\\d*(([Ee][-+]\\d+)?i?\\b)?", + "name": "constant.numeric.floating-point.gop" + }, + { + "comment": "Integers", + "match": "\\b((0x[0-9a-fA-F]+)|(0[0-7]+i?)|(\\d+([Ee]\\d+)?i?)|(\\d+[Ee][-+]\\d+i?))\\b", + "name": "constant.numeric.integer.gop" + }, + { + "comment": "Language constants", + "match": "\\b(true|false|nil|iota)\\b", + "name": "constant.language.gop" + }, + { + "begin": "\\b(package)\\s+", + "beginCaptures": { + "1": { + "name": "keyword.package.gop" + } + }, + "end": "(?!\\G)", + "patterns": [ + { + "match": "\\d\\w*", + "name": "invalid.illegal.identifier.gop" + }, + { + "match": "\\w+", + "name": "entity.name.package.gop" + } + ] + }, + { + "begin": "\\b(type)\\s+", + "beginCaptures": { + "1": { + "name": "keyword.type.gop" + } + }, + "end": "(?!\\G)", + "patterns": [ + { + "match": "\\d\\w*", + "name": "invalid.illegal.identifier.gop" + }, + { + "match": "\\w+", + "name": "entity.name.type.gop" + } + ] + }, + { + "begin": "\\b(import)\\s+", + "beginCaptures": { + "1": { + "name": "keyword.import.gop" + } + }, + "end": "(?!\\G)", + "patterns": [ + { + "include": "#imports" + } + ] + }, + { + "begin": "\\b(var)\\s+", + "beginCaptures": { + "1": { + "name": "keyword.var.gop" + } + }, + "end": "(?!\\G)", + "patterns": [ + { + "include": "#variables" + } + ] + }, + { + "match": "(?,\\s*\\w+(?:\\.\\w+)*)*)(?=\\s*=(?!=))", + "captures": { + "1": { + "patterns": [ + { + "match": "\\d\\w*", + "name": "invalid.illegal.identifier.gop" + }, + { + "match": "\\w+(?:\\.\\w+)*", + "name": "variable.other.assignment.gop", + "captures": { + "0": { + "patterns": [ + { + "include": "#delimiters" + } + ] + } + } + }, + { + "include": "#delimiters" + } + ] + } + } + }, + { + "match": "\\w+(?:,\\s*\\w+)*(?=\\s*:=)", + "captures": { + "0": { + "patterns": [ + { + "match": "\\d\\w*", + "name": "invalid.illegal.identifier.gop" + }, + { + "match": "\\w+", + "name": "variable.other.assignment.gop" + }, + { + "include": "#delimiters" + } + ] + } + } + }, + { + "comment": "Terminators", + "match": ";", + "name": "punctuation.terminator.gop" + }, + { + "include": "#brackets" + }, + { + "include": "#delimiters" + }, + { + "include": "#keywords" + }, + { + "include": "#operators" + }, + { + "include": "#runes" + }, + { + "include": "#storage_types" + } + ], + "repository": { + "brackets": { + "patterns": [ + { + "begin": "{", + "beginCaptures": { + "0": { + "name": "punctuation.definition.begin.bracket.curly.gop" + } + }, + "end": "}", + "endCaptures": { + "0": { + "name": "punctuation.definition.end.bracket.curly.gop" + } + }, + "patterns": [ + { + "include": "$self" + } + ] + }, + { + "begin": "\\(", + "beginCaptures": { + "0": { + "name": "punctuation.definition.begin.bracket.round.gop" + } + }, + "end": "\\)", + "endCaptures": { + "0": { + "name": "punctuation.definition.end.bracket.round.gop" + } + }, + "patterns": [ + { + "include": "$self" + } + ] + }, + { + "match": "\\[|\\]", + "name": "punctuation.definition.bracket.square.gop" + } + ] + }, + "comments": { + "patterns": [ + { + "begin": "/\\*", + "end": "\\*/", + "captures": { + "0": { + "name": "punctuation.definition.comment.gop" + } + }, + "name": "comment.block.gop" + }, + { + "begin": "//", + "beginCaptures": { + "0": { + "name": "punctuation.definition.comment.gop" + } + }, + "end": "$", + "name": "comment.line.double-slash.gop" + } + ] + }, + "delimiters": { + "patterns": [ + { + "match": ",", + "name": "punctuation.other.comma.gop" + }, + { + "match": "\\.(?!\\.\\.)", + "name": "punctuation.other.period.gop" + }, + { + "match": ":(?!=)", + "name": "punctuation.other.colon.gop" + } + ] + }, + "imports": { + "patterns": [ + { + "match": "((?!\\s+\")[^\\s]*)?\\s*((\")([^\"]*)(\"))", + "captures": { + "1": { + "name": "entity.alias.import.gop" + }, + "2": { + "name": "string.quoted.double.gop" + }, + "3": { + "name": "punctuation.definition.string.begin.gop" + }, + "4": { + "name": "entity.name.import.gop" + }, + "5": { + "name": "punctuation.definition.string.end.gop" + } + } + }, + { + "begin": "\\(", + "beginCaptures": { + "0": { + "name": "punctuation.definition.imports.begin.bracket.round.gop" + } + }, + "end": "\\)", + "endCaptures": { + "0": { + "name": "punctuation.definition.imports.end.bracket.round.gop" + } + }, + "patterns": [ + { + "include": "#comments" + }, + { + "include": "#imports" + } + ] + } + ] + }, + "keywords": { + "patterns": [ + { + "comment": "Flow control keywords", + "match": "\\b(break|case|continue|default|defer|else|fallthrough|for|go|goto|if|range|return|select|switch)\\b", + "name": "keyword.control.gop" + }, + { + "match": "\\bchan\\b", + "name": "keyword.channel.gop" + }, + { + "match": "\\bconst\\b", + "name": "keyword.const.gop" + }, + { + "match": "\\bfunc\\b", + "name": "keyword.function.gop" + }, + { + "match": "\\binterface\\b", + "name": "keyword.interface.gop" + }, + { + "match": "\\bmap\\b", + "name": "keyword.map.gop" + }, + { + "match": "\\bstruct\\b", + "name": "keyword.struct.gop" + } + ] + }, + "operators": { + "comment": "Note that the order here is very important!", + "patterns": [ + { + "match": "(\\*|&)(?=\\w)", + "name": "keyword.operator.address.gop" + }, + { + "match": "<\\-", + "name": "keyword.operator.channel.gop" + }, + { + "match": "\\-\\-", + "name": "keyword.operator.decrement.gop" + }, + { + "match": "\\+\\+", + "name": "keyword.operator.increment.gop" + }, + { + "match": "(==|!=|<=|>=|<(?!<)|>(?!>))", + "name": "keyword.operator.comparison.gop" + }, + { + "match": "(&&|\\|\\||!)", + "name": "keyword.operator.logical.gop" + }, + { + "match": "(=|\\+=|\\-=|\\|=|\\^=|\\*=|/=|:=|%=|<<=|>>=|&\\^=|&=)", + "name": "keyword.operator.assignment.gop" + }, + { + "match": "(\\+|\\-|\\*|/|%)", + "name": "keyword.operator.arithmetic.gop" + }, + { + "match": "(&(?!\\^)|\\||\\^|&\\^|<<|>>)", + "name": "keyword.operator.arithmetic.bitwise.gop" + }, + { + "match": "\\.\\.\\.", + "name": "keyword.operator.ellipsis.gop" + } + ] + }, + "runes": { + "patterns": [ + { + "begin": "'", + "beginCaptures": { + "0": { + "name": "punctuation.definition.string.begin.gop" + } + }, + "end": "'", + "endCaptures": { + "0": { + "name": "punctuation.definition.string.end.gop" + } + }, + "name": "string.quoted.rune.gop", + "patterns": [ + { + "match": "\\G(\\\\([0-7]{3}|[abfnrtv\\\\'\"]|x[0-9a-fA-F]{2}|u[0-9a-fA-F]{4}|U[0-9a-fA-F]{8})|.)(?=')", + "name": "constant.other.rune.gop" + }, + { + "match": "[^']+", + "name": "invalid.illegal.unknown-rune.gop" + } + ] + } + ] + }, + "storage_types": { + "patterns": [ + { + "match": "\\bbool\\b", + "name": "storage.type.boolean.go" + }, + { + "match": "\\bbyte\\b", + "name": "storage.type.byte.go" + }, + { + "match": "\\berror\\b", + "name": "storage.type.error.go" + }, + { + "match": "\\b(complex(64|128)|float(32|64)|u?int(8|16|32|64)?)\\b", + "name": "storage.type.numeric.go" + }, + { + "match": "\\brune\\b", + "name": "storage.type.rune.go" + }, + { + "match": "\\bstring\\b", + "name": "storage.type.string.go" + }, + { + "match": "\\buintptr\\b", + "name": "storage.type.uintptr.go" + } + ] + }, + "string_escaped_char": { + "patterns": [ + { + "match": "\\\\([0-7]{3}|[abfnrtv\\\\'\"]|x[0-9a-fA-F]{2}|u[0-9a-fA-F]{4}|U[0-9a-fA-F]{8})", + "name": "constant.character.escape.gop" + }, + { + "match": "\\\\[^0-7xuUabfnrtv\\'\"]", + "name": "invalid.illegal.unknown-escape.gop" + } + ] + }, + "string_placeholder": { + "patterns": [ + { + "match": "%(\\[\\d+\\])?([\\+#\\-0\\x20]{,2}((\\d+|\\*)?(\\.?(\\d+|\\*|(\\[\\d+\\])\\*?)?(\\[\\d+\\])?)?))?[vT%tbcdoqxXUbeEfFgGsp]", + "name": "constant.other.placeholder.gop" + } + ] + }, + "variables": { + "patterns": [ + { + "match": "(\\w+(?:,\\s*\\w+)*)(\\s+\\*?\\w+(?:\\.\\w+)?\\s*)?(?=\\s*=)", + "captures": { + "1": { + "patterns": [ + { + "match": "\\d\\w*", + "name": "invalid.illegal.identifier.gop" + }, + { + "match": "\\w+", + "name": "variable.other.assignment.gop" + }, + { + "include": "#delimiters" + } + ] + }, + "2": { + "patterns": [ + { + "include": "$self" + } + ] + } + } + }, + { + "match": "(\\w+(?:,\\s*\\w+)*)(\\s+(\\[(\\d*|\\.\\.\\.)\\])*\\*?(<-)?\\w+(?:\\.\\w+)?\\s*[^=].*)", + "captures": { + "1": { + "patterns": [ + { + "match": "\\d\\w*", + "name": "invalid.illegal.identifier.gop" + }, + { + "match": "\\w+", + "name": "variable.other.declaration.gop" + }, + { + "include": "#delimiters" + } + ] + }, + "2": { + "patterns": [ + { + "include": "$self" + } + ] + } + } + }, + { + "begin": "\\(", + "beginCaptures": { + "0": { + "name": "punctuation.definition.variables.begin.bracket.round.gop" + } + }, + "end": "\\)", + "endCaptures": { + "0": { + "name": "punctuation.definition.variables.end.bracket.round.gop" + } + }, + "patterns": [ + { + "include": "$self" + }, + { + "include": "#variables" + } + ] + } + ] + } + } +} \ No newline at end of file diff --git a/third_party/README.md b/third_party/README.md new file mode 100644 index 0000000..07a562b --- /dev/null +++ b/third_party/README.md @@ -0,0 +1,26 @@ +# Vendored dependencies + +third_party directory contains code from the third party including +vendored modules that need local modifications (e.g. bug fixes or +necessary enhancement before they are incorporated and released +in the upstream). Every directory must contain LICENSE files. + +The vendored node modules still need to be specified in the dependencies. +For example, after copying the `tree-kill` module to this directory +and applying necessary local modification, run from the root of this +project directory: + +``` +$ npm install --save ./third_party/tree-kill + +``` + +This will update `package.json` and `package-lock.json` to point to +the local dependency. + +Note: We didn't test vendoring platform-dependent modules yet. + + +## List of local modification + +`tree-kill`: vendored 1.2.2 with a fix for https://github.com/golang/vscode-go/issues/90 diff --git a/third_party/tree-kill/LICENSE b/third_party/tree-kill/LICENSE new file mode 100644 index 0000000..aa86c2d --- /dev/null +++ b/third_party/tree-kill/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2018 Peter Krumins + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/third_party/tree-kill/README.md b/third_party/tree-kill/README.md new file mode 100644 index 0000000..59a00ea --- /dev/null +++ b/third_party/tree-kill/README.md @@ -0,0 +1,89 @@ +Tree Kill +========= + +Kill all processes in the process tree, including the root process. + +Examples +======= + +Kill all the descendent processes of the process with pid `1`, including the process with pid `1` itself: +```js +var kill = require('tree-kill'); +kill(1); +``` + +Send a signal other than SIGTERM.: +```js +var kill = require('tree-kill'); +kill(1, 'SIGKILL'); +``` + +Run a callback when done killing the processes. Passes an error argument if there was an error. +```js +var kill = require('tree-kill'); +kill(1, 'SIGKILL', function(err) { + // Do things +}); +``` + +You can also install tree-kill globally and use it as a command: +```sh +tree-kill 1 # sends SIGTERM to process 1 and its descendents +tree-kill 1 SIGTERM # same +tree-kill 1 SIGKILL # sends KILL instead of TERMINATE +``` + +Methods +======= + +## require('tree-kill')(pid, [signal], [callback]); + +Sends signal `signal` to all children processes of the process with pid `pid`, including `pid`. Signal defaults to `SIGTERM`. + +For Linux, this uses `ps -o pid --no-headers --ppid PID` to find the parent pids of `PID`. + +For Darwin/OSX, this uses `pgrep -P PID` to find the parent pids of `PID`. + +For Windows, this uses `'taskkill /pid PID /T /F'` to kill the process tree. Note that on Windows, sending the different kinds of POSIX signals is not possible. + +Install +======= + +With [npm](https://npmjs.org) do: + +``` +npm install tree-kill +``` + +License +======= + +MIT + +Changelog +========= + + +## [1.2.2] - 2019-12-11 +### Changed +- security fix: sanitize `pid` parameter to fix arbitrary code execution vulnerability + +## [1.2.1] - 2018-11-05 +### Changed +- added missing LICENSE file +- updated TypeScript definitions + +## [1.2.0] - 2017-09-19 +### Added +- TypeScript definitions +### Changed +- `kill(pid, callback)` works. Before you had to use `kill(pid, signal, callback)` + +## [1.1.0] - 2016-05-13 +### Added +- A `tree-kill` CLI + +## [1.0.0] - 2015-09-17 +### Added +- optional callback +- Darwin support diff --git a/third_party/tree-kill/cli.js b/third_party/tree-kill/cli.js new file mode 100755 index 0000000..1acb815 --- /dev/null +++ b/third_party/tree-kill/cli.js @@ -0,0 +1,14 @@ +#!/usr/bin/env node +kill = require('.') +try { + kill(process.argv[2], process.argv[3], function(err){ + if (err) { + console.log(err.message) + process.exit(1) + } + }) +} +catch (err) { + console.log(err.message) + process.exit(1) +} diff --git a/third_party/tree-kill/index.d.ts b/third_party/tree-kill/index.d.ts new file mode 100644 index 0000000..e0b1302 --- /dev/null +++ b/third_party/tree-kill/index.d.ts @@ -0,0 +1,13 @@ +/** + * Kills process identified by `pid` and all its children + * + * @param pid + * @param signal 'SIGTERM' by default + * @param callback + */ +declare function treeKill(pid: number, callback?: (error?: Error) => void): void; +declare function treeKill(pid: number, signal?: string | number, callback?: (error?: Error) => void): void; + +declare namespace treeKill {} + +export = treeKill; diff --git a/third_party/tree-kill/index.js b/third_party/tree-kill/index.js new file mode 100755 index 0000000..9499a89 --- /dev/null +++ b/third_party/tree-kill/index.js @@ -0,0 +1,136 @@ +'use strict'; + +var childProcess = require('child_process'); +const { existsSync } = require('fs'); +var spawn = childProcess.spawn; +var exec = childProcess.exec; + +module.exports = function (pid, signal, callback) { + if (typeof signal === 'function' && callback === undefined) { + callback = signal; + signal = undefined; + } + + pid = parseInt(pid); + if (Number.isNaN(pid)) { + if (callback) { + return callback(new Error("pid must be a number")); + } else { + throw new Error("pid must be a number"); + } + } + + var tree = {}; + var pidsToProcess = {}; + tree[pid] = []; + pidsToProcess[pid] = 1; + + switch (process.platform) { + case 'win32': + exec('taskkill /pid ' + pid + ' /T /F', callback); + break; + case 'darwin': + buildProcessTree(pid, tree, pidsToProcess, function (parentPid) { + return spawn(pathToPgrep(), ['-P', parentPid]); + }, function () { + killAll(tree, signal, callback); + }); + break; + // case 'sunos': + // buildProcessTreeSunOS(pid, tree, pidsToProcess, function () { + // killAll(tree, signal, callback); + // }); + // break; + default: // Linux + buildProcessTree(pid, tree, pidsToProcess, function (parentPid) { + return spawn('ps', ['-o', 'pid', '--no-headers', '--ppid', parentPid]); + }, function () { + killAll(tree, signal, callback); + }); + break; + } +}; + +function killAll (tree, signal, callback) { + var killed = {}; + try { + Object.keys(tree).forEach(function (pid) { + tree[pid].forEach(function (pidpid) { + if (!killed[pidpid]) { + killPid(pidpid, signal); + killed[pidpid] = 1; + } + }); + if (!killed[pid]) { + killPid(pid, signal); + killed[pid] = 1; + } + }); + } catch (err) { + if (callback) { + return callback(err); + } else { + throw err; + } + } + if (callback) { + return callback(); + } +} + +function killPid(pid, signal) { + try { + process.kill(parseInt(pid, 10), signal); + } + catch (err) { + if (err.code !== 'ESRCH') throw err; + } +} + +function buildProcessTree (parentPid, tree, pidsToProcess, spawnChildProcessesList, cb) { + var ps = spawnChildProcessesList(parentPid); + var allData = ''; + ps.stdout.on('data', function (data) { + var data = data.toString('ascii'); + allData += data; + }); + + var onClose = function (code) { + delete pidsToProcess[parentPid]; + + if (code != 0) { + // no more parent processes + if (Object.keys(pidsToProcess).length == 0) { + cb(); + } + return; + } + + allData.match(/\d+/g).forEach(function (pid) { + pid = parseInt(pid, 10); + tree[parentPid].push(pid); + tree[pid] = []; + pidsToProcess[pid] = 1; + buildProcessTree(pid, tree, pidsToProcess, spawnChildProcessesList, cb); + }); + }; + + ps.on('close', onClose); +} + +var pgrep = ''; +function pathToPgrep () { + if (pgrep) { + return pgrep; + } + // Use the default pgrep, available since os x mountain lion. + // proctools' pgrep does not implement `-P` correctly and returns + // unrelated processes. + // https://github.com/golang/vscode-go/issues/90#issuecomment-634430428 + try { + pgrep = existsSync('/usr/bin/pgrep') ? '/usr/bin/pgrep' : 'pgrep'; + } catch (e) { + pgrep = 'pgrep'; + } + return pgrep; +} \ No newline at end of file diff --git a/third_party/tree-kill/package.json b/third_party/tree-kill/package.json new file mode 100644 index 0000000..68f0c49 --- /dev/null +++ b/third_party/tree-kill/package.json @@ -0,0 +1,82 @@ +{ + "_args": [ + [ + "tree-kill@1.2.2", + "/Users/hakim/projects/google/vscode-go" + ] + ], + "_from": "tree-kill@1.2.2", + "_id": "tree-kill@1.2.2", + "_inBundle": false, + "_integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==", + "_location": "/tree-kill", + "_phantomChildren": {}, + "_requested": { + "type": "version", + "registry": true, + "raw": "tree-kill@1.2.2", + "name": "tree-kill", + "escapedName": "tree-kill", + "rawSpec": "1.2.2", + "saveSpec": null, + "fetchSpec": "1.2.2" + }, + "_requiredBy": [ + "/" + ], + "_resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz", + "_spec": "1.2.2", + "_where": "/Users/hakim/projects/google/vscode-go", + "author": { + "name": "Peteris Krumins", + "email": "peteris.krumins@gmail.com", + "url": "http://www.catonmat.net" + }, + "bin": { + "tree-kill": "cli.js" + }, + "bugs": { + "url": "https://github.com/pkrumins/node-tree-kill/issues" + }, + "contributors": [ + { + "name": "Todd Wolfson", + "email": "todd@twolfson.com", + "url": "http://twolfson.com/" + }, + { + "name": "William Hilton", + "email": "wmhilton@gmail.com", + "url": "http://wmhilton.com/" + }, + { + "name": "Fabrício Matté", + "url": "http://ultcombo.js.org/" + } + ], + "description": "kill trees of processes", + "devDependencies": { + "mocha": "^2.2.5" + }, + "homepage": "https://github.com/pkrumins/node-tree-kill", + "keywords": [ + "tree", + "trees", + "process", + "processes", + "kill", + "signal" + ], + "license": "MIT", + "main": "index.js", + "name": "tree-kill", + "repository": { + "type": "git", + "url": "git://github.com/pkrumins/node-tree-kill.git" + }, + "scripts": { + "test": "mocha" + }, + "types": "index.d.ts", + "version": "1.2.2" +} diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..6045bef --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,23 @@ +{ + "compilerOptions": { + "module": "commonjs", + "outDir": "out", + "sourceMap": true, + "target": "es6", + "lib": [ + "es2015" + ], + //"strict": true, + "noImplicitAny": true, + "noImplicitThis": true, + "alwaysStrict": true, + "strictBindCallApply": true, + "strictFunctionTypes": true, + //"strictNullChecks": true, + //"strictPropertyInitialization": true, + }, + "exclude": [ + "node_modules", + "third_party" + ] +} \ No newline at end of file