├── .editorconfig ├── .gitignore ├── .travis.yml ├── .vscode ├── launch.json ├── settings.json └── tasks.json ├── .vscodeignore ├── CHANGELOG.md ├── LICENSE.txt ├── README.md ├── _config.yml ├── images ├── code completion example.png ├── code navigation example.png └── function signature example.png ├── package-lock.json ├── package.json ├── src ├── bencodeUtil.ts ├── cljConnection.ts ├── cljParser.ts ├── clojureConfiguration.ts ├── clojureDefinition.ts ├── clojureEval.ts ├── clojureFormat.ts ├── clojureHover.ts ├── clojureMain.ts ├── clojureMode.ts ├── clojureSignature.ts ├── clojureSuggest.ts ├── jarContentProvider.ts ├── nreplClient.ts ├── nreplController.ts └── testRunner.ts ├── test ├── bencodeUtil.test.ts ├── cljParser.test.ts ├── documents │ └── evalBlock.clj ├── extension.test.ts ├── index.ts └── utils.ts ├── tsconfig.json └── webpack.config.js /.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | 3 | root = true 4 | 5 | [*] 6 | charset = utf-8 7 | indent_style = space 8 | indent_size = 4 9 | end_of_line = lf 10 | insert_final_newline = true 11 | trim_trailing_whitespace = true 12 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | out 2 | node_modules 3 | .vscode/.browse* 4 | *.vsix 5 | *.vscode-test 6 | dist 7 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | os: 3 | - osx 4 | - linux 5 | node_js: 8 6 | 7 | install: 8 | - | 9 | if [ $TRAVIS_OS_NAME == "linux" ]; then 10 | export DISPLAY=':99.0' 11 | /usr/bin/Xvfb :99 -screen 0 1024x768x24 > /dev/null 2>&1 & 12 | fi 13 | script: 14 | - | 15 | echo ">>> Compile clojureVSCode" 16 | npm install 17 | npm run test-compile 18 | echo ">>> Run clojureVSCode unit tests" 19 | npm run test --silent 20 | cache: npm 21 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | // A launch configuration that compiles the extension and then opens it inside a new window 2 | { 3 | "version": "0.1.0", 4 | "configurations": [ 5 | { 6 | "name": "Launch Extension", 7 | "type": "extensionHost", 8 | "request": "launch", 9 | "runtimeExecutable": "${execPath}", 10 | "args": ["--extensionDevelopmentPath=${workspaceRoot}" ], 11 | "stopOnEntry": false, 12 | "sourceMaps": true, 13 | "outFiles": ["${workspaceRoot}/dist/*.js"], 14 | "preLaunchTask": "npm: webpack" 15 | }, 16 | { 17 | "name": "Launch Tests", 18 | "type": "extensionHost", 19 | "request": "launch", 20 | "runtimeExecutable": "${execPath}", 21 | "args": [ 22 | "--disable-extensions", 23 | "--extensionDevelopmentPath=${workspaceRoot}", 24 | "--extensionTestsPath=${workspaceRoot}/out/test" 25 | ], 26 | "stopOnEntry": false, 27 | "sourceMaps": true, 28 | "outFiles": ["${workspaceRoot}/out/test/**/*.js"], 29 | "preLaunchTask": "npm: test-compile" 30 | } 31 | ] 32 | } 33 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | // Place your settings in this file to overwrite default and user settings. 2 | { 3 | "files.exclude": { 4 | "out": false, // set this to true to hide the "out" folder with the compiled JS files 5 | "node_modules": true 6 | }, 7 | "search.exclude": { 8 | "out": true, // set this to false to include "out" folder in search results 9 | "node_modules": true 10 | }, 11 | "typescript.tsdk": "./node_modules/typescript/lib" // we want to use the TS server from our node_modules folder to control its version 12 | } 13 | -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | // Available variables which can be used inside of strings. 2 | // ${workspaceRoot}: the root folder of the team 3 | // ${file}: the current opened file 4 | // ${fileBasename}: the current opened file's basename 5 | // ${fileDirname}: the current opened file's dirname 6 | // ${fileExtname}: the current opened file's extension 7 | // ${cwd}: the current working directory of the spawned process 8 | 9 | // A task runner that calls a custom npm script that compiles the extension. 10 | { 11 | "version": "2.0.0", 12 | 13 | // we want to run npm 14 | "command": "npm", 15 | 16 | // the command is a shell script 17 | "type": "shell", 18 | 19 | // show the output window only if unrecognized errors occur. 20 | "showOutput": "silent", 21 | 22 | // we run the custom script "compile" as defined in package.json 23 | "args": ["run", "compile", "--loglevel", "silent"], 24 | 25 | // The tsc compiler is started in watching mode 26 | "isBackground": true, 27 | 28 | // use the standard tsc in watch mode problem matcher to find compile problems in the output. 29 | "problemMatcher": "$tsc-watch" 30 | } 31 | -------------------------------------------------------------------------------- /.vscodeignore: -------------------------------------------------------------------------------- 1 | .vscode/** 2 | .vscode-test/** 3 | out/** 4 | test/** 5 | src/** 6 | **/*.map 7 | .gitignore 8 | tsconfig.json 9 | webpack.config.js 10 | node_modules/** 11 | _config.yml 12 | images/** 13 | !images/icon.png 14 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Version 0.13.2 2 | 3 | Update the dependencies. Remove the logo (see [168](https://github.com/avli/clojureVSCode/issues/168)). 4 | 5 | # Version 0.13.1 6 | 7 | Updates the embedded nREPL dependencies. Add settings for explicitly setting these dependencies versions. 8 | 9 | # Version 0.13.0 10 | 11 | Adds the ability to show evaluation results inline: 12 | 13 | ![inline-results](https://user-images.githubusercontent.com/1375411/74069697-13c79300-4a10-11ea-9faf-3c2e09c55675.gif) 14 | 15 | Adds the ability to show a large evaluation result [#134](https://github.com/avli/clojureVSCode/issues/134). 16 | 17 | # Version 0.12.0 18 | 19 | Uses VSCode built-in formatting provider to format code [#90](https://github.com/avli/clojureVSCode/issues/90). 20 | 21 | # Version 0.11.3 22 | 23 | Bundles the extension in order to minimize its size. 24 | 25 | # Version 0.11.2 26 | 27 | Makes it possible to pick definitions from JAR files (Clojure only) [#111](https://github.com/avli/clojureVSCode/issues/111). 28 | 29 | # Version 0.11.1 30 | 31 | Fix-up release. Reenable automatic nREPL starting. 32 | 33 | # Version 0.11.0 34 | 35 | Adds the ability to evaluate blocks of code without selection [#61](https://github.com/avli/clojureVSCode/issues/61): 36 | 37 | ![eval-block](https://user-images.githubusercontent.com/1375411/68340984-874bc280-00f8-11ea-9eea-34c7160b8c05.gif) 38 | 39 | Use the "Clojure: Eval and show the result command" for evaluation of the current 40 | code block and "Clojure: Eval" for the outer. 41 | 42 | Fixes the issue with completion in the middle of a line [#139](https://github.com/avli/clojureVSCode/issues/139). 43 | 44 | # Version 0.10.5 45 | 46 | Fix namespace evaluation for modules beginning with a semicolon. 47 | 48 | # Version 0.10.4 49 | 50 | Show the full stacktrace on an evaluation error. 51 | 52 | # Version 0.10.3 53 | 54 | Updates the embedded nREPL cider-nrepl version. 55 | 56 | # Version 0.10.2 57 | 58 | Output window now preserves focus. 59 | 60 | # Version 0.10.1 61 | 62 | Fixes the issue with the "Eval" command. 63 | 64 | # Version 0.10.0 65 | 66 | Adds integration with Clojure unit test framework. 67 | 68 | ![Test results UI demo](https://user-images.githubusercontent.com/448001/39921203-ee8e30b6-5511-11e8-843a-690dd8624b87.gif) 69 | 70 | # Version 0.9.8 71 | 72 | Temporary removes `refactor-nrepl` from the dependencies of the embedded nREPL at least untile [this issue](https://github.com/clojure-emacs/refactor-nrepl/issues/206) won't be fixed. 73 | 74 | # Version 0.9.7 75 | 76 | Fixes the behavior of nREPL connection. If a remote nREPL is closed evaluation of code will show the message about it and the connection indicator will be removed. 77 | 78 | # Version 0.9.6 79 | 80 | Adds the [Connecting to the REPL](https://github.com/avli/clojureVSCode#connecting-to-the-repl) section to README.md and Slightly changes the behavior of the nREPL output channel only bringing it to the foreground on error. 81 | 82 | # Version 0.9.5 83 | 84 | Adds a channel for nREPL output to the Output Window. 85 | 86 | # Version 0.9.4 87 | 88 | Adds the `:headless` option to the embedded nREPL and [clojure-warrior](https://marketplace.visualstudio.com/items?itemName=tonsky.clojure-warrior) to the extension recommendations. 89 | 90 | # Version 0.9.3 91 | 92 | Adds support of `cljfmt` options. 93 | 94 | # Version 0.9.2 95 | 96 | Adds Clojure 1.9 support. 97 | 98 | # Version 0.9.1 99 | 100 | Adds a configuration option for formatting code on save. 101 | 102 | # Version 0.9.0 103 | 104 | Adds experimental ClojureScript support. Please check out [README.md](https://github.com/avli/clojureVSCode#clojurescript-project-setup) to learn how to use the extension for ClojureScript. 105 | 106 | # Version 0.8.2 107 | 108 | Fixes the issue with unescaping newlines that are parts of a string [#64](https://github.com/avli/clojureVSCode/issues/64). 109 | 110 | # Version 0.8.1 111 | 112 | Adds a configuration option to disable an embedded nREPL start. 113 | 114 | # Version 0.8.0 115 | 116 | Adds code formatting support [#57](https://github.com/avli/clojureVSCode/issues/57). 117 | 118 | # Version 0.7.10 119 | 120 | Adds workaround for the hanging Java process issue on the Windows platform [#56](https://github.com/avli/clojureVSCode/issues/56). 121 | 122 | # Version 0.7.9 123 | 124 | Hotfix that detaches nREPL process on UNIX and non-detached one on Windows [#56](https://github.com/avli/clojureVSCode/issues/56). 125 | 126 | # Version 0.7.8 127 | 128 | Fixes the troubles with a hanging Java process [#56](https://github.com/avli/clojureVSCode/issues/56). 129 | 130 | # Version 0.7.7 131 | 132 | Fixes the error with embedded nREPL on Windows. 133 | 134 | # Version 0.7.6 135 | 136 | Does code refactoring and minor nREPL connection indicator UI fixes. 137 | 138 | # Version 0.7.5 139 | 140 | Fixes the `Unable to start nREPL` bug [#47](https://github.com/avli/clojureVSCode/issues/47). 141 | 142 | # Version 0.7.4 143 | 144 | Fixes the nREPL connection indicator behavior [#42](https://github.com/avli/clojureVSCode/issues/42). 145 | 146 | # Version 0.7.3 147 | 148 | Fixes typos in documentation. 149 | 150 | # Version 0.7.2 151 | 152 | Fixes README.md formatting to display correctly on the Visual Studio Marketplace web-site. 153 | 154 | # Version 0.7.1 155 | 156 | Fixes the links to the images in README.md. 157 | 158 | # Version 0.7.0 159 | 160 | Adds embedded nREPL functionality – no need to connect manually anymore. 161 | [#21](https://github.com/avli/clojureVSCode/issues/21). 162 | 163 | # Version 0.6.1 164 | 165 | Adds the forgotten changelog for the version 0.6.0 :-) 166 | 167 | # Version 0.6.0 168 | 169 | Changes the behavior of the `Eval` command - now it shows compilation errors in the `Output` panel [#28](https://github.com/avli/clojureVSCode/issues/28). 170 | 171 | # Version 0.5.4 172 | 173 | Fixes possible bug with missing return from the `provideDefinition` function [#26](https://github.com/avli/clojureVSCode/issues/26). 174 | 175 | # Version 0.5.3 176 | 177 | Cleans up the documentation a little 178 | 179 | # Version 0.5.2 180 | 181 | Improves better support of special form signatures. 182 | 183 | # Version 0.5.1 184 | 185 | Updates the information about the supported features. 186 | 187 | # Version 0.5.0 188 | 189 | Adds signature helper [#6](https://github.com/avli/clojureVSCode/issues/8). 190 | 191 | # Version 0.4.3 192 | 193 | Fixes the issue when only one output line is printed [#9](https://github.com/avli/clojureVSCode/issues/9). 194 | 195 | # Version 0.4.2 196 | 197 | Shows output of the "Eval" command [#5](https://github.com/avli/clojureVSCode/issues/5). 198 | 199 | # Version 0.4.0 200 | 201 | The first version public version. 202 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2017 Andrey Lisin 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # clojureVSCode 2 | 3 | [![Version](https://vsmarketplacebadge.apphb.com/version/avli.clojure.svg)](https://marketplace.visualstudio.com/items?itemName=avli.clojure) [![Build Status](https://travis-ci.org/avli/clojureVSCode.svg?branch=master)](https://travis-ci.org/avli/clojureVSCode) 4 | 5 | [Clojure](https://clojure.org) and [ClojureScript](https://clojurescript.org) support for Visual Studio Code. 6 | 7 | If you are a ClojureScript user, please read [this section](#clojurescript-project-setup) carefully. 8 | 9 | I'm trying, believe me! 10 | 11 | ## Quickstart 12 | 13 | Make sure that [Leiningen](https://leiningen.org/) is installed on your machine, open a Clojure file or project, wait until the extension starts the nREPL (see status on the bottom of the VSCode window) and [connect to it](#connecting-to-the-repl) - now all the goodies should work :-) 14 | 15 | Doesn't work? Not exactly what you need? See the [Manual Configuration section](#manual-configuration)! 16 | 17 | ## Supported Features 18 | 19 | * Code completion 20 | 21 | ![Code completion example](https://github.com/avli/clojureVSCode/raw/master/images/code%20completion%20example.png) 22 | 23 | * Code navigation 24 | 25 | ![Code navigationtion example](https://github.com/avli/clojureVSCode/raw/master/images/code%20navigation%20example.png) 26 | 27 | * Interaction with REPL 28 | * Showing documentation on hover 29 | * Code formatting ([cljfmt](https://github.com/weavejester/cljfmt)) 30 | * Function signatures 31 | * Integration with the Clojure unit test framework 32 | 33 | ![Code completion example](https://github.com/avli/clojureVSCode/raw/master/images/function%20signature%20example.png) 34 | 35 | ## Features That Are not Supported (but Nice to Have) 36 | 37 | * Linting 38 | * [Debug](https://github.com/indiejames/vscode-clojure-debug) 39 | 40 | ## Connecting to the REPL 41 | 42 | - Open a terminal (either the one embedded in VSCode or a separate one) 43 | - Change directory to the root directory of the Clojure project (where the REPL started by clojureVSCode will have updated the hidden file `.nrepl-port`) 44 | - with lein, do `lein repl :connect`. 45 | 46 | ## Evaluating code in the REPL 47 | 48 | `Clojure: Eval` (in the command palette) will compile the current file in the editor and load it in the REPL. 49 | 50 | ## Manual Configuration 51 | 52 | The method from the [Quickstart section](#Quickstart) utilizes the so-called embedded nREPL that is run as an internal process. Sometimes you need more control on your development environment. In this case you can disable the automatical firing of the embedded nREPL by setting the 53 | 54 | ```json 55 | 56 | "clojureVSCode.autoStartNRepl": true 57 | 58 | ``` 59 | 60 | option in your VSCode settings globally or per-project and connect manually to whichever REPL instance you want by "Clojure: Connect to a running nREPL" command. Note, that in order to make the autocompletion, go to definition, and formatting functionality work you have to write necessary dependencies in your `profiles.clj`. Put the following content to your `~/.lein/profiles.clj` for macOS and Linux: 61 | 62 | ```clojure 63 | {:user {:plugins [[cider/cider-nrepl "0.24.0"]] 64 | :dependencies [[cljfmt "0.6.7"]]}} 65 | ``` 66 | 67 | Alternatively, you can put the code above to your project `project.clj` file. 68 | 69 | ## Contributed Configuration 70 | 71 | The extension contributes the configuration parameters listed in the table below. 72 | 73 | | Parameter | Description | 74 | |---------------------------------|-------------| 75 | |`clojureVSCode.autoStartNRepl` | Whether to start an nREPL when opening a file or project. | 76 | |`clojureVSCode.formatOnSave` | Format files with [cljfmt](https://github.com/weavejester/cljfmt) on save. | 77 | |`clojureVSCode.cljfmtParameters` | Formatting parameters passed to `cljfmt` each time it runs, e.g. `:indentation? true :remove-surrounding-whitespace? false` | 78 | |`clojureVSCode.showResultInline` | Show evaluation result inline. | 79 | |`clojureVSCode.ciderNReplVersion`| Version of [CIDER nREPL](https://github.com/clojure-emacs/cider-nrepl) to use for the embedded nREPL. | 80 | |`clojureVSCode.cljfmtVersion` | Version of `cljfmt` to use for formatting Clojure files. | 81 | 82 | ## ClojureScript Project Setup 83 | 84 | The extension has the experimental support of ClojureScript. The example of a ClojureScript project setup can be found [here](https://github.com/avli/clojurescript-example-project). Checkout the project `profile.clj` file to learn what dependencies you need. 85 | 86 | The embedded nREPL **does not** support ClojureScript, consider to use the "clojureVSCode.autoStartNRepl" setting. You will need to run an nREPL manually and execute the following commands inside it: 87 | 88 | ```clojure 89 | (require 'cljs.repl.node) 90 | (cider.piggieback/cljs-repl (cljs.repl.node/repl-env)) 91 | ``` 92 | 93 | After that you can connect to the nREPL using the "Clojure: Connect to a running nREPL" command. Now you can evaluate you ClojureScript code and use the other extension facilities. 94 | 95 | ## Troubleshooting 96 | 97 | ### All kinds of errors on nREPL start 98 | 99 | Please check that you're using the latest version of [CIDER nREPL](https://github.com/clojure-emacs/cider-nrepl). The version the extension uses by default updates periodically, but there still can be a mismatch. In order to redefined the `CIDER nREPL` version you can either: 100 | 101 | 1. Define it in the `~/.lein/profiles.clj` (see the "Manual Configuration" section above). 102 | 2. Redefine it with the `clojureVSCode.cljfmtVersion` extension setting. 103 | 104 | ## How to Contribute 105 | 106 | Open an [issue](https://github.com/avli/clojureVSCode/issues) if you want to propose new features and ideas or to report bugs. If you want to help with some code and looking for a place to start, please check out the [How to Contribute](https://github.com/avli/clojureVSCode/wiki/Contribution) wiki page. 107 | 108 | ## Thanks 109 | 110 | - [Thiago Almeida](https://github.com/fasfsfgs) 111 | - [Mike Ball](https://github.com/mikeball) 112 | - [Egor Yurtaev](https://github.com/yurtaev) 113 | - [Mark Hansen](https://github.com/mhansen) 114 | - [Fabian Achammer](https://github.com/fachammer) 115 | - [Nikita Prokopov](https://github.com/tonsky) 116 | - [Alessandro Decina](https://github.com/alessandrod) 117 | - [Marc O'Morain](https://github.com/marcomorain) 118 | - [Andrey Bogoyavlensky](https://github.com/abogoyavlensky) 119 | 120 | ## License 121 | 122 | [MIT](https://raw.githubusercontent.com/avli/clojureVSCode/master/LICENSE.txt) 123 | -------------------------------------------------------------------------------- /_config.yml: -------------------------------------------------------------------------------- 1 | theme: jekyll-theme-midnight -------------------------------------------------------------------------------- /images/code completion example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/avli/clojureVSCode/9fe8692005143674d4fa65ed2d2b17cde3065601/images/code completion example.png -------------------------------------------------------------------------------- /images/code navigation example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/avli/clojureVSCode/9fe8692005143674d4fa65ed2d2b17cde3065601/images/code navigation example.png -------------------------------------------------------------------------------- /images/function signature example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/avli/clojureVSCode/9fe8692005143674d4fa65ed2d2b17cde3065601/images/function signature example.png -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "clojure", 3 | "version": "0.13.2", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "@discoveryjs/json-ext": { 8 | "version": "0.5.7", 9 | "resolved": "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-0.5.7.tgz", 10 | "integrity": "sha512-dBVuXR082gk3jsFp7Rd/JI4kytwGHecnCoTtXFb7DB6CNHp4rg5k1bhg0nWdLGLnOV71lmDzGQaLMy8iPLY0pw==", 11 | "dev": true 12 | }, 13 | "@jridgewell/gen-mapping": { 14 | "version": "0.3.5", 15 | "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz", 16 | "integrity": "sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==", 17 | "dev": true, 18 | "requires": { 19 | "@jridgewell/set-array": "^1.2.1", 20 | "@jridgewell/sourcemap-codec": "^1.4.10", 21 | "@jridgewell/trace-mapping": "^0.3.24" 22 | } 23 | }, 24 | "@jridgewell/resolve-uri": { 25 | "version": "3.1.2", 26 | "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", 27 | "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", 28 | "dev": true 29 | }, 30 | "@jridgewell/set-array": { 31 | "version": "1.2.1", 32 | "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", 33 | "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", 34 | "dev": true 35 | }, 36 | "@jridgewell/source-map": { 37 | "version": "0.3.6", 38 | "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.6.tgz", 39 | "integrity": "sha512-1ZJTZebgqllO79ue2bm3rIGud/bOe0pP5BjSRCRxxYkEZS8STV7zN84UBbiYu7jy+eCKSnVIUgoWWE/tt+shMQ==", 40 | "dev": true, 41 | "requires": { 42 | "@jridgewell/gen-mapping": "^0.3.5", 43 | "@jridgewell/trace-mapping": "^0.3.25" 44 | } 45 | }, 46 | "@jridgewell/sourcemap-codec": { 47 | "version": "1.5.0", 48 | "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", 49 | "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", 50 | "dev": true 51 | }, 52 | "@jridgewell/trace-mapping": { 53 | "version": "0.3.25", 54 | "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", 55 | "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", 56 | "dev": true, 57 | "requires": { 58 | "@jridgewell/resolve-uri": "^3.1.0", 59 | "@jridgewell/sourcemap-codec": "^1.4.14" 60 | } 61 | }, 62 | "@tootallnate/once": { 63 | "version": "1.1.2", 64 | "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-1.1.2.tgz", 65 | "integrity": "sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw==", 66 | "dev": true 67 | }, 68 | "@types/cross-spawn": { 69 | "version": "6.0.2", 70 | "resolved": "https://registry.npmjs.org/@types/cross-spawn/-/cross-spawn-6.0.2.tgz", 71 | "integrity": "sha512-KuwNhp3eza+Rhu8IFI5HUXRP0LIhqH5cAjubUvGXXthh4YYBuP2ntwEX+Cz8GJoZUHlKo247wPWOfA9LYEq4cw==", 72 | "dev": true, 73 | "requires": { 74 | "@types/node": "*" 75 | } 76 | }, 77 | "@types/estree": { 78 | "version": "1.0.5", 79 | "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz", 80 | "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==", 81 | "dev": true 82 | }, 83 | "@types/json-schema": { 84 | "version": "7.0.15", 85 | "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", 86 | "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", 87 | "dev": true 88 | }, 89 | "@types/jszip": { 90 | "version": "3.4.1", 91 | "resolved": "https://registry.npmjs.org/@types/jszip/-/jszip-3.4.1.tgz", 92 | "integrity": "sha512-TezXjmf3lj+zQ651r6hPqvSScqBLvyPI9FxdXBqpEwBijNGQ2NXpaFW/7joGzveYkKQUil7iiDHLo6LV71Pc0A==", 93 | "dev": true, 94 | "requires": { 95 | "jszip": "*" 96 | } 97 | }, 98 | "@types/mocha": { 99 | "version": "2.2.48", 100 | "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-2.2.48.tgz", 101 | "integrity": "sha512-nlK/iyETgafGli8Zh9zJVCTicvU3iajSkRwOh3Hhiva598CMqNJ4NcVCGMTGKpGpTYj/9R8RLzS9NAykSSCqGw==", 102 | "dev": true 103 | }, 104 | "@types/node": { 105 | "version": "6.14.10", 106 | "resolved": "https://registry.npmjs.org/@types/node/-/node-6.14.10.tgz", 107 | "integrity": "sha512-pF4HjZGSog75kGq7B1InK/wt/N08BuPATo+7HRfv7gZUzccebwv/fmWVGs/j6LvSiLWpCuGGhql51M/wcQsNzA==", 108 | "dev": true 109 | }, 110 | "@webassemblyjs/ast": { 111 | "version": "1.12.1", 112 | "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.12.1.tgz", 113 | "integrity": "sha512-EKfMUOPRRUTy5UII4qJDGPpqfwjOmZ5jeGFwid9mnoqIFK+e0vqoi1qH56JpmZSzEL53jKnNzScdmftJyG5xWg==", 114 | "dev": true, 115 | "requires": { 116 | "@webassemblyjs/helper-numbers": "1.11.6", 117 | "@webassemblyjs/helper-wasm-bytecode": "1.11.6" 118 | } 119 | }, 120 | "@webassemblyjs/floating-point-hex-parser": { 121 | "version": "1.11.6", 122 | "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.11.6.tgz", 123 | "integrity": "sha512-ejAj9hfRJ2XMsNHk/v6Fu2dGS+i4UaXBXGemOfQ/JfQ6mdQg/WXtwleQRLLS4OvfDhv8rYnVwH27YJLMyYsxhw==", 124 | "dev": true 125 | }, 126 | "@webassemblyjs/helper-api-error": { 127 | "version": "1.11.6", 128 | "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.11.6.tgz", 129 | "integrity": "sha512-o0YkoP4pVu4rN8aTJgAyj9hC2Sv5UlkzCHhxqWj8butaLvnpdc2jOwh4ewE6CX0txSfLn/UYaV/pheS2Txg//Q==", 130 | "dev": true 131 | }, 132 | "@webassemblyjs/helper-buffer": { 133 | "version": "1.12.1", 134 | "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.12.1.tgz", 135 | "integrity": "sha512-nzJwQw99DNDKr9BVCOZcLuJJUlqkJh+kVzVl6Fmq/tI5ZtEyWT1KZMyOXltXLZJmDtvLCDgwsyrkohEtopTXCw==", 136 | "dev": true 137 | }, 138 | "@webassemblyjs/helper-numbers": { 139 | "version": "1.11.6", 140 | "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.11.6.tgz", 141 | "integrity": "sha512-vUIhZ8LZoIWHBohiEObxVm6hwP034jwmc9kuq5GdHZH0wiLVLIPcMCdpJzG4C11cHoQ25TFIQj9kaVADVX7N3g==", 142 | "dev": true, 143 | "requires": { 144 | "@webassemblyjs/floating-point-hex-parser": "1.11.6", 145 | "@webassemblyjs/helper-api-error": "1.11.6", 146 | "@xtuc/long": "4.2.2" 147 | } 148 | }, 149 | "@webassemblyjs/helper-wasm-bytecode": { 150 | "version": "1.11.6", 151 | "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.11.6.tgz", 152 | "integrity": "sha512-sFFHKwcmBprO9e7Icf0+gddyWYDViL8bpPjJJl0WHxCdETktXdmtWLGVzoHbqUcY4Be1LkNfwTmXOJUFZYSJdA==", 153 | "dev": true 154 | }, 155 | "@webassemblyjs/helper-wasm-section": { 156 | "version": "1.12.1", 157 | "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.12.1.tgz", 158 | "integrity": "sha512-Jif4vfB6FJlUlSbgEMHUyk1j234GTNG9dBJ4XJdOySoj518Xj0oGsNi59cUQF4RRMS9ouBUxDDdyBVfPTypa5g==", 159 | "dev": true, 160 | "requires": { 161 | "@webassemblyjs/ast": "1.12.1", 162 | "@webassemblyjs/helper-buffer": "1.12.1", 163 | "@webassemblyjs/helper-wasm-bytecode": "1.11.6", 164 | "@webassemblyjs/wasm-gen": "1.12.1" 165 | } 166 | }, 167 | "@webassemblyjs/ieee754": { 168 | "version": "1.11.6", 169 | "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.11.6.tgz", 170 | "integrity": "sha512-LM4p2csPNvbij6U1f19v6WR56QZ8JcHg3QIJTlSwzFcmx6WSORicYj6I63f9yU1kEUtrpG+kjkiIAkevHpDXrg==", 171 | "dev": true, 172 | "requires": { 173 | "@xtuc/ieee754": "^1.2.0" 174 | } 175 | }, 176 | "@webassemblyjs/leb128": { 177 | "version": "1.11.6", 178 | "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.11.6.tgz", 179 | "integrity": "sha512-m7a0FhE67DQXgouf1tbN5XQcdWoNgaAuoULHIfGFIEVKA6tu/edls6XnIlkmS6FrXAquJRPni3ZZKjw6FSPjPQ==", 180 | "dev": true, 181 | "requires": { 182 | "@xtuc/long": "4.2.2" 183 | } 184 | }, 185 | "@webassemblyjs/utf8": { 186 | "version": "1.11.6", 187 | "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.11.6.tgz", 188 | "integrity": "sha512-vtXf2wTQ3+up9Zsg8sa2yWiQpzSsMyXj0qViVP6xKGCUT8p8YJ6HqI7l5eCnWx1T/FYdsv07HQs2wTFbbof/RA==", 189 | "dev": true 190 | }, 191 | "@webassemblyjs/wasm-edit": { 192 | "version": "1.12.1", 193 | "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.12.1.tgz", 194 | "integrity": "sha512-1DuwbVvADvS5mGnXbE+c9NfA8QRcZ6iKquqjjmR10k6o+zzsRVesil54DKexiowcFCPdr/Q0qaMgB01+SQ1u6g==", 195 | "dev": true, 196 | "requires": { 197 | "@webassemblyjs/ast": "1.12.1", 198 | "@webassemblyjs/helper-buffer": "1.12.1", 199 | "@webassemblyjs/helper-wasm-bytecode": "1.11.6", 200 | "@webassemblyjs/helper-wasm-section": "1.12.1", 201 | "@webassemblyjs/wasm-gen": "1.12.1", 202 | "@webassemblyjs/wasm-opt": "1.12.1", 203 | "@webassemblyjs/wasm-parser": "1.12.1", 204 | "@webassemblyjs/wast-printer": "1.12.1" 205 | } 206 | }, 207 | "@webassemblyjs/wasm-gen": { 208 | "version": "1.12.1", 209 | "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.12.1.tgz", 210 | "integrity": "sha512-TDq4Ojh9fcohAw6OIMXqiIcTq5KUXTGRkVxbSo1hQnSy6lAM5GSdfwWeSxpAo0YzgsgF182E/U0mDNhuA0tW7w==", 211 | "dev": true, 212 | "requires": { 213 | "@webassemblyjs/ast": "1.12.1", 214 | "@webassemblyjs/helper-wasm-bytecode": "1.11.6", 215 | "@webassemblyjs/ieee754": "1.11.6", 216 | "@webassemblyjs/leb128": "1.11.6", 217 | "@webassemblyjs/utf8": "1.11.6" 218 | } 219 | }, 220 | "@webassemblyjs/wasm-opt": { 221 | "version": "1.12.1", 222 | "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.12.1.tgz", 223 | "integrity": "sha512-Jg99j/2gG2iaz3hijw857AVYekZe2SAskcqlWIZXjji5WStnOpVoat3gQfT/Q5tb2djnCjBtMocY/Su1GfxPBg==", 224 | "dev": true, 225 | "requires": { 226 | "@webassemblyjs/ast": "1.12.1", 227 | "@webassemblyjs/helper-buffer": "1.12.1", 228 | "@webassemblyjs/wasm-gen": "1.12.1", 229 | "@webassemblyjs/wasm-parser": "1.12.1" 230 | } 231 | }, 232 | "@webassemblyjs/wasm-parser": { 233 | "version": "1.12.1", 234 | "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.12.1.tgz", 235 | "integrity": "sha512-xikIi7c2FHXysxXe3COrVUPSheuBtpcfhbpFj4gmu7KRLYOzANztwUU0IbsqvMqzuNK2+glRGWCEqZo1WCLyAQ==", 236 | "dev": true, 237 | "requires": { 238 | "@webassemblyjs/ast": "1.12.1", 239 | "@webassemblyjs/helper-api-error": "1.11.6", 240 | "@webassemblyjs/helper-wasm-bytecode": "1.11.6", 241 | "@webassemblyjs/ieee754": "1.11.6", 242 | "@webassemblyjs/leb128": "1.11.6", 243 | "@webassemblyjs/utf8": "1.11.6" 244 | } 245 | }, 246 | "@webassemblyjs/wast-printer": { 247 | "version": "1.12.1", 248 | "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.12.1.tgz", 249 | "integrity": "sha512-+X4WAlOisVWQMikjbcvY2e0rwPsKQ9F688lksZhBcPycBBuii3O7m8FACbDMWDojpAqvjIncrG8J0XHKyQfVeA==", 250 | "dev": true, 251 | "requires": { 252 | "@webassemblyjs/ast": "1.12.1", 253 | "@xtuc/long": "4.2.2" 254 | } 255 | }, 256 | "@webpack-cli/configtest": { 257 | "version": "2.0.1", 258 | "resolved": "https://registry.npmjs.org/@webpack-cli/configtest/-/configtest-2.0.1.tgz", 259 | "integrity": "sha512-njsdJXJSiS2iNbQVS0eT8A/KPnmyH4pv1APj2K0d1wrZcBLw+yppxOy4CGqa0OxDJkzfL/XELDhD8rocnIwB5A==", 260 | "dev": true 261 | }, 262 | "@webpack-cli/info": { 263 | "version": "2.0.1", 264 | "resolved": "https://registry.npmjs.org/@webpack-cli/info/-/info-2.0.1.tgz", 265 | "integrity": "sha512-fE1UEWTwsAxRhrJNikE7v4EotYflkEhBL7EbajfkPlf6E37/2QshOy/D48Mw8G5XMFlQtS6YV42vtbG9zBpIQA==", 266 | "dev": true 267 | }, 268 | "@webpack-cli/serve": { 269 | "version": "2.0.1", 270 | "resolved": "https://registry.npmjs.org/@webpack-cli/serve/-/serve-2.0.1.tgz", 271 | "integrity": "sha512-0G7tNyS+yW8TdgHwZKlDWYXFA6OJQnoLCQvYKkQP0Q2X205PSQ6RNUj0M+1OB/9gRQaUZ/ccYfaxd0nhaWKfjw==", 272 | "dev": true 273 | }, 274 | "@xtuc/ieee754": { 275 | "version": "1.2.0", 276 | "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", 277 | "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==", 278 | "dev": true 279 | }, 280 | "@xtuc/long": { 281 | "version": "4.2.2", 282 | "resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz", 283 | "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==", 284 | "dev": true 285 | }, 286 | "acorn": { 287 | "version": "8.12.1", 288 | "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.12.1.tgz", 289 | "integrity": "sha512-tcpGyI9zbizT9JbV6oYE477V6mTlXvvi0T0G3SNIYE2apm/G5huBa1+K89VGeovbg+jycCrfhl3ADxErOuO6Jg==", 290 | "dev": true 291 | }, 292 | "acorn-import-attributes": { 293 | "version": "1.9.5", 294 | "resolved": "https://registry.npmjs.org/acorn-import-attributes/-/acorn-import-attributes-1.9.5.tgz", 295 | "integrity": "sha512-n02Vykv5uA3eHGM/Z2dQrcD56kL8TyDb2p1+0P83PClMnC/nc+anbQRhIOWnSq4Ke/KvDPrY3C9hDtC/A3eHnQ==", 296 | "dev": true 297 | }, 298 | "agent-base": { 299 | "version": "6.0.0", 300 | "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.0.tgz", 301 | "integrity": "sha512-j1Q7cSCqN+AwrmDd+pzgqc0/NpC655x2bUf5ZjRIO77DcNBFmh+OgRNzF6OKdCC9RSCb19fGd99+bhXFdkRNqw==", 302 | "dev": true, 303 | "requires": { 304 | "debug": "4" 305 | }, 306 | "dependencies": { 307 | "debug": { 308 | "version": "4.1.1", 309 | "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", 310 | "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", 311 | "dev": true, 312 | "requires": { 313 | "ms": "^2.1.1" 314 | } 315 | }, 316 | "ms": { 317 | "version": "2.1.2", 318 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", 319 | "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", 320 | "dev": true 321 | } 322 | } 323 | }, 324 | "ajv": { 325 | "version": "6.12.6", 326 | "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", 327 | "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", 328 | "dev": true, 329 | "requires": { 330 | "fast-deep-equal": "^3.1.1", 331 | "fast-json-stable-stringify": "^2.0.0", 332 | "json-schema-traverse": "^0.4.1", 333 | "uri-js": "^4.2.2" 334 | } 335 | }, 336 | "ajv-keywords": { 337 | "version": "3.5.2", 338 | "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", 339 | "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", 340 | "dev": true 341 | }, 342 | "ansi-styles": { 343 | "version": "4.3.0", 344 | "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", 345 | "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", 346 | "dev": true, 347 | "requires": { 348 | "color-convert": "^2.0.1" 349 | } 350 | }, 351 | "balanced-match": { 352 | "version": "1.0.0", 353 | "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", 354 | "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", 355 | "dev": true 356 | }, 357 | "bencoder": { 358 | "version": "0.0.5", 359 | "resolved": "https://registry.npmjs.org/bencoder/-/bencoder-0.0.5.tgz", 360 | "integrity": "sha1-uwluE1yila8Wuv7Gs7EMAerI7Ow=" 361 | }, 362 | "brace-expansion": { 363 | "version": "1.1.11", 364 | "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", 365 | "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", 366 | "dev": true, 367 | "requires": { 368 | "balanced-match": "^1.0.0", 369 | "concat-map": "0.0.1" 370 | } 371 | }, 372 | "braces": { 373 | "version": "3.0.3", 374 | "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", 375 | "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", 376 | "dev": true, 377 | "requires": { 378 | "fill-range": "^7.1.1" 379 | }, 380 | "dependencies": { 381 | "fill-range": { 382 | "version": "7.1.1", 383 | "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", 384 | "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", 385 | "dev": true, 386 | "requires": { 387 | "to-regex-range": "^5.0.1" 388 | } 389 | } 390 | } 391 | }, 392 | "browserslist": { 393 | "version": "4.23.3", 394 | "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.23.3.tgz", 395 | "integrity": "sha512-btwCFJVjI4YWDNfau8RhZ+B1Q/VLoUITrm3RlP6y1tYGWIOa+InuYiRGXUBXo8nA1qKmHMyLB/iVQg5TT4eFoA==", 396 | "dev": true, 397 | "requires": { 398 | "caniuse-lite": "^1.0.30001646", 399 | "electron-to-chromium": "^1.5.4", 400 | "node-releases": "^2.0.18", 401 | "update-browserslist-db": "^1.1.0" 402 | } 403 | }, 404 | "buffer-from": { 405 | "version": "1.1.1", 406 | "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", 407 | "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==", 408 | "dev": true 409 | }, 410 | "caniuse-lite": { 411 | "version": "1.0.30001655", 412 | "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001655.tgz", 413 | "integrity": "sha512-jRGVy3iSGO5Uutn2owlb5gR6qsGngTw9ZTb4ali9f3glshcNmJ2noam4Mo9zia5P9Dk3jNNydy7vQjuE5dQmfg==", 414 | "dev": true 415 | }, 416 | "chalk": { 417 | "version": "4.1.2", 418 | "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", 419 | "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", 420 | "dev": true, 421 | "requires": { 422 | "ansi-styles": "^4.1.0", 423 | "supports-color": "^7.1.0" 424 | }, 425 | "dependencies": { 426 | "supports-color": { 427 | "version": "7.2.0", 428 | "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", 429 | "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", 430 | "dev": true, 431 | "requires": { 432 | "has-flag": "^4.0.0" 433 | } 434 | } 435 | } 436 | }, 437 | "chrome-trace-event": { 438 | "version": "1.0.4", 439 | "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.4.tgz", 440 | "integrity": "sha512-rNjApaLzuwaOTjCiT8lSDdGN1APCiqkChLMJxJPWLunPAt5fy8xgU9/jNOchV84wfIxrA0lRQB7oCT8jrn/wrQ==", 441 | "dev": true 442 | }, 443 | "clone-deep": { 444 | "version": "4.0.1", 445 | "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-4.0.1.tgz", 446 | "integrity": "sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ==", 447 | "dev": true, 448 | "requires": { 449 | "is-plain-object": "^2.0.4", 450 | "kind-of": "^6.0.2", 451 | "shallow-clone": "^3.0.0" 452 | } 453 | }, 454 | "color-convert": { 455 | "version": "2.0.1", 456 | "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", 457 | "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", 458 | "dev": true, 459 | "requires": { 460 | "color-name": "~1.1.4" 461 | } 462 | }, 463 | "color-name": { 464 | "version": "1.1.4", 465 | "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", 466 | "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", 467 | "dev": true 468 | }, 469 | "colorette": { 470 | "version": "2.0.19", 471 | "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.19.tgz", 472 | "integrity": "sha512-3tlv/dIP7FWvj3BsbHrGLJ6l/oKh1O3TcgBqMn+yyCagOxc23fyzDS6HypQbgxWbkpDnf52p1LuR4eWDQ/K9WQ==", 473 | "dev": true 474 | }, 475 | "commander": { 476 | "version": "2.15.1", 477 | "resolved": "https://registry.npmjs.org/commander/-/commander-2.15.1.tgz", 478 | "integrity": "sha512-VlfT9F3V0v+jr4yxPc5gg9s62/fIVWsd2Bk2iD435um1NlGMYdVCq+MjcXnhYq2icNOizHr1kK+5TI6H0Hy0ag==", 479 | "dev": true 480 | }, 481 | "concat-map": { 482 | "version": "0.0.1", 483 | "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", 484 | "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", 485 | "dev": true 486 | }, 487 | "core-util-is": { 488 | "version": "1.0.3", 489 | "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", 490 | "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==" 491 | }, 492 | "cross-spawn": { 493 | "version": "6.0.6", 494 | "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.6.tgz", 495 | "integrity": "sha512-VqCUuhcd1iB+dsv8gxPttb5iZh/D0iubSP21g36KXdEuf6I5JiioesUVjpCdHV9MZRUfVFlvwtIUyPfxo5trtw==", 496 | "requires": { 497 | "nice-try": "^1.0.4", 498 | "path-key": "^2.0.1", 499 | "semver": "^5.5.0", 500 | "shebang-command": "^1.2.0", 501 | "which": "^1.2.9" 502 | } 503 | }, 504 | "debug": { 505 | "version": "3.1.0", 506 | "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", 507 | "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", 508 | "dev": true, 509 | "requires": { 510 | "ms": "2.0.0" 511 | } 512 | }, 513 | "diff": { 514 | "version": "3.5.0", 515 | "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz", 516 | "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==", 517 | "dev": true 518 | }, 519 | "electron-to-chromium": { 520 | "version": "1.5.13", 521 | "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.13.tgz", 522 | "integrity": "sha512-lbBcvtIJ4J6sS4tb5TLp1b4LyfCdMkwStzXPyAgVgTRAsep4bvrAGaBOP7ZJtQMNJpSQ9SqG4brWOroNaQtm7Q==", 523 | "dev": true 524 | }, 525 | "enhanced-resolve": { 526 | "version": "5.12.0", 527 | "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.12.0.tgz", 528 | "integrity": "sha512-QHTXI/sZQmko1cbDoNAa3mJ5qhWUUNAq3vR0/YiD379fWQrcfuoX1+HW2S0MTt7XmoPLapdaDKUtelUSPic7hQ==", 529 | "dev": true, 530 | "requires": { 531 | "graceful-fs": "^4.2.4", 532 | "tapable": "^2.2.0" 533 | } 534 | }, 535 | "envinfo": { 536 | "version": "7.8.1", 537 | "resolved": "https://registry.npmjs.org/envinfo/-/envinfo-7.8.1.tgz", 538 | "integrity": "sha512-/o+BXHmB7ocbHEAs6F2EnG0ogybVVUdkRunTT2glZU9XAaGmhqskrvKwqXuDfNjEO0LZKWdejEEpnq8aM0tOaw==", 539 | "dev": true 540 | }, 541 | "es-module-lexer": { 542 | "version": "1.5.4", 543 | "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.5.4.tgz", 544 | "integrity": "sha512-MVNK56NiMrOwitFB7cqDwq0CQutbw+0BvLshJSse0MUNU+y1FC3bUS/AQg7oUng+/wKrrki7JfmwtVHkVfPLlw==", 545 | "dev": true 546 | }, 547 | "es6-promise": { 548 | "version": "4.2.8", 549 | "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.8.tgz", 550 | "integrity": "sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w==", 551 | "dev": true 552 | }, 553 | "es6-promisify": { 554 | "version": "5.0.0", 555 | "resolved": "https://registry.npmjs.org/es6-promisify/-/es6-promisify-5.0.0.tgz", 556 | "integrity": "sha1-UQnWLz5W6pZ8S2NQWu8IKRyKUgM=", 557 | "dev": true, 558 | "requires": { 559 | "es6-promise": "^4.0.3" 560 | } 561 | }, 562 | "escalade": { 563 | "version": "3.2.0", 564 | "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", 565 | "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", 566 | "dev": true 567 | }, 568 | "escape-string-regexp": { 569 | "version": "1.0.5", 570 | "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", 571 | "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", 572 | "dev": true 573 | }, 574 | "eslint-scope": { 575 | "version": "5.1.1", 576 | "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", 577 | "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", 578 | "dev": true, 579 | "requires": { 580 | "esrecurse": "^4.3.0", 581 | "estraverse": "^4.1.1" 582 | } 583 | }, 584 | "esrecurse": { 585 | "version": "4.3.0", 586 | "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", 587 | "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", 588 | "dev": true, 589 | "requires": { 590 | "estraverse": "^5.2.0" 591 | }, 592 | "dependencies": { 593 | "estraverse": { 594 | "version": "5.3.0", 595 | "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", 596 | "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", 597 | "dev": true 598 | } 599 | } 600 | }, 601 | "estraverse": { 602 | "version": "4.3.0", 603 | "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", 604 | "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", 605 | "dev": true 606 | }, 607 | "events": { 608 | "version": "3.3.0", 609 | "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", 610 | "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", 611 | "dev": true 612 | }, 613 | "fast-deep-equal": { 614 | "version": "3.1.3", 615 | "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", 616 | "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", 617 | "dev": true 618 | }, 619 | "fast-json-stable-stringify": { 620 | "version": "2.1.0", 621 | "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", 622 | "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", 623 | "dev": true 624 | }, 625 | "fastest-levenshtein": { 626 | "version": "1.0.16", 627 | "resolved": "https://registry.npmjs.org/fastest-levenshtein/-/fastest-levenshtein-1.0.16.tgz", 628 | "integrity": "sha512-eRnCtTTtGZFpQCwhJiUOuxPQWRXVKYDn0b2PeHfXL6/Zi53SLAzAHfVhVWK2AryC/WH05kGfxhFIPvTF0SXQzg==", 629 | "dev": true 630 | }, 631 | "find-up": { 632 | "version": "4.1.0", 633 | "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", 634 | "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", 635 | "dev": true, 636 | "requires": { 637 | "locate-path": "^5.0.0", 638 | "path-exists": "^4.0.0" 639 | } 640 | }, 641 | "fs.realpath": { 642 | "version": "1.0.0", 643 | "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", 644 | "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", 645 | "dev": true 646 | }, 647 | "function-bind": { 648 | "version": "1.1.1", 649 | "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", 650 | "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", 651 | "dev": true 652 | }, 653 | "glob": { 654 | "version": "7.1.2", 655 | "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", 656 | "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==", 657 | "dev": true, 658 | "requires": { 659 | "fs.realpath": "^1.0.0", 660 | "inflight": "^1.0.4", 661 | "inherits": "2", 662 | "minimatch": "^3.0.4", 663 | "once": "^1.3.0", 664 | "path-is-absolute": "^1.0.0" 665 | } 666 | }, 667 | "glob-to-regexp": { 668 | "version": "0.4.1", 669 | "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz", 670 | "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==", 671 | "dev": true 672 | }, 673 | "graceful-fs": { 674 | "version": "4.2.10", 675 | "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz", 676 | "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==", 677 | "dev": true 678 | }, 679 | "growl": { 680 | "version": "1.10.5", 681 | "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.5.tgz", 682 | "integrity": "sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==", 683 | "dev": true 684 | }, 685 | "has": { 686 | "version": "1.0.3", 687 | "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", 688 | "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", 689 | "dev": true, 690 | "requires": { 691 | "function-bind": "^1.1.1" 692 | } 693 | }, 694 | "has-flag": { 695 | "version": "4.0.0", 696 | "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", 697 | "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", 698 | "dev": true 699 | }, 700 | "he": { 701 | "version": "1.1.1", 702 | "resolved": "https://registry.npmjs.org/he/-/he-1.1.1.tgz", 703 | "integrity": "sha1-k0EP0hsAlzUVH4howvJx80J+I/0=", 704 | "dev": true 705 | }, 706 | "http-proxy-agent": { 707 | "version": "4.0.1", 708 | "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-4.0.1.tgz", 709 | "integrity": "sha512-k0zdNgqWTGA6aeIRVpvfVob4fL52dTfaehylg0Y4UvSySvOq/Y+BOyPrgpUrA7HylqvU8vIZGsRuXmspskV0Tg==", 710 | "dev": true, 711 | "requires": { 712 | "@tootallnate/once": "1", 713 | "agent-base": "6", 714 | "debug": "4" 715 | }, 716 | "dependencies": { 717 | "debug": { 718 | "version": "4.1.1", 719 | "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", 720 | "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", 721 | "dev": true, 722 | "requires": { 723 | "ms": "^2.1.1" 724 | } 725 | }, 726 | "ms": { 727 | "version": "2.1.2", 728 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", 729 | "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", 730 | "dev": true 731 | } 732 | } 733 | }, 734 | "https-proxy-agent": { 735 | "version": "5.0.0", 736 | "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.0.tgz", 737 | "integrity": "sha512-EkYm5BcKUGiduxzSt3Eppko+PiNWNEpa4ySk9vTC6wDsQJW9rHSa+UhGNJoRYp7bz6Ht1eaRIa6QaJqO5rCFbA==", 738 | "dev": true, 739 | "requires": { 740 | "agent-base": "6", 741 | "debug": "4" 742 | }, 743 | "dependencies": { 744 | "debug": { 745 | "version": "4.1.1", 746 | "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", 747 | "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", 748 | "dev": true, 749 | "requires": { 750 | "ms": "^2.1.1" 751 | } 752 | }, 753 | "ms": { 754 | "version": "2.1.2", 755 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", 756 | "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", 757 | "dev": true 758 | } 759 | } 760 | }, 761 | "immediate": { 762 | "version": "3.0.6", 763 | "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz", 764 | "integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==" 765 | }, 766 | "import-local": { 767 | "version": "3.1.0", 768 | "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.1.0.tgz", 769 | "integrity": "sha512-ASB07uLtnDs1o6EHjKpX34BKYDSqnFerfTOJL2HvMqF70LnxpjkzDB8J44oT9pu4AMPkQwf8jl6szgvNd2tRIg==", 770 | "dev": true, 771 | "requires": { 772 | "pkg-dir": "^4.2.0", 773 | "resolve-cwd": "^3.0.0" 774 | } 775 | }, 776 | "inflight": { 777 | "version": "1.0.6", 778 | "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", 779 | "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", 780 | "dev": true, 781 | "requires": { 782 | "once": "^1.3.0", 783 | "wrappy": "1" 784 | } 785 | }, 786 | "inherits": { 787 | "version": "2.0.3", 788 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", 789 | "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" 790 | }, 791 | "interpret": { 792 | "version": "3.1.1", 793 | "resolved": "https://registry.npmjs.org/interpret/-/interpret-3.1.1.tgz", 794 | "integrity": "sha512-6xwYfHbajpoF0xLW+iwLkhwgvLoZDfjYfoFNu8ftMoXINzwuymNLd9u/KmwtdT2GbR+/Cz66otEGEVVUHX9QLQ==", 795 | "dev": true 796 | }, 797 | "is-core-module": { 798 | "version": "2.11.0", 799 | "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.11.0.tgz", 800 | "integrity": "sha512-RRjxlvLDkD1YJwDbroBHMb+cukurkDWNyHx7D3oNB5x9rb5ogcksMC5wHCadcXoo67gVr/+3GFySh3134zi6rw==", 801 | "dev": true, 802 | "requires": { 803 | "has": "^1.0.3" 804 | } 805 | }, 806 | "is-number": { 807 | "version": "7.0.0", 808 | "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", 809 | "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", 810 | "dev": true 811 | }, 812 | "is-plain-object": { 813 | "version": "2.0.4", 814 | "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", 815 | "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", 816 | "dev": true, 817 | "requires": { 818 | "isobject": "^3.0.1" 819 | } 820 | }, 821 | "isarray": { 822 | "version": "1.0.0", 823 | "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", 824 | "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==" 825 | }, 826 | "isexe": { 827 | "version": "2.0.0", 828 | "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", 829 | "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=" 830 | }, 831 | "isobject": { 832 | "version": "3.0.1", 833 | "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", 834 | "integrity": "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==", 835 | "dev": true 836 | }, 837 | "jest-worker": { 838 | "version": "27.5.1", 839 | "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz", 840 | "integrity": "sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==", 841 | "dev": true, 842 | "requires": { 843 | "@types/node": "*", 844 | "merge-stream": "^2.0.0", 845 | "supports-color": "^8.0.0" 846 | }, 847 | "dependencies": { 848 | "supports-color": { 849 | "version": "8.1.1", 850 | "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", 851 | "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", 852 | "dev": true, 853 | "requires": { 854 | "has-flag": "^4.0.0" 855 | } 856 | } 857 | } 858 | }, 859 | "json-parse-even-better-errors": { 860 | "version": "2.3.1", 861 | "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", 862 | "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", 863 | "dev": true 864 | }, 865 | "json-schema-traverse": { 866 | "version": "0.4.1", 867 | "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", 868 | "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", 869 | "dev": true 870 | }, 871 | "jszip": { 872 | "version": "3.8.0", 873 | "resolved": "https://registry.npmjs.org/jszip/-/jszip-3.8.0.tgz", 874 | "integrity": "sha512-cnpQrXvFSLdsR9KR5/x7zdf6c3m8IhZfZzSblFEHSqBaVwD2nvJ4CuCKLyvKvwBgZm08CgfSoiTBQLm5WW9hGw==", 875 | "requires": { 876 | "lie": "~3.3.0", 877 | "pako": "~1.0.2", 878 | "readable-stream": "~2.3.6", 879 | "set-immediate-shim": "~1.0.1" 880 | } 881 | }, 882 | "kind-of": { 883 | "version": "6.0.3", 884 | "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", 885 | "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", 886 | "dev": true 887 | }, 888 | "lie": { 889 | "version": "3.3.0", 890 | "resolved": "https://registry.npmjs.org/lie/-/lie-3.3.0.tgz", 891 | "integrity": "sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==", 892 | "requires": { 893 | "immediate": "~3.0.5" 894 | } 895 | }, 896 | "loader-runner": { 897 | "version": "4.3.0", 898 | "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.0.tgz", 899 | "integrity": "sha512-3R/1M+yS3j5ou80Me59j7F9IMs4PXs3VqRrm0TU3AbKPxlmpoY1TNscJV/oGJXo8qCatFGTfDbY6W6ipGOYXfg==", 900 | "dev": true 901 | }, 902 | "locate-path": { 903 | "version": "5.0.0", 904 | "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", 905 | "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", 906 | "dev": true, 907 | "requires": { 908 | "p-locate": "^4.1.0" 909 | } 910 | }, 911 | "lru-cache": { 912 | "version": "6.0.0", 913 | "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", 914 | "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", 915 | "dev": true, 916 | "requires": { 917 | "yallist": "^4.0.0" 918 | } 919 | }, 920 | "merge-stream": { 921 | "version": "2.0.0", 922 | "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", 923 | "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", 924 | "dev": true 925 | }, 926 | "micromatch": { 927 | "version": "4.0.5", 928 | "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", 929 | "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", 930 | "dev": true, 931 | "requires": { 932 | "braces": "^3.0.2", 933 | "picomatch": "^2.3.1" 934 | } 935 | }, 936 | "mime-db": { 937 | "version": "1.52.0", 938 | "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", 939 | "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", 940 | "dev": true 941 | }, 942 | "mime-types": { 943 | "version": "2.1.35", 944 | "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", 945 | "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", 946 | "dev": true, 947 | "requires": { 948 | "mime-db": "1.52.0" 949 | } 950 | }, 951 | "minimatch": { 952 | "version": "3.0.4", 953 | "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", 954 | "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", 955 | "dev": true, 956 | "requires": { 957 | "brace-expansion": "^1.1.7" 958 | } 959 | }, 960 | "minimist": { 961 | "version": "1.2.8", 962 | "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", 963 | "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", 964 | "dev": true 965 | }, 966 | "mkdirp": { 967 | "version": "0.5.1", 968 | "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", 969 | "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", 970 | "dev": true, 971 | "requires": { 972 | "minimist": "0.0.8" 973 | }, 974 | "dependencies": { 975 | "minimist": { 976 | "version": "0.0.8", 977 | "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", 978 | "integrity": "sha512-miQKw5Hv4NS1Psg2517mV4e4dYNaO3++hjAvLOAzKqZ61rH8NS1SK+vbfBWZ5PY/Me/bEWhUwqMghEW5Fb9T7Q==", 979 | "dev": true 980 | } 981 | } 982 | }, 983 | "mocha": { 984 | "version": "5.2.0", 985 | "resolved": "https://registry.npmjs.org/mocha/-/mocha-5.2.0.tgz", 986 | "integrity": "sha512-2IUgKDhc3J7Uug+FxMXuqIyYzH7gJjXECKe/w43IGgQHTSj3InJi+yAA7T24L9bQMRKiUEHxEX37G5JpVUGLcQ==", 987 | "dev": true, 988 | "requires": { 989 | "browser-stdout": "1.3.1", 990 | "commander": "2.15.1", 991 | "debug": "3.1.0", 992 | "diff": "3.5.0", 993 | "escape-string-regexp": "1.0.5", 994 | "glob": "7.1.2", 995 | "growl": "1.10.5", 996 | "he": "1.1.1", 997 | "minimatch": "3.0.4", 998 | "mkdirp": "0.5.1", 999 | "supports-color": "5.4.0" 1000 | }, 1001 | "dependencies": { 1002 | "browser-stdout": { 1003 | "version": "1.3.1", 1004 | "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", 1005 | "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", 1006 | "dev": true 1007 | } 1008 | } 1009 | }, 1010 | "ms": { 1011 | "version": "2.0.0", 1012 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", 1013 | "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", 1014 | "dev": true 1015 | }, 1016 | "neo-async": { 1017 | "version": "2.6.2", 1018 | "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", 1019 | "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", 1020 | "dev": true 1021 | }, 1022 | "nice-try": { 1023 | "version": "1.0.5", 1024 | "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", 1025 | "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==" 1026 | }, 1027 | "node-releases": { 1028 | "version": "2.0.18", 1029 | "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.18.tgz", 1030 | "integrity": "sha512-d9VeXT4SJ7ZeOqGX6R5EM022wpL+eWPooLI+5UpWn2jCT1aosUQEhQP214x33Wkwx3JQMvIm+tIoVOdodFS40g==", 1031 | "dev": true 1032 | }, 1033 | "once": { 1034 | "version": "1.4.0", 1035 | "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", 1036 | "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", 1037 | "dev": true, 1038 | "requires": { 1039 | "wrappy": "1" 1040 | } 1041 | }, 1042 | "p-limit": { 1043 | "version": "2.3.0", 1044 | "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", 1045 | "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", 1046 | "dev": true, 1047 | "requires": { 1048 | "p-try": "^2.0.0" 1049 | } 1050 | }, 1051 | "p-locate": { 1052 | "version": "4.1.0", 1053 | "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", 1054 | "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", 1055 | "dev": true, 1056 | "requires": { 1057 | "p-limit": "^2.2.0" 1058 | } 1059 | }, 1060 | "p-try": { 1061 | "version": "2.2.0", 1062 | "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", 1063 | "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", 1064 | "dev": true 1065 | }, 1066 | "pako": { 1067 | "version": "1.0.11", 1068 | "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", 1069 | "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==" 1070 | }, 1071 | "path-exists": { 1072 | "version": "4.0.0", 1073 | "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", 1074 | "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", 1075 | "dev": true 1076 | }, 1077 | "path-is-absolute": { 1078 | "version": "1.0.1", 1079 | "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", 1080 | "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", 1081 | "dev": true 1082 | }, 1083 | "path-key": { 1084 | "version": "2.0.1", 1085 | "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", 1086 | "integrity": "sha512-fEHGKCSmUSDPv4uoj8AlD+joPlq3peND+HRYyxFz4KPw4z926S/b8rIuFs2FYJg3BwsxJf6A9/3eIdLaYC+9Dw==" 1087 | }, 1088 | "path-parse": { 1089 | "version": "1.0.7", 1090 | "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", 1091 | "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", 1092 | "dev": true 1093 | }, 1094 | "picocolors": { 1095 | "version": "1.0.1", 1096 | "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.1.tgz", 1097 | "integrity": "sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew==", 1098 | "dev": true 1099 | }, 1100 | "picomatch": { 1101 | "version": "2.3.1", 1102 | "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", 1103 | "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", 1104 | "dev": true 1105 | }, 1106 | "pkg-dir": { 1107 | "version": "4.2.0", 1108 | "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", 1109 | "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", 1110 | "dev": true, 1111 | "requires": { 1112 | "find-up": "^4.0.0" 1113 | } 1114 | }, 1115 | "process-nextick-args": { 1116 | "version": "2.0.1", 1117 | "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", 1118 | "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" 1119 | }, 1120 | "punycode": { 1121 | "version": "2.3.1", 1122 | "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", 1123 | "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", 1124 | "dev": true 1125 | }, 1126 | "randombytes": { 1127 | "version": "2.1.0", 1128 | "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", 1129 | "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", 1130 | "dev": true, 1131 | "requires": { 1132 | "safe-buffer": "^5.1.0" 1133 | } 1134 | }, 1135 | "readable-stream": { 1136 | "version": "2.3.7", 1137 | "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", 1138 | "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", 1139 | "requires": { 1140 | "core-util-is": "~1.0.0", 1141 | "inherits": "~2.0.3", 1142 | "isarray": "~1.0.0", 1143 | "process-nextick-args": "~2.0.0", 1144 | "safe-buffer": "~5.1.1", 1145 | "string_decoder": "~1.1.1", 1146 | "util-deprecate": "~1.0.1" 1147 | } 1148 | }, 1149 | "rechoir": { 1150 | "version": "0.8.0", 1151 | "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.8.0.tgz", 1152 | "integrity": "sha512-/vxpCXddiX8NGfGO/mTafwjq4aFa/71pvamip0++IQk3zG8cbCj0fifNPrjjF1XMXUne91jL9OoxmdykoEtifQ==", 1153 | "dev": true, 1154 | "requires": { 1155 | "resolve": "^1.20.0" 1156 | } 1157 | }, 1158 | "resolve": { 1159 | "version": "1.22.1", 1160 | "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz", 1161 | "integrity": "sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==", 1162 | "dev": true, 1163 | "requires": { 1164 | "is-core-module": "^2.9.0", 1165 | "path-parse": "^1.0.7", 1166 | "supports-preserve-symlinks-flag": "^1.0.0" 1167 | } 1168 | }, 1169 | "resolve-cwd": { 1170 | "version": "3.0.0", 1171 | "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", 1172 | "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==", 1173 | "dev": true, 1174 | "requires": { 1175 | "resolve-from": "^5.0.0" 1176 | } 1177 | }, 1178 | "resolve-from": { 1179 | "version": "5.0.0", 1180 | "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", 1181 | "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", 1182 | "dev": true 1183 | }, 1184 | "safe-buffer": { 1185 | "version": "5.1.2", 1186 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", 1187 | "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" 1188 | }, 1189 | "schema-utils": { 1190 | "version": "3.3.0", 1191 | "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz", 1192 | "integrity": "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==", 1193 | "dev": true, 1194 | "requires": { 1195 | "@types/json-schema": "^7.0.8", 1196 | "ajv": "^6.12.5", 1197 | "ajv-keywords": "^3.5.2" 1198 | } 1199 | }, 1200 | "semver": { 1201 | "version": "5.7.2", 1202 | "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", 1203 | "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==" 1204 | }, 1205 | "serialize-javascript": { 1206 | "version": "6.0.2", 1207 | "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz", 1208 | "integrity": "sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==", 1209 | "dev": true, 1210 | "requires": { 1211 | "randombytes": "^2.1.0" 1212 | } 1213 | }, 1214 | "set-immediate-shim": { 1215 | "version": "1.0.1", 1216 | "resolved": "https://registry.npmjs.org/set-immediate-shim/-/set-immediate-shim-1.0.1.tgz", 1217 | "integrity": "sha512-Li5AOqrZWCVA2n5kryzEmqai6bKSIvpz5oUJHPVj6+dsbD3X1ixtsY5tEnsaNpH3pFAHmG8eIHUrtEtohrg+UQ==" 1218 | }, 1219 | "shallow-clone": { 1220 | "version": "3.0.1", 1221 | "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-3.0.1.tgz", 1222 | "integrity": "sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA==", 1223 | "dev": true, 1224 | "requires": { 1225 | "kind-of": "^6.0.2" 1226 | } 1227 | }, 1228 | "shebang-command": { 1229 | "version": "1.2.0", 1230 | "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", 1231 | "integrity": "sha512-EV3L1+UQWGor21OmnvojK36mhg+TyIKDh3iFBKBohr5xeXIhNBcx8oWdgkTEEQ+BEFFYdLRuqMfd5L84N1V5Vg==", 1232 | "requires": { 1233 | "shebang-regex": "^1.0.0" 1234 | } 1235 | }, 1236 | "shebang-regex": { 1237 | "version": "1.0.0", 1238 | "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", 1239 | "integrity": "sha512-wpoSFAxys6b2a2wHZ1XpDSgD7N9iVjg29Ph9uV/uaP9Ex/KXlkTZTeddxDPSYQpgvzKLGJke2UU0AzoGCjNIvQ==" 1240 | }, 1241 | "source-map": { 1242 | "version": "0.6.1", 1243 | "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", 1244 | "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", 1245 | "dev": true 1246 | }, 1247 | "source-map-support": { 1248 | "version": "0.5.19", 1249 | "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.19.tgz", 1250 | "integrity": "sha512-Wonm7zOCIJzBGQdB+thsPar0kYuCIzYvxZwlBa87yi/Mdjv7Tip2cyVbLj5o0cFPN4EVkuTwb3GDDyUx2DGnGw==", 1251 | "dev": true, 1252 | "requires": { 1253 | "buffer-from": "^1.0.0", 1254 | "source-map": "^0.6.0" 1255 | } 1256 | }, 1257 | "string_decoder": { 1258 | "version": "1.1.1", 1259 | "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", 1260 | "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", 1261 | "requires": { 1262 | "safe-buffer": "~5.1.0" 1263 | } 1264 | }, 1265 | "supports-color": { 1266 | "version": "5.4.0", 1267 | "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.4.0.tgz", 1268 | "integrity": "sha512-zjaXglF5nnWpsq470jSv6P9DwPvgLkuapYmfDm3JWOm0vkNTVF2tI4UrN2r6jH1qM/uc/WtxYY1hYoA2dOKj5w==", 1269 | "dev": true, 1270 | "requires": { 1271 | "has-flag": "^3.0.0" 1272 | }, 1273 | "dependencies": { 1274 | "has-flag": { 1275 | "version": "3.0.0", 1276 | "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", 1277 | "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", 1278 | "dev": true 1279 | } 1280 | } 1281 | }, 1282 | "supports-preserve-symlinks-flag": { 1283 | "version": "1.0.0", 1284 | "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", 1285 | "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", 1286 | "dev": true 1287 | }, 1288 | "tapable": { 1289 | "version": "2.2.1", 1290 | "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", 1291 | "integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==", 1292 | "dev": true 1293 | }, 1294 | "terser": { 1295 | "version": "5.31.6", 1296 | "resolved": "https://registry.npmjs.org/terser/-/terser-5.31.6.tgz", 1297 | "integrity": "sha512-PQ4DAriWzKj+qgehQ7LK5bQqCFNMmlhjR2PFFLuqGCpuCAauxemVBWwWOxo3UIwWQx8+Pr61Df++r76wDmkQBg==", 1298 | "dev": true, 1299 | "requires": { 1300 | "@jridgewell/source-map": "^0.3.3", 1301 | "acorn": "^8.8.2", 1302 | "commander": "^2.20.0", 1303 | "source-map-support": "~0.5.20" 1304 | }, 1305 | "dependencies": { 1306 | "commander": { 1307 | "version": "2.20.3", 1308 | "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", 1309 | "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", 1310 | "dev": true 1311 | }, 1312 | "source-map-support": { 1313 | "version": "0.5.21", 1314 | "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", 1315 | "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", 1316 | "dev": true, 1317 | "requires": { 1318 | "buffer-from": "^1.0.0", 1319 | "source-map": "^0.6.0" 1320 | } 1321 | } 1322 | } 1323 | }, 1324 | "terser-webpack-plugin": { 1325 | "version": "5.3.10", 1326 | "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.10.tgz", 1327 | "integrity": "sha512-BKFPWlPDndPs+NGGCr1U59t0XScL5317Y0UReNrHaw9/FwhPENlq6bfgs+4yPfyP51vqC1bQ4rp1EfXW5ZSH9w==", 1328 | "dev": true, 1329 | "requires": { 1330 | "@jridgewell/trace-mapping": "^0.3.20", 1331 | "jest-worker": "^27.4.5", 1332 | "schema-utils": "^3.1.1", 1333 | "serialize-javascript": "^6.0.1", 1334 | "terser": "^5.26.0" 1335 | } 1336 | }, 1337 | "to-regex-range": { 1338 | "version": "5.0.1", 1339 | "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", 1340 | "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", 1341 | "dev": true, 1342 | "requires": { 1343 | "is-number": "^7.0.0" 1344 | } 1345 | }, 1346 | "ts-loader": { 1347 | "version": "9.4.2", 1348 | "resolved": "https://registry.npmjs.org/ts-loader/-/ts-loader-9.4.2.tgz", 1349 | "integrity": "sha512-OmlC4WVmFv5I0PpaxYb+qGeGOdm5giHU7HwDDUjw59emP2UYMHy9fFSDcYgSNoH8sXcj4hGCSEhlDZ9ULeDraA==", 1350 | "dev": true, 1351 | "requires": { 1352 | "chalk": "^4.1.0", 1353 | "enhanced-resolve": "^5.0.0", 1354 | "micromatch": "^4.0.0", 1355 | "semver": "^7.3.4" 1356 | }, 1357 | "dependencies": { 1358 | "semver": { 1359 | "version": "7.5.4", 1360 | "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", 1361 | "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", 1362 | "dev": true, 1363 | "requires": { 1364 | "lru-cache": "^6.0.0" 1365 | } 1366 | } 1367 | } 1368 | }, 1369 | "typescript": { 1370 | "version": "3.9.2", 1371 | "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.9.2.tgz", 1372 | "integrity": "sha512-q2ktq4n/uLuNNShyayit+DTobV2ApPEo/6so68JaD5ojvc/6GClBipedB9zNWYxRSAlZXAe405Rlijzl6qDiSw==", 1373 | "dev": true 1374 | }, 1375 | "update-browserslist-db": { 1376 | "version": "1.1.0", 1377 | "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.0.tgz", 1378 | "integrity": "sha512-EdRAaAyk2cUE1wOf2DkEhzxqOQvFOoRJFNS6NeyJ01Gp2beMRpBAINjM2iDXE3KCuKhwnvHIQCJm6ThL2Z+HzQ==", 1379 | "dev": true, 1380 | "requires": { 1381 | "escalade": "^3.1.2", 1382 | "picocolors": "^1.0.1" 1383 | } 1384 | }, 1385 | "uri-js": { 1386 | "version": "4.4.1", 1387 | "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", 1388 | "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", 1389 | "dev": true, 1390 | "requires": { 1391 | "punycode": "^2.1.0" 1392 | } 1393 | }, 1394 | "util-deprecate": { 1395 | "version": "1.0.2", 1396 | "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", 1397 | "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" 1398 | }, 1399 | "vscode": { 1400 | "version": "1.1.37", 1401 | "resolved": "https://registry.npmjs.org/vscode/-/vscode-1.1.37.tgz", 1402 | "integrity": "sha512-vJNj6IlN7IJPdMavlQa1KoFB3Ihn06q1AiN3ZFI/HfzPNzbKZWPPuiU+XkpNOfGU5k15m4r80nxNPlM7wcc0wg==", 1403 | "dev": true, 1404 | "requires": { 1405 | "glob": "^7.1.2", 1406 | "http-proxy-agent": "^4.0.1", 1407 | "https-proxy-agent": "^5.0.0", 1408 | "mocha": "^5.2.0", 1409 | "semver": "^5.4.1", 1410 | "source-map-support": "^0.5.0", 1411 | "vscode-test": "^0.4.1" 1412 | } 1413 | }, 1414 | "vscode-test": { 1415 | "version": "0.4.3", 1416 | "resolved": "https://registry.npmjs.org/vscode-test/-/vscode-test-0.4.3.tgz", 1417 | "integrity": "sha512-EkMGqBSefZH2MgW65nY05rdRSko15uvzq4VAPM5jVmwYuFQKE7eikKXNJDRxL+OITXHB6pI+a3XqqD32Y3KC5w==", 1418 | "dev": true, 1419 | "requires": { 1420 | "http-proxy-agent": "^2.1.0", 1421 | "https-proxy-agent": "^2.2.1" 1422 | }, 1423 | "dependencies": { 1424 | "agent-base": { 1425 | "version": "4.3.0", 1426 | "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-4.3.0.tgz", 1427 | "integrity": "sha512-salcGninV0nPrwpGNn4VTXBb1SOuXQBiqbrNXoeizJsHrsL6ERFM2Ne3JUSBWRE6aeNJI2ROP/WEEIDUiDe3cg==", 1428 | "dev": true, 1429 | "requires": { 1430 | "es6-promisify": "^5.0.0" 1431 | } 1432 | }, 1433 | "http-proxy-agent": { 1434 | "version": "2.1.0", 1435 | "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-2.1.0.tgz", 1436 | "integrity": "sha512-qwHbBLV7WviBl0rQsOzH6o5lwyOIvwp/BdFnvVxXORldu5TmjFfjzBcWUWS5kWAZhmv+JtiDhSuQCp4sBfbIgg==", 1437 | "dev": true, 1438 | "requires": { 1439 | "agent-base": "4", 1440 | "debug": "3.1.0" 1441 | } 1442 | }, 1443 | "https-proxy-agent": { 1444 | "version": "2.2.4", 1445 | "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-2.2.4.tgz", 1446 | "integrity": "sha512-OmvfoQ53WLjtA9HeYP9RNrWMJzzAz1JGaSFr1nijg0PVR1JaD/xbJq1mdEIIlxGpXp9eSe/O2LgU9DJmTPd0Eg==", 1447 | "dev": true, 1448 | "requires": { 1449 | "agent-base": "^4.3.0", 1450 | "debug": "^3.1.0" 1451 | } 1452 | } 1453 | } 1454 | }, 1455 | "watchpack": { 1456 | "version": "2.4.2", 1457 | "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.2.tgz", 1458 | "integrity": "sha512-TnbFSbcOCcDgjZ4piURLCbJ3nJhznVh9kw6F6iokjiFPl8ONxe9A6nMDVXDiNbrSfLILs6vB07F7wLBrwPYzJw==", 1459 | "dev": true, 1460 | "requires": { 1461 | "glob-to-regexp": "^0.4.1", 1462 | "graceful-fs": "^4.1.2" 1463 | } 1464 | }, 1465 | "webpack": { 1466 | "version": "5.94.0", 1467 | "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.94.0.tgz", 1468 | "integrity": "sha512-KcsGn50VT+06JH/iunZJedYGUJS5FGjow8wb9c0v5n1Om8O1g4L6LjtfxwlXIATopoQu+vOXXa7gYisWxCoPyg==", 1469 | "dev": true, 1470 | "requires": { 1471 | "@types/estree": "^1.0.5", 1472 | "@webassemblyjs/ast": "^1.12.1", 1473 | "@webassemblyjs/wasm-edit": "^1.12.1", 1474 | "@webassemblyjs/wasm-parser": "^1.12.1", 1475 | "acorn": "^8.7.1", 1476 | "acorn-import-attributes": "^1.9.5", 1477 | "browserslist": "^4.21.10", 1478 | "chrome-trace-event": "^1.0.2", 1479 | "enhanced-resolve": "^5.17.1", 1480 | "es-module-lexer": "^1.2.1", 1481 | "eslint-scope": "5.1.1", 1482 | "events": "^3.2.0", 1483 | "glob-to-regexp": "^0.4.1", 1484 | "graceful-fs": "^4.2.11", 1485 | "json-parse-even-better-errors": "^2.3.1", 1486 | "loader-runner": "^4.2.0", 1487 | "mime-types": "^2.1.27", 1488 | "neo-async": "^2.6.2", 1489 | "schema-utils": "^3.2.0", 1490 | "tapable": "^2.1.1", 1491 | "terser-webpack-plugin": "^5.3.10", 1492 | "watchpack": "^2.4.1", 1493 | "webpack-sources": "^3.2.3" 1494 | }, 1495 | "dependencies": { 1496 | "enhanced-resolve": { 1497 | "version": "5.17.1", 1498 | "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.17.1.tgz", 1499 | "integrity": "sha512-LMHl3dXhTcfv8gM4kEzIUeTQ+7fpdA0l2tUf34BddXPkz2A5xJ5L/Pchd5BL6rdccM9QGvu0sWZzK1Z1t4wwyg==", 1500 | "dev": true, 1501 | "requires": { 1502 | "graceful-fs": "^4.2.4", 1503 | "tapable": "^2.2.0" 1504 | } 1505 | }, 1506 | "graceful-fs": { 1507 | "version": "4.2.11", 1508 | "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", 1509 | "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", 1510 | "dev": true 1511 | } 1512 | } 1513 | }, 1514 | "webpack-cli": { 1515 | "version": "5.0.1", 1516 | "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-5.0.1.tgz", 1517 | "integrity": "sha512-S3KVAyfwUqr0Mo/ur3NzIp6jnerNpo7GUO6so51mxLi1spqsA17YcMXy0WOIJtBSnj748lthxC6XLbNKh/ZC+A==", 1518 | "dev": true, 1519 | "requires": { 1520 | "@discoveryjs/json-ext": "^0.5.0", 1521 | "@webpack-cli/configtest": "^2.0.1", 1522 | "@webpack-cli/info": "^2.0.1", 1523 | "@webpack-cli/serve": "^2.0.1", 1524 | "colorette": "^2.0.14", 1525 | "commander": "^9.4.1", 1526 | "cross-spawn": "^7.0.3", 1527 | "envinfo": "^7.7.3", 1528 | "fastest-levenshtein": "^1.0.12", 1529 | "import-local": "^3.0.2", 1530 | "interpret": "^3.1.1", 1531 | "rechoir": "^0.8.0", 1532 | "webpack-merge": "^5.7.3" 1533 | }, 1534 | "dependencies": { 1535 | "commander": { 1536 | "version": "9.4.1", 1537 | "resolved": "https://registry.npmjs.org/commander/-/commander-9.4.1.tgz", 1538 | "integrity": "sha512-5EEkTNyHNGFPD2H+c/dXXfQZYa/scCKasxWcXJaWnNJ99pnQN9Vnmqow+p+PlFPE63Q6mThaZws1T+HxfpgtPw==", 1539 | "dev": true 1540 | }, 1541 | "cross-spawn": { 1542 | "version": "7.0.3", 1543 | "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", 1544 | "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", 1545 | "dev": true, 1546 | "requires": { 1547 | "path-key": "^3.1.0", 1548 | "shebang-command": "^2.0.0", 1549 | "which": "^2.0.1" 1550 | } 1551 | }, 1552 | "path-key": { 1553 | "version": "3.1.1", 1554 | "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", 1555 | "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", 1556 | "dev": true 1557 | }, 1558 | "shebang-command": { 1559 | "version": "2.0.0", 1560 | "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", 1561 | "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", 1562 | "dev": true, 1563 | "requires": { 1564 | "shebang-regex": "^3.0.0" 1565 | } 1566 | }, 1567 | "shebang-regex": { 1568 | "version": "3.0.0", 1569 | "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", 1570 | "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", 1571 | "dev": true 1572 | }, 1573 | "which": { 1574 | "version": "2.0.2", 1575 | "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", 1576 | "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", 1577 | "dev": true, 1578 | "requires": { 1579 | "isexe": "^2.0.0" 1580 | } 1581 | } 1582 | } 1583 | }, 1584 | "webpack-merge": { 1585 | "version": "5.8.0", 1586 | "resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-5.8.0.tgz", 1587 | "integrity": "sha512-/SaI7xY0831XwP6kzuwhKWVKDP9t1QY1h65lAFLbZqMPIuYcD9QAW4u9STIbU9kaJbPBB/geU/gLr1wDjOhQ+Q==", 1588 | "dev": true, 1589 | "requires": { 1590 | "clone-deep": "^4.0.1", 1591 | "wildcard": "^2.0.0" 1592 | } 1593 | }, 1594 | "webpack-sources": { 1595 | "version": "3.2.3", 1596 | "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.2.3.tgz", 1597 | "integrity": "sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w==", 1598 | "dev": true 1599 | }, 1600 | "which": { 1601 | "version": "1.3.1", 1602 | "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", 1603 | "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", 1604 | "requires": { 1605 | "isexe": "^2.0.0" 1606 | } 1607 | }, 1608 | "wildcard": { 1609 | "version": "2.0.0", 1610 | "resolved": "https://registry.npmjs.org/wildcard/-/wildcard-2.0.0.tgz", 1611 | "integrity": "sha512-JcKqAHLPxcdb9KM49dufGXn2x3ssnfjbcaQdLlfZsL9rH9wgDQjUtDxbo8NE0F6SFvydeu1VhZe7hZuHsB2/pw==", 1612 | "dev": true 1613 | }, 1614 | "wrappy": { 1615 | "version": "1.0.2", 1616 | "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", 1617 | "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", 1618 | "dev": true 1619 | }, 1620 | "yallist": { 1621 | "version": "4.0.0", 1622 | "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", 1623 | "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", 1624 | "dev": true 1625 | } 1626 | } 1627 | } 1628 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "clojure", 3 | "displayName": "Clojure", 4 | "description": "Clojure nREPL support for Visual Studio Code", 5 | "version": "0.13.2", 6 | "publisher": "avli", 7 | "author": { 8 | "name": "Andrey Lisin", 9 | "email": "andrey.lisin@gmail.com" 10 | }, 11 | "contributors": [ 12 | { 13 | "name": "Thiago Almeida", 14 | "email": "fasfsfgs@gmail.com" 15 | }, 16 | { 17 | "name": "Mike Ball", 18 | "email": "michaelhball@gmail.com" 19 | }, 20 | { 21 | "name": "Egor Yurtaev", 22 | "email": "yurtaev.egor+github@gmail.com" 23 | }, 24 | { 25 | "name": "Mark Hansen", 26 | "email": "mark@markhansen.co.nz" 27 | }, 28 | { 29 | "name": "Fabian Achammer", 30 | "email": "fabian.achammer@gmail.com" 31 | }, 32 | { 33 | "name": "Nikita Prokopov", 34 | "email": "prokopov@gmail.com" 35 | }, 36 | { 37 | "name": "Frederik Ring", 38 | "email": "frederik.ring@gmail.com" 39 | }, 40 | { 41 | "name": "Alessandro Decina", 42 | "email": "alessandro.d@gmail.com" 43 | }, 44 | { 45 | "name": "Marc O'Morain", 46 | "email": "github.com@marcomorain.com" 47 | }, 48 | { 49 | "name": "Andrey Bogoyavlensky", 50 | "email": "abogoyavlensky@gmail.com" 51 | } 52 | ], 53 | "license": "MIT", 54 | "engines": { 55 | "vscode": "^1.14.0" 56 | }, 57 | "recommendations": [ 58 | "tonsky.clojure-warrior" 59 | ], 60 | "categories": [ 61 | "Programming Languages", 62 | "Other" 63 | ], 64 | "activationEvents": [ 65 | "onLanguage:clojure" 66 | ], 67 | "main": "./dist/extension", 68 | "contributes": { 69 | "commands": [ 70 | { 71 | "command": "clojureVSCode.eval", 72 | "title": "Clojure: Eval" 73 | }, 74 | { 75 | "command": "clojureVSCode.evalAndShowResult", 76 | "title": "Clojure: Eval and show the result" 77 | }, 78 | { 79 | "command": "clojureVSCode.testNamespace", 80 | "title": "Clojure: Test the current namespace" 81 | }, 82 | { 83 | "command": "clojureVSCode.runAllTests", 84 | "title": "Clojure: Run all tests" 85 | }, 86 | { 87 | "command": "clojureVSCode.manuallyConnectToNRepl", 88 | "title": "Clojure: Connect to a running nREPL" 89 | }, 90 | { 91 | "command": "clojureVSCode.startNRepl", 92 | "title": "Clojure: Start nREPL" 93 | }, 94 | { 95 | "command": "clojureVSCode.stopDisconnectNRepl", 96 | "title": "Clojure: Disconnect from nREPL" 97 | } 98 | ], 99 | "views": { 100 | "test": [ 101 | { 102 | "id": "clojure", 103 | "name": "Clojure" 104 | } 105 | ] 106 | }, 107 | "configuration": { 108 | "type": "object", 109 | "title": "Clojure extension configuration", 110 | "properties": { 111 | "clojureVSCode.autoStartNRepl": { 112 | "type": "boolean", 113 | "default": true, 114 | "description": "Automatically run an embedded nREPL instance and connect to it on Clojure file open." 115 | }, 116 | "clojureVSCode.formatOnSave": { 117 | "type": "boolean", 118 | "default": false, 119 | "description": "Format the code on save." 120 | }, 121 | "clojureVSCode.cljfmtParameters": { 122 | "type": "string", 123 | "description": "Parameters which will be passed to cljfmt.", 124 | "default": "" 125 | }, 126 | "clojureVSCode.showResultInline": { 127 | "type": "boolean", 128 | "default": true, 129 | "description": "Show evaluation result inline." 130 | }, 131 | "clojureVSCode.ciderNReplVersion": { 132 | "type": "string", 133 | "default": "0.24.0", 134 | "description": "Version of CIDER to use for the embedded nREPL." 135 | }, 136 | "clojureVSCode.cljfmtVersion": { 137 | "type": "string", 138 | "default": "0.6.7", 139 | "description": "Version of cljfmt to use for formatting Clojure files." 140 | } 141 | } 142 | }, 143 | "colors": [ 144 | { 145 | "id": "clojureVSCode.inlineResultBackground", 146 | "description": "Background color of the inline result.", 147 | "defaults": { 148 | "dark": "#00000000", 149 | "light": "#00000000", 150 | "highContrast": "#00000000" 151 | } 152 | }, 153 | { 154 | "id": "clojureVSCode.inlineResultForeground", 155 | "description": "Foreground color of the inline result.", 156 | "defaults": { 157 | "dark": "#99999999", 158 | "light": "#99999999", 159 | "highContrast": "#99999999" 160 | } 161 | } 162 | ] 163 | }, 164 | "scripts": { 165 | "vscode:prepublish": "webpack --mode production", 166 | "webpack": "webpack --mode development", 167 | "webpack-dev": "webpack --mode development --watch", 168 | "test-compile": "tsc -p ./", 169 | "test": "node ./node_modules/vscode/bin/test" 170 | }, 171 | "devDependencies": { 172 | "@types/cross-spawn": "^6.0.2", 173 | "@types/jszip": "^3.4.1", 174 | "@types/mocha": "^2.2.48", 175 | "@types/node": "^6.14.10", 176 | "minimist": "^1.2.6", 177 | "mocha": "^5.2.0", 178 | "ts-loader": "^9.4.2", 179 | "typescript": "^3.9.2", 180 | "vscode": "^1.1.37", 181 | "webpack": "^5.94.0", 182 | "webpack-cli": "^5.0.1" 183 | }, 184 | "dependencies": { 185 | "bencoder": "^0.0.5", 186 | "cross-spawn": "^6.0.6", 187 | "jszip": "^3.8.0" 188 | }, 189 | "repository": { 190 | "type": "git", 191 | "url": "https://github.com/avli/clojureVSCode" 192 | } 193 | } 194 | -------------------------------------------------------------------------------- /src/bencodeUtil.ts: -------------------------------------------------------------------------------- 1 | import * as bencoder from 'bencoder'; 2 | 3 | interface DecodedResult { 4 | decodedObjects: any[]; 5 | rest: Buffer; 6 | isDone: boolean; 7 | } 8 | 9 | interface Message { 10 | msg: any; 11 | buffer: Buffer; 12 | msgLen: number; 13 | } 14 | 15 | const CONTINUATION_ERROR_MESSAGE: string = "Unexpected continuation: \""; 16 | const BENCODE_END_SYMBOL = 0x65; // `e` 17 | const VALUE_LENGTH_REGEXP = /\:(?value|out)(?\d+)\:/m; 18 | 19 | export function encode(msg: any): Buffer { 20 | return bencoder.encode(msg); 21 | } 22 | 23 | function isMessageIncomplete(message: Message): boolean { 24 | const lastByte = message.buffer[message.buffer.length - 1], 25 | matched = message.buffer.toString().match(VALUE_LENGTH_REGEXP), 26 | // @ts-ignore: target es6 doesn't support `groups` in RegExpMatchArray 27 | { groups: { len, name } = {} } = matched || {}, 28 | requiredLength = len ? Number.parseInt(len) : null, 29 | isLengthInvalid = name in message.msg 30 | && requiredLength !== null 31 | // check length of parsed message 32 | && message.msg[name].length < requiredLength; 33 | 34 | // message's length is valid and the end symbol is presented 35 | return isLengthInvalid || lastByte !== BENCODE_END_SYMBOL; 36 | } 37 | 38 | function decodeNextMessage(data: Buffer): Message { 39 | let message: Message = { msg: null, buffer: data, msgLen: data.length }; 40 | 41 | while (!message.msg) { 42 | try { 43 | message.msg = bencoder.decode(message.buffer.slice(0, message.msgLen)); 44 | 45 | const isWholeBufferParsed = message.msgLen === message.buffer.length; 46 | if (isWholeBufferParsed && isMessageIncomplete(message)) { 47 | message.msg = null; 48 | break; 49 | } 50 | } catch (error) { 51 | if (!!error.message && error.message.startsWith(CONTINUATION_ERROR_MESSAGE)) { 52 | const unexpectedContinuation: string = error.message.slice(CONTINUATION_ERROR_MESSAGE.length, 53 | error.message.length - 1); 54 | message.msgLen -= unexpectedContinuation.length; 55 | } else { 56 | console.log("Unexpected output decoding error."); 57 | break; 58 | } 59 | } 60 | } 61 | 62 | return message; 63 | } 64 | 65 | /* 66 | receives a buffer and returns an array of decoded objects and the remaining unused buffer 67 | */ 68 | export function decodeBuffer(data: Buffer): DecodedResult { 69 | let result: DecodedResult = { decodedObjects: [], rest: data, isDone: false }; 70 | 71 | while (result.rest.length > 0) { 72 | const message = decodeNextMessage(result.rest); 73 | if (!message.msg) { 74 | break; 75 | } 76 | 77 | result.decodedObjects.push(message.msg); 78 | result.rest = result.rest.slice(message.msgLen, result.rest.length); 79 | 80 | if (message.msg.status && message.msg.status.indexOf('done') > -1) { 81 | result.isDone = true; 82 | break; 83 | } 84 | } 85 | 86 | return result; 87 | } 88 | -------------------------------------------------------------------------------- /src/cljConnection.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from 'vscode'; 2 | import * as path from 'path'; 3 | import * as fs from 'fs'; 4 | 5 | import { nreplClient } from './nreplClient'; 6 | import { nreplController } from './nreplController'; 7 | 8 | export interface CljConnectionInformation { 9 | host: string; 10 | port: number; 11 | } 12 | export interface REPLSession { 13 | type: 'ClojureScript' | 'Clojure'; 14 | id?: string; 15 | } 16 | 17 | const CONNECTION_STATE_KEY: string = 'CLJ_CONNECTION'; 18 | const DEFAULT_LOCAL_IP: string = '127.0.0.1'; 19 | const CLJS_SESSION_KEY: string = 'CLJS_SESSION'; 20 | const connectionIndicator: vscode.StatusBarItem = vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Left); 21 | 22 | let cljContext: vscode.ExtensionContext; 23 | 24 | const setCljContext = (context: vscode.ExtensionContext) => cljContext = context; 25 | 26 | const getConnection = (): CljConnectionInformation | undefined => cljContext.workspaceState.get(CONNECTION_STATE_KEY); 27 | 28 | const isConnected = (): boolean => !!getConnection(); 29 | 30 | const saveConnection = (connection: CljConnectionInformation): void => { 31 | cljContext.workspaceState.update(CONNECTION_STATE_KEY, connection); 32 | 33 | connectionIndicator.text = `⚡nrepl://${connection.host}:${connection.port}`; 34 | connectionIndicator.show(); 35 | 36 | vscode.window.showInformationMessage('Connected to nREPL.'); 37 | }; 38 | 39 | const saveDisconnection = (showMessage: boolean = true): void => { 40 | cljContext.workspaceState.update(CONNECTION_STATE_KEY, undefined); 41 | cljContext.workspaceState.update(CLJS_SESSION_KEY, undefined); 42 | 43 | connectionIndicator.text = ''; 44 | connectionIndicator.show(); 45 | 46 | if (showMessage) 47 | vscode.window.showInformationMessage('Disconnected from nREPL.'); 48 | }; 49 | 50 | let loadingHandler: NodeJS.Timer | null 51 | const startLoadingAnimation = () => { 52 | if (loadingHandler) 53 | return; 54 | 55 | const maxAnimationDots: number = 3; 56 | let animationTime: number = 0; 57 | 58 | loadingHandler = setInterval(() => { 59 | connectionIndicator.text = '⚡Starting nREPL' + '.'.repeat(animationTime); 60 | connectionIndicator.show(); 61 | 62 | animationTime += animationTime < maxAnimationDots ? 1 : -maxAnimationDots; 63 | }, 500); 64 | }; 65 | 66 | const stopLoadingAnimation = () => { 67 | if (loadingHandler) { 68 | clearInterval(loadingHandler); 69 | loadingHandler = null; 70 | connectionIndicator.text = ''; 71 | connectionIndicator.show(); 72 | } 73 | }; 74 | 75 | const manuallyConnect = (): void => { 76 | if (loadingHandler) { 77 | vscode.window.showWarningMessage('Already starting a nREPL. Disconnect first.'); 78 | return; 79 | } 80 | if (isConnected()) { 81 | vscode.window.showWarningMessage('Already connected to nREPL. Disconnect first.'); 82 | return; 83 | } 84 | 85 | let host: string; 86 | let port: number; 87 | vscode.window.showInputBox({ prompt: 'nREPL host', value: DEFAULT_LOCAL_IP }) 88 | .then(hostFromUser => { 89 | if (!hostFromUser) 90 | return Promise.reject({ connectionError: 'Host must be informed.' }); 91 | 92 | host = hostFromUser; 93 | 94 | const portNumberPromptOptions: vscode.InputBoxOptions = { prompt: 'nREPL port number' }; 95 | 96 | if (hostFromUser === DEFAULT_LOCAL_IP || hostFromUser.toLowerCase() === 'localhost') { 97 | const localPort = getLocalNReplPort(); 98 | if (localPort) 99 | portNumberPromptOptions.value = String(localPort); 100 | } 101 | 102 | return >vscode.window.showInputBox(portNumberPromptOptions); // cast needed to chain promises 103 | }) 104 | .then(portFromUser => { 105 | if (!portFromUser) 106 | return Promise.reject({ connectionError: 'Port number must be informed.' }); 107 | 108 | const intPort = Number.parseInt(portFromUser); 109 | if (!intPort) 110 | return Promise.reject({ connectionError: 'Port number must be an integer.' }); 111 | 112 | port = intPort; 113 | }) 114 | .then(() => nreplClient.test({ host, port })) 115 | .then(() => { 116 | saveConnection({ host, port }); 117 | } 118 | , ({ connectionError }) => { 119 | if (!connectionError) 120 | connectionError = "Can't connect to the nREPL."; 121 | 122 | vscode.window.showErrorMessage(connectionError); 123 | }); 124 | }; 125 | 126 | const startNRepl = (): void => { 127 | if (isConnected()) { 128 | vscode.window.showWarningMessage('Already connected to nREPL. Disconnect first.'); 129 | return; 130 | } 131 | 132 | startLoadingAnimation(); 133 | 134 | let nreplConnection: CljConnectionInformation; 135 | nreplController.start() 136 | .then(connectionInfo => nreplConnection = connectionInfo) 137 | .then(() => nreplClient.test(nreplConnection)) 138 | .then(stopLoadingAnimation) 139 | .then(() => saveConnection(nreplConnection), ({ nreplError }) => { 140 | stopLoadingAnimation(); 141 | if (!nreplError) 142 | nreplError = "Can't start nREPL."; 143 | disconnect(false); 144 | vscode.window.showErrorMessage(nreplError); 145 | }); 146 | }; 147 | 148 | const disconnect = (showMessage: boolean = true): void => { 149 | if (isConnected() || loadingHandler) { 150 | stopLoadingAnimation(); 151 | nreplController.stop(); 152 | saveDisconnection(showMessage); 153 | } else if (showMessage) 154 | vscode.window.showWarningMessage('Not connected to any nREPL.'); 155 | }; 156 | 157 | const getLocalNReplPort = (): number | undefined => { 158 | const projectDir = vscode.workspace.rootPath; 159 | 160 | if (projectDir) { 161 | const projectPort: number = getPortFromFS(path.join(projectDir, '.nrepl-port')); 162 | if (projectPort) 163 | return projectPort; 164 | } 165 | 166 | const homeDir = process.env.HOME || process.env.HOMEPATH || process.env.USERPROFILE; 167 | if (homeDir) { 168 | return getPortFromFS(path.join(homeDir, '.lein', 'repl-port')); 169 | } 170 | }; 171 | 172 | const getPortFromFS = (path: string): number => fs.existsSync(path) ? Number.parseInt(fs.readFileSync(path, 'utf-8')) : NaN; 173 | 174 | const findClojureScriptSession = (sessions: string[]): Promise => { 175 | if (sessions.length == 0) 176 | return Promise.reject(null); 177 | 178 | const base_session = sessions.shift(); 179 | 180 | if (!base_session) { 181 | return Promise.reject("no base session"); 182 | } 183 | return nreplClient.evaluate('(js/parseInt "42")', base_session).then(results => { 184 | let { session, value } = results[0]; 185 | nreplClient.close(session); 186 | if (value == 42) { 187 | return Promise.resolve(base_session); 188 | } 189 | 190 | return findClojureScriptSession(sessions); 191 | }); 192 | } 193 | 194 | const discoverSessions = (): Promise => { 195 | return nreplClient.listSessions().then(sessions => { 196 | return findClojureScriptSession(sessions).then(cljs_session => { 197 | console.log("found ClojureScript session", cljs_session); 198 | cljContext.workspaceState.update(CLJS_SESSION_KEY, cljs_session); 199 | return cljs_session; 200 | }).catch(reason => { 201 | cljContext.workspaceState.update(CLJS_SESSION_KEY, undefined); 202 | throw reason; 203 | }); 204 | }); 205 | } 206 | 207 | const sessionForFilename = (filename: string): Promise => { 208 | return new Promise((resolve, reject) => { 209 | const sessionType = filename.endsWith('.cljs') ? "ClojureScript" : "Clojure"; 210 | if (sessionType == "Clojure") { 211 | // Assume that the default session is Clojure. This is always the case with cider. 212 | return resolve({ type: sessionType, id: undefined }); 213 | } 214 | 215 | const session_id = cljContext.workspaceState.get(CLJS_SESSION_KEY); 216 | if (session_id) 217 | return resolve({ type: sessionType, id: session_id }); 218 | return discoverSessions().then(session_id => { 219 | resolve({ type: sessionType, id: session_id }); 220 | }); 221 | }); 222 | } 223 | 224 | export const cljConnection = { 225 | setCljContext, 226 | getConnection, 227 | isConnected, 228 | manuallyConnect, 229 | startNRepl, 230 | disconnect, 231 | sessionForFilename 232 | }; 233 | -------------------------------------------------------------------------------- /src/cljParser.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from 'vscode'; 2 | 3 | interface ExpressionInfo { 4 | functionName: string; 5 | parameterPosition: number; 6 | } 7 | 8 | interface RelativeExpressionInfo { 9 | startPosition: number; 10 | parameterPosition: number; 11 | } 12 | 13 | const CLJ_TEXT_DELIMITER = `"`; 14 | const CLJ_TEXT_ESCAPE = `\\`; 15 | const CLJ_COMMENT_DELIMITER = `;`; 16 | const R_CLJ_WHITE_SPACE = /\s|,/; 17 | const R_CLJ_OPERATOR_DELIMITERS = /\s|,|\(|{|\[/; 18 | const OPEN_CLJ_BLOCK_BRACKET = `(`; 19 | const CLOSE_CLJ_BLOCK_BRACKET = `)`; 20 | 21 | /** { close_char open_char } */ 22 | const CLJ_EXPRESSION_DELIMITERS: Map = new Map([ 23 | [`}`, `{`], 24 | [CLOSE_CLJ_BLOCK_BRACKET, OPEN_CLJ_BLOCK_BRACKET], 25 | [`]`, `[`], 26 | [CLJ_TEXT_DELIMITER, CLJ_TEXT_DELIMITER], 27 | ]); 28 | 29 | const getExpressionInfo = (text: string): ExpressionInfo | undefined => { 30 | text = removeCljComments(text); 31 | const relativeExpressionInfo = getRelativeExpressionInfo(text); 32 | if (!relativeExpressionInfo) 33 | return; 34 | 35 | let functionName = text.substring(relativeExpressionInfo.startPosition + 1); // expression openning ignored 36 | functionName = functionName.substring(functionName.search(/[^,\s]/)); // trim left 37 | functionName = functionName.substring(0, functionName.search(R_CLJ_OPERATOR_DELIMITERS)); // trim right according to operator delimiter 38 | 39 | if (!functionName.length) 40 | return; 41 | 42 | return { 43 | functionName, 44 | parameterPosition: relativeExpressionInfo.parameterPosition, 45 | }; 46 | }; 47 | 48 | const removeCljComments = (text: string): string => { 49 | const lines = text.match(/[^\r\n]+/g) || [] // split string by line 50 | 51 | if (lines.length > 1) { 52 | return lines.map(line => removeCljComments(line)).join(`\n`); // remove comments from each line and concat them again after 53 | } 54 | 55 | const line = lines[0]; 56 | let uncommentedIndex = line.length; 57 | let insideString = false; 58 | for (let i = 0; i < line.length; i++) { 59 | if (line[i] === CLJ_TEXT_DELIMITER) { 60 | insideString = !insideString || line[i - 1] === CLJ_TEXT_ESCAPE; 61 | continue; 62 | } 63 | if (line[i] === CLJ_COMMENT_DELIMITER && !insideString) { // ignore comment delimiter inside a string 64 | uncommentedIndex = i; 65 | break; 66 | } 67 | } 68 | 69 | return line.substring(0, uncommentedIndex); 70 | }; 71 | 72 | const getRelativeExpressionInfo = (text: string, openChar: string = `(`): RelativeExpressionInfo | undefined => { 73 | const relativeExpressionInfo: RelativeExpressionInfo = { 74 | startPosition: text.length - 1, 75 | parameterPosition: -1, 76 | }; 77 | 78 | let newParameterFound = false; 79 | while (relativeExpressionInfo.startPosition >= 0) { 80 | const char = text[relativeExpressionInfo.startPosition]; 81 | 82 | // check if found the beginning of the expression (string escape taken care of) 83 | if (char === openChar && (openChar !== CLJ_TEXT_DELIMITER || (text[relativeExpressionInfo.startPosition - 1] !== CLJ_TEXT_ESCAPE))) { 84 | if (newParameterFound) // ignore one parameter found if it's actually the function we're looking for 85 | relativeExpressionInfo.parameterPosition--; 86 | return relativeExpressionInfo; 87 | } 88 | 89 | // ignore everything if searching inside a string 90 | if (openChar === CLJ_TEXT_DELIMITER) { 91 | relativeExpressionInfo.startPosition--; 92 | continue; 93 | } 94 | 95 | // invalid code if a beginning of an expression is found without being searched for 96 | if (char !== CLJ_TEXT_DELIMITER && containsValue(CLJ_EXPRESSION_DELIMITERS, char)) 97 | return; 98 | 99 | // keep searching if it's white space 100 | if (R_CLJ_WHITE_SPACE.test(char)) { 101 | if (!newParameterFound) { 102 | relativeExpressionInfo.parameterPosition++; 103 | newParameterFound = true; 104 | } 105 | relativeExpressionInfo.startPosition--; 106 | continue; 107 | } 108 | 109 | // check for new expressions 110 | const expressionDelimiter = CLJ_EXPRESSION_DELIMITERS.get(char); 111 | if (!!expressionDelimiter) { 112 | const innerExpressionInfo = getRelativeExpressionInfo(text.substring(0, relativeExpressionInfo.startPosition), expressionDelimiter); 113 | if (!innerExpressionInfo) 114 | return; 115 | 116 | relativeExpressionInfo.startPosition = innerExpressionInfo.startPosition - 1; 117 | relativeExpressionInfo.parameterPosition++; 118 | newParameterFound = true; 119 | continue; 120 | } 121 | 122 | newParameterFound = false; 123 | relativeExpressionInfo.startPosition--; 124 | } 125 | 126 | return; // reached the beginning of the text without finding the start of the expression 127 | }; 128 | 129 | const containsValue = (map: Map, checkValue: any): boolean => { 130 | for (let value of map.values()) { 131 | if (value === checkValue) 132 | return true; 133 | } 134 | return false; 135 | }; 136 | 137 | const getNamespace = (text: string): string => { 138 | const m = text.match(/^[;\s\t\n]*\((?:[\s\t\n]*(?:in-){0,1}ns)[\s\t\n]+'?([\w\-.]+)[\s\S]*\)[\s\S]*/); 139 | return m ? m[1] : 'user'; 140 | }; 141 | 142 | /** A range of numbers between `start` and `end`. 143 | 144 | * The `end` index is not included and `end` could be before `start`. 145 | * Whereas default `vscode.Range` requires that `start` should be 146 | * before or equal than `end`. */ 147 | const range = (start: number, end: number): Array => { 148 | if (start < end) { 149 | const length = end - start; 150 | return Array.from(Array(length), (_, i) => start + i); 151 | } else { 152 | const length = start - end; 153 | return Array.from(Array(length), (_, i) => start - i); 154 | } 155 | } 156 | 157 | const findNearestBracket = ( 158 | editor: vscode.TextEditor, 159 | current: vscode.Position, 160 | bracket: string): vscode.Position | undefined => { 161 | 162 | const isBackward = bracket == OPEN_CLJ_BLOCK_BRACKET; 163 | // "open" and "close" brackets as keys related to search direction 164 | let openBracket = OPEN_CLJ_BLOCK_BRACKET, 165 | closeBracket = CLOSE_CLJ_BLOCK_BRACKET; 166 | if (isBackward) { 167 | [closeBracket, openBracket] = [openBracket, closeBracket] 168 | }; 169 | 170 | let bracketStack: string[] = [], 171 | // get begin of text if we are searching `(` and end of text otherwise 172 | lastLine = isBackward ? -1 : editor.document.lineCount, 173 | lineRange = range(current.line, lastLine); 174 | 175 | for (var line of lineRange) { 176 | const textLine = editor.document.lineAt(line); 177 | if (textLine.isEmptyOrWhitespace) continue; 178 | 179 | // get line and strip clj comments 180 | const firstChar = textLine.firstNonWhitespaceCharacterIndex, 181 | strippedLine = removeCljComments(textLine.text); 182 | let startColumn = firstChar, 183 | endColumn = strippedLine.length; 184 | if (isBackward) { 185 | // dec both as `range` doesn't include an end edge 186 | [startColumn, endColumn] = [endColumn - 1, startColumn - 1]; 187 | } 188 | 189 | // select block if cursor right after the closed bracket or right before opening 190 | if (current.line == line) { 191 | // get current current char index if it is first iteration of loop 192 | let currentColumn = current.character; 193 | // set current position as start 194 | if (isBackward) { 195 | if (currentColumn <= endColumn) continue; 196 | startColumn = currentColumn; 197 | if (strippedLine[startColumn - 1] == CLOSE_CLJ_BLOCK_BRACKET) { 198 | startColumn = startColumn - 2; 199 | } else if (strippedLine[startColumn] == CLOSE_CLJ_BLOCK_BRACKET) { 200 | startColumn--; 201 | }; 202 | } else if (currentColumn <= endColumn) { 203 | // forward direction 204 | startColumn = currentColumn; 205 | if (strippedLine[startColumn - 1] == CLOSE_CLJ_BLOCK_BRACKET) { 206 | return new vscode.Position(line, startColumn); 207 | } else if (strippedLine[startColumn] == OPEN_CLJ_BLOCK_BRACKET) { 208 | startColumn++; 209 | }; 210 | } 211 | } 212 | 213 | // search nearest bracket 214 | for (var column of range(startColumn, endColumn)) { 215 | const char = strippedLine[column]; 216 | if (!bracketStack.length && char == bracket) { 217 | // inc column if `char` is a `)` to get correct selection 218 | if (!isBackward) column++; 219 | return new vscode.Position(line, column); 220 | } else if (char == openBracket) { 221 | bracketStack.push(char); 222 | // check if inner block is closing 223 | } else if (char == closeBracket && bracketStack.length > 0) { 224 | bracketStack.pop(); 225 | }; 226 | }; 227 | } 228 | }; 229 | 230 | 231 | const getCurrentBlock = ( 232 | editor: vscode.TextEditor, 233 | left?: vscode.Position, 234 | right?: vscode.Position): vscode.Selection | undefined => { 235 | 236 | if (!left || !right) { 237 | left = right = editor.selection.active; 238 | }; 239 | const prevBracket = findNearestBracket(editor, left, OPEN_CLJ_BLOCK_BRACKET); 240 | if (!prevBracket) return; 241 | 242 | const nextBracket = findNearestBracket(editor, right, CLOSE_CLJ_BLOCK_BRACKET); 243 | if (nextBracket) { 244 | return new vscode.Selection(prevBracket, nextBracket); 245 | } 246 | }; 247 | 248 | const getOuterBlock = ( 249 | editor: vscode.TextEditor, 250 | left?: vscode.Position, 251 | right?: vscode.Position, 252 | prevBlock?: vscode.Selection): vscode.Selection | undefined => { 253 | 254 | if (!left || !right) { 255 | left = right = editor.selection.active; 256 | }; 257 | 258 | const nextBlock = getCurrentBlock(editor, left, right); 259 | 260 | if (nextBlock) { 261 | // calculate left position one step before 262 | if (nextBlock.anchor.character > 0) { 263 | left = new vscode.Position(nextBlock.anchor.line, 264 | nextBlock.anchor.character - 1); 265 | } else if (nextBlock.anchor.line > 0) { 266 | const line = nextBlock.anchor.line - 1; 267 | left = editor.document.lineAt(line).range.end; 268 | } else { 269 | return new vscode.Selection(nextBlock.anchor, nextBlock.active); 270 | } 271 | 272 | // calculate right position one step after 273 | const lineLength = editor.document.lineAt(nextBlock.active.line).text.length; 274 | if (nextBlock.active.character < lineLength) { 275 | right = new vscode.Position(nextBlock.active.line, 276 | nextBlock.active.character + 1); 277 | } else if (nextBlock.active.line < editor.document.lineCount - 1) { 278 | right = new vscode.Position(nextBlock.active.line + 1, 0); 279 | } else { 280 | return new vscode.Selection(nextBlock.anchor, nextBlock.active); 281 | }; 282 | 283 | // try to find next outer block 284 | return getOuterBlock(editor, left, right, nextBlock); 285 | } else if (right != left) { 286 | return prevBlock; 287 | }; 288 | } 289 | 290 | export const cljParser = { 291 | R_CLJ_WHITE_SPACE, 292 | getExpressionInfo, 293 | getNamespace, 294 | getCurrentBlock, 295 | getOuterBlock, 296 | }; 297 | -------------------------------------------------------------------------------- /src/clojureConfiguration.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from 'vscode'; 2 | 3 | const keywords = [ 4 | 'ns', 5 | 'fn', 6 | 'def', 7 | 'defn', 8 | 'bound\\-fn', 9 | 'if', 10 | 'if\\-not', 11 | 'case,', 12 | 'cond', 13 | 'condp', 14 | 'cond\\-\\>', 15 | 'cond\\-\\>\\>', 16 | 'when', 17 | 'while', 18 | 'when\\-not', 19 | 'when\\-first', 20 | 'do', 21 | 'future', 22 | 'comment', 23 | 'doto', 24 | 'locking', 25 | 'proxy', 26 | 'as\\-\\>', 27 | 'reify', 28 | 'deftype', 29 | 'defrecord', 30 | 'defprotocol', 31 | 'extend', 32 | 'extend-protocol', 33 | 'extend-type', 34 | 'specify', 35 | 'specify\\!', 36 | 'try', 37 | 'catch', 38 | 'finally', 39 | 'let', 40 | 'letfn', 41 | 'binding', 42 | 'loop', 43 | 'for', 44 | 'doseq', 45 | 'dotimes', 46 | 'when\\-let', 47 | 'if\\-let', 48 | 'when\\-some', 49 | 'if\\-some', 50 | 'this\\-as', 51 | 'defmethod', 52 | 'testing', 53 | 'deftest', 54 | 'are', 55 | 'use\\-fixtures', 56 | 'run', 57 | 'run\\*', 58 | 'fresh', 59 | 'alt!', 60 | 'alt!!', 61 | 'go', 62 | 'go\\-loop', 63 | 'thread', 64 | ] 65 | 66 | export const ClojureLanguageConfiguration : vscode.LanguageConfiguration = { 67 | wordPattern: /[\w\-\.:<>\*][\w\d\.\\/\-\?<>\*!]+/, 68 | indentationRules: { 69 | decreaseIndentPattern: /$^/, // line end then start - this will never match anything 70 | increaseIndentPattern: /^\s*\(.*[^)]\s*$/ 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/clojureDefinition.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from 'vscode'; 2 | 3 | import { cljConnection } from './cljConnection'; 4 | import { cljParser } from './cljParser'; 5 | import { nreplClient } from './nreplClient'; 6 | 7 | export class ClojureDefinitionProvider implements vscode.DefinitionProvider { 8 | 9 | provideDefinition(document: vscode.TextDocument, position: vscode.Position, token: vscode.CancellationToken): Thenable { 10 | if (!cljConnection.isConnected()) 11 | return Promise.reject('No nREPL connected.'); 12 | 13 | const wordRange = document.getWordRangeAtPosition(position); 14 | if (!wordRange) 15 | return Promise.reject('No word selected.'); 16 | 17 | const currentWord: string = document.lineAt(position.line).text.slice(wordRange.start.character, wordRange.end.character); 18 | const ns = cljParser.getNamespace(document.getText()); 19 | 20 | return cljConnection.sessionForFilename(document.fileName).then(session => { 21 | return nreplClient.info(currentWord, ns, session.id).then(info => { 22 | if (!info.file) 23 | return Promise.reject('No word definition found.'); 24 | 25 | let uri = vscode.Uri.parse(info.file); 26 | let pos = new vscode.Position(info.line - 1, info.column) 27 | let definition = new vscode.Location(uri, pos); 28 | return Promise.resolve(definition); 29 | }); 30 | }); 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /src/clojureEval.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from 'vscode'; 2 | 3 | import { cljConnection } from './cljConnection'; 4 | import { LANGUAGE } from './clojureMode'; 5 | import { cljParser } from './cljParser'; 6 | import { nreplClient } from './nreplClient'; 7 | import { TestListener } from './testRunner'; 8 | 9 | const HIGHLIGHTING_TIMEOUT = 350; 10 | const BLOCK_DECORATION_TYPE = vscode.window.createTextEditorDecorationType({ 11 | backgroundColor: { id: 'editor.findMatchHighlightBackground' } 12 | }); 13 | 14 | const INLINE_RESULT_LENGTH = 150; 15 | const INLINE_RESULT_DECORATION_TYPE = vscode.window.createTextEditorDecorationType({ 16 | before: { 17 | margin: '0 0 0 2em', 18 | textDecoration: 'none', 19 | fontWeight: 'normal', 20 | fontStyle: 'normal', 21 | }, 22 | rangeBehavior: vscode.DecorationRangeBehavior.ClosedOpen 23 | }); 24 | 25 | export function clojureEval(outputChannel: vscode.OutputChannel): void { 26 | evaluate(outputChannel, false); 27 | } 28 | 29 | export function clojureEvalAndShowResult(outputChannel: vscode.OutputChannel): void { 30 | evaluate(outputChannel, true); 31 | } 32 | 33 | type TestResults = { 34 | summary: { 35 | error: number 36 | fail: number 37 | ns: number 38 | pass: number 39 | test: number 40 | var: number 41 | } 42 | 'testing-ns': string 43 | 'gen-input': any[] 44 | status?: string[] 45 | results: { 46 | [key: string]: { // Namespace 47 | [key: string]: { 48 | context: any 49 | file?: string 50 | index: number 51 | line?: number 52 | message?: string 53 | ns: string 54 | type: string 55 | var: string 56 | actual?: string 57 | expected?: string 58 | }[]; 59 | } 60 | } 61 | session: string 62 | } 63 | 64 | function runTests(outputChannel: vscode.OutputChannel, listener: TestListener, namespace?: string): void { 65 | if (!cljConnection.isConnected()) { 66 | vscode.window.showWarningMessage('You must be connected to an nREPL session to test a namespace.'); 67 | return; 68 | } 69 | 70 | const promise: Promise = nreplClient.runTests(namespace); 71 | 72 | promise.then((responses) => { 73 | 74 | console.log("Test result promise delivery"); 75 | 76 | responses.forEach(response => { 77 | 78 | console.log(response); 79 | console.log(response.results); 80 | 81 | if (response.status && response.status.indexOf("unknown-op") != -1) { 82 | outputChannel.appendLine("Failed to run tests: the cider.nrepl.middleware.test middleware in not loaded."); 83 | return; 84 | } 85 | 86 | for (const ns in response.results) { 87 | 88 | const namespace = response.results[ns]; 89 | 90 | outputChannel.appendLine("Results for " + ns) 91 | 92 | for (const varName in namespace) { 93 | 94 | // Each var being tested reports a list of statuses, one for each 95 | // `is` assertion in the test. Here we just want to reduce this 96 | // down to a single pass/fail. 97 | const statuses = new Set(namespace[varName].map(r => r.type)); 98 | const passed = (statuses.size == 0) || 99 | ((statuses.size == 1) && statuses.has('pass')); 100 | listener.onTestResult(ns, varName, passed); 101 | 102 | namespace[varName].forEach(r => { 103 | if (r.type != 'pass') { 104 | outputChannel.appendLine(r.type + " in (" + r.var + ") (" + r.file + ":" + r.line + ")"); 105 | if (typeof r.message === 'string') { 106 | outputChannel.appendLine(r.message); 107 | } 108 | if (r.expected) { 109 | outputChannel.append("expected: " + r.expected) 110 | } 111 | if (r.actual) { 112 | outputChannel.append(" actual: " + r.actual) 113 | } 114 | } 115 | }); 116 | } 117 | } 118 | 119 | if ('summary' in response) { 120 | const failed = response.summary.fail + response.summary.error; 121 | if (failed > 0) { 122 | vscode.window.showErrorMessage(failed + " tests failed.") 123 | } else { 124 | vscode.window.showInformationMessage(response.summary.var + " tests passed") 125 | } 126 | } 127 | }); 128 | 129 | }).catch((reason): void => { 130 | const message: string = "" + reason; 131 | outputChannel.append("Tests failed: "); 132 | outputChannel.appendLine(message); 133 | }); 134 | } 135 | 136 | export function testNamespace(outputChannel: vscode.OutputChannel, listener: TestListener): void { 137 | const editor = vscode.window.activeTextEditor; 138 | if (editor) { 139 | const text = editor.document.getText(); 140 | const ns = cljParser.getNamespace(text); // log ns and 'starting' 141 | outputChannel.appendLine("Testing " + ns) 142 | runTests(outputChannel, listener, ns); 143 | } else { 144 | // if having troubles with finding the namespace (though I'm not sure 145 | // if it can actually happen), run all tests 146 | runAllTests(outputChannel, listener); 147 | } 148 | } 149 | 150 | export function runAllTests(outputChannel: vscode.OutputChannel, listener: TestListener): void { 151 | outputChannel.appendLine("Testing all namespaces"); 152 | runTests(outputChannel, listener); 153 | } 154 | 155 | const highlightSelection = (editor: vscode.TextEditor, selection: vscode.Selection) => { 156 | let selectionRange = new vscode.Range(selection.start, selection.end); 157 | // setup highlighting of evaluated block 158 | editor.setDecorations(BLOCK_DECORATION_TYPE, [selectionRange]) 159 | // stop highlighting of block after timeout 160 | setTimeout(() => { 161 | editor.setDecorations(BLOCK_DECORATION_TYPE, []) 162 | }, 163 | HIGHLIGHTING_TIMEOUT); 164 | }; 165 | 166 | function evaluate(outputChannel: vscode.OutputChannel, showResults: boolean): void { 167 | if (!cljConnection.isConnected()) { 168 | vscode.window.showWarningMessage('You should connect to nREPL first to evaluate code.'); 169 | return; 170 | } 171 | 172 | const editor = vscode.window.activeTextEditor; 173 | 174 | if (!editor) return; 175 | 176 | // select and highlight appropriate block if selection is empty 177 | let blockSelection: vscode.Selection | undefined; 178 | if (editor.selection.isEmpty) { 179 | blockSelection = showResults ? cljParser.getCurrentBlock(editor) : cljParser.getOuterBlock(editor); 180 | if (blockSelection) { 181 | highlightSelection(editor, blockSelection); 182 | console.log("eval:\n", editor.document.getText(blockSelection)); 183 | } else { 184 | console.log("eval:", "Whole file"); 185 | } 186 | } 187 | 188 | const selection = blockSelection || editor.selection; 189 | let text = editor.document.getText(), 190 | selectionEndLine: number; 191 | 192 | if (!selection.isEmpty) { 193 | selectionEndLine = selection.end.line; 194 | const ns: string = cljParser.getNamespace(text); 195 | text = `(ns ${ns})\n${editor.document.getText(selection)}`; 196 | } else { 197 | selectionEndLine = editor.document.lineCount - 1; 198 | } 199 | 200 | cljConnection.sessionForFilename(editor.document.fileName).then(session => { 201 | let response; 202 | if (!selection.isEmpty && session.type == 'ClojureScript') { 203 | // Piggieback's evalFile() ignores the text sent as part of the request 204 | // and just loads the whole file content from disk. So we use eval() 205 | // here, which as a drawback will give us a random temporary filename in 206 | // the stacktrace should an exception occur. 207 | response = nreplClient.evaluate(text, session.id); 208 | } else { 209 | response = nreplClient.evaluateFile(text, editor.document.fileName, session.id); 210 | } 211 | response.then(respObjs => { 212 | if (!!respObjs[0].ex) 213 | return handleError(outputChannel, selection, showResults, respObjs[0].session); 214 | 215 | return handleSuccess(outputChannel, showResults, respObjs, selectionEndLine); 216 | }) 217 | }); 218 | } 219 | 220 | function handleError(outputChannel: vscode.OutputChannel, selection: vscode.Selection, showResults: boolean, session: string): Promise { 221 | if (!showResults) 222 | vscode.window.showErrorMessage('Compilation error'); 223 | 224 | return nreplClient.stacktrace(session) 225 | .then(stacktraceObjs => { 226 | stacktraceObjs.forEach((stacktraceObj: any) => { 227 | if (stacktraceObj.status && stacktraceObj.status.indexOf("done") >= 0) { 228 | return; 229 | } 230 | 231 | let errLine = stacktraceObj.line !== undefined ? stacktraceObj.line - 1 : 0; 232 | let errChar = stacktraceObj.column !== undefined ? stacktraceObj.column - 1 : 0; 233 | 234 | if (!selection.isEmpty) { 235 | errLine += selection.start.line; 236 | errChar += selection.start.character; 237 | } 238 | 239 | outputChannel.appendLine(`${stacktraceObj.class} ${stacktraceObj.message}`); 240 | if (stacktraceObj.file) { 241 | outputChannel.appendLine(` at ${stacktraceObj.file}:${errLine}:${errChar}`); 242 | } 243 | 244 | stacktraceObj.stacktrace.forEach((trace: any) => { 245 | if (trace.flags.indexOf('tooling') > -1) 246 | outputChannel.appendLine(` ${trace.class}.${trace.method} (${trace.file}:${trace.line})`); 247 | }); 248 | 249 | outputChannel.show(true); 250 | nreplClient.close(session); 251 | }); 252 | }); 253 | } 254 | 255 | function truncateLine(value: string): string { 256 | if (value.length > INLINE_RESULT_LENGTH) { 257 | return value.substring(0, INLINE_RESULT_LENGTH) + '...' 258 | } 259 | return value; 260 | } 261 | 262 | function showInlineResult(respObj: any, line: number): void { 263 | const isError = Boolean(respObj.err), 264 | editor = vscode.window.activeTextEditor; 265 | let result: string, 266 | foregroundColor: vscode.ThemeColor; 267 | 268 | if (isError) { 269 | // show more error description at once 270 | result = respObj.err.replace(/\n/g, ' '); 271 | foregroundColor = new vscode.ThemeColor('editorError.foreground'); 272 | } else { 273 | result = respObj.value; 274 | foregroundColor = new vscode.ThemeColor('clojureVSCode.inlineResultForeground'); 275 | } 276 | 277 | if (result && editor) { 278 | const decoration: vscode.DecorationOptions = { 279 | renderOptions: { 280 | before: { 281 | backgroundColor: new vscode.ThemeColor('clojureVSCode.inlineResultBackground'), 282 | color: foregroundColor, 283 | contentText: truncateLine(result), 284 | }, 285 | }, 286 | range: editor.document.validateRange( 287 | new vscode.Range(line, Number.MAX_SAFE_INTEGER, line, Number.MAX_SAFE_INTEGER) 288 | ) 289 | }; 290 | editor.setDecorations(INLINE_RESULT_DECORATION_TYPE, [decoration]); 291 | } 292 | } 293 | 294 | function handleSuccess(outputChannel: vscode.OutputChannel, showResults: boolean, respObjs: any[], selectionEndLine: number): void { 295 | if (!showResults) { 296 | vscode.window.showInformationMessage('Successfully compiled'); 297 | } else { 298 | const config = vscode.workspace.getConfiguration('clojureVSCode'); 299 | 300 | respObjs.forEach(respObj => { 301 | if (respObj.out) 302 | outputChannel.append(respObj.out); 303 | if (respObj.err) 304 | outputChannel.append(respObj.err); 305 | if (respObj.value) 306 | outputChannel.appendLine(`=> ${respObj.value}`); 307 | 308 | if (config.showResultInline) { 309 | showInlineResult(respObj, selectionEndLine); 310 | } else { 311 | outputChannel.show(true); 312 | }; 313 | }); 314 | } 315 | nreplClient.close(respObjs[0].session); 316 | }; 317 | 318 | export function clearInlineResultDecorationOnMove(event: vscode.TextEditorSelectionChangeEvent) { 319 | const config = vscode.workspace.getConfiguration('clojureVSCode'); 320 | if (config.showResultInline 321 | && event.textEditor.document.languageId === LANGUAGE 322 | && event.textEditor === vscode.window.activeTextEditor) { 323 | 324 | event.textEditor.setDecorations(INLINE_RESULT_DECORATION_TYPE, []); 325 | } 326 | } 327 | -------------------------------------------------------------------------------- /src/clojureFormat.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from 'vscode'; 2 | 3 | import { cljConnection } from './cljConnection'; 4 | import { nreplClient } from './nreplClient'; 5 | 6 | function slashEscape(contents: string) { 7 | return contents 8 | .replace(/\\/g, '\\\\') 9 | .replace(/"/g, '\\"') 10 | .replace(/\n/g, '\\n'); 11 | } 12 | 13 | function slashUnescape(contents: string) { 14 | const replacements : { [key: string]: string} = { '\\\\': '\\', '\\n': '\n', '\\"': '"' }; 15 | return contents.replace(/\\(\\|n|")/g, function(match) { 16 | return replacements[match] 17 | }); 18 | } 19 | 20 | 21 | export const formatFile = (document: vscode.TextDocument, range: vscode.Range): Promise => { 22 | 23 | if (!cljConnection.isConnected()) { 24 | return Promise.reject("Formatting functions don't work, connect to nREPL first."); 25 | } 26 | 27 | let contents: string = document.getText(range); 28 | 29 | // Escaping the string before sending it to nREPL 30 | contents = slashEscape(contents) 31 | 32 | 33 | let cljfmtParams = vscode.workspace.getConfiguration('clojureVSCode').cljfmtParameters; 34 | cljfmtParams = cljfmtParams.isEmpty ? "nil" : "{"+cljfmtParams+"}"; 35 | 36 | 37 | // Running "(require 'cljfmt.core)" in right after we have checked we are connected to nREPL 38 | // would be a better option but in this case "cljfmt.core/reformat-string" fails the first 39 | // time it is called. I have no idea what causes this behavior so I decided to put the require 40 | // statement right here - don't think it does any harm. If someone knows how to fix it 41 | // please send a pull request with a fix. 42 | return nreplClient.evaluate(`(require 'cljfmt.core) (cljfmt.core/reformat-string "${contents}" ${cljfmtParams})`) 43 | .then(value => { 44 | if ('ex' in value[0]) { 45 | return Promise.reject(value[1].err); 46 | }; 47 | if (('value' in value[1]) && (value[1].value != 'nil')) { 48 | let new_content: string = value[1].value.slice(1, -1); 49 | new_content = slashUnescape(new_content); 50 | return Promise.resolve([vscode.TextEdit.replace(range, new_content)]); 51 | }; 52 | }); 53 | } 54 | 55 | 56 | export const maybeActivateFormatOnSave = () => { 57 | vscode.workspace.onWillSaveTextDocument(e => { 58 | const document = e.document; 59 | if (document.languageId !== "clojure") { 60 | return; 61 | } 62 | let textEditor = vscode.window.activeTextEditor; 63 | if (!textEditor || textEditor.document.isClosed) { 64 | return 65 | } 66 | let editorConfig = vscode.workspace.getConfiguration('editor'); 67 | const globalEditorFormatOnSave = editorConfig && editorConfig.has('formatOnSave') && editorConfig.get('formatOnSave') === true, 68 | clojureConfig = vscode.workspace.getConfiguration('clojureVSCode'), 69 | currentText = textEditor.document.getText(), 70 | lastLine = textEditor.document.lineCount - 1, 71 | lastPosition = textEditor.document.lineAt(lastLine).range.end, 72 | range = new vscode.Range(new vscode.Position(0, 0), lastPosition); 73 | 74 | if ((clojureConfig.formatOnSave || globalEditorFormatOnSave) && textEditor.document === document) { 75 | formatFile(textEditor.document, range).then(value => { 76 | if (textEditor && value && currentText != value[0].newText) { 77 | textEditor.edit(editBuilder => { 78 | editBuilder.replace(range, value[0].newText); 79 | }); 80 | } 81 | }).catch(reason => { 82 | vscode.window.showErrorMessage(reason); 83 | }); 84 | } 85 | }); 86 | } 87 | 88 | 89 | export class ClojureRangeFormattingEditProvider implements vscode.DocumentRangeFormattingEditProvider { 90 | provideDocumentRangeFormattingEdits( 91 | document: vscode.TextDocument, 92 | range: vscode.Range, 93 | options: vscode.FormattingOptions, 94 | token: vscode.CancellationToken): vscode.ProviderResult { 95 | 96 | return formatFile(document, range); 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /src/clojureHover.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from 'vscode'; 2 | 3 | import { cljConnection } from './cljConnection'; 4 | import { cljParser } from './cljParser'; 5 | import { nreplClient } from './nreplClient'; 6 | 7 | export class ClojureHoverProvider implements vscode.HoverProvider { 8 | 9 | provideHover(document: vscode.TextDocument, position: vscode.Position, token: vscode.CancellationToken): Thenable { 10 | if (!cljConnection.isConnected()) 11 | return Promise.reject('No nREPL connected.'); 12 | 13 | let wordRange = document.getWordRangeAtPosition(position); 14 | if (wordRange === undefined) 15 | return Promise.resolve(new vscode.Hover('Docstring not found')); 16 | 17 | let currentWord: string; 18 | currentWord = document.lineAt(position.line).text.slice(wordRange.start.character, wordRange.end.character); 19 | const ns = cljParser.getNamespace(document.getText()); 20 | 21 | return cljConnection.sessionForFilename(document.fileName).then(session => { 22 | return nreplClient.info(currentWord, ns, session.id).then(info => { 23 | if (info.doc) { 24 | return Promise.resolve(new vscode.Hover(info.doc)); 25 | } 26 | return Promise.reject(undefined); 27 | }); 28 | }); 29 | } 30 | 31 | } 32 | -------------------------------------------------------------------------------- /src/clojureMain.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from 'vscode'; 2 | 3 | import { CLOJURE_MODE, LANGUAGE } from './clojureMode'; 4 | import { ClojureCompletionItemProvider } from './clojureSuggest'; 5 | import { 6 | clojureEval, clojureEvalAndShowResult, testNamespace, runAllTests, 7 | clearInlineResultDecorationOnMove 8 | } from './clojureEval'; 9 | import { ClojureDefinitionProvider } from './clojureDefinition'; 10 | import { ClojureLanguageConfiguration } from './clojureConfiguration'; 11 | import { ClojureHoverProvider } from './clojureHover'; 12 | import { ClojureSignatureProvider } from './clojureSignature'; 13 | import { JarContentProvider } from './jarContentProvider'; 14 | import { nreplController } from './nreplController'; 15 | import { cljConnection } from './cljConnection'; 16 | import { ClojureRangeFormattingEditProvider, maybeActivateFormatOnSave } from './clojureFormat'; 17 | 18 | import { buildTestProvider } from './testRunner' 19 | 20 | export function activate(context: vscode.ExtensionContext) { 21 | cljConnection.setCljContext(context); 22 | context.subscriptions.push(nreplController); 23 | cljConnection.disconnect(false); 24 | var config = vscode.workspace.getConfiguration('clojureVSCode'); 25 | if (config.autoStartNRepl) { 26 | cljConnection.startNRepl(); 27 | } 28 | 29 | maybeActivateFormatOnSave(); 30 | 31 | const testResultDataProvidier = buildTestProvider(); 32 | 33 | vscode.commands.registerCommand('clojureVSCode.manuallyConnectToNRepl', cljConnection.manuallyConnect); 34 | vscode.commands.registerCommand('clojureVSCode.stopDisconnectNRepl', cljConnection.disconnect); 35 | vscode.commands.registerCommand('clojureVSCode.startNRepl', cljConnection.startNRepl); 36 | 37 | const evaluationResultChannel = vscode.window.createOutputChannel('Evaluation results'); 38 | vscode.commands.registerCommand('clojureVSCode.eval', () => clojureEval(evaluationResultChannel)); 39 | vscode.commands.registerCommand('clojureVSCode.evalAndShowResult', () => clojureEvalAndShowResult(evaluationResultChannel)); 40 | 41 | vscode.commands.registerCommand('clojureVSCode.testNamespace', () => testNamespace(evaluationResultChannel, testResultDataProvidier)); 42 | vscode.commands.registerCommand('clojureVSCode.runAllTests', () => runAllTests(evaluationResultChannel, testResultDataProvidier)); 43 | vscode.window.registerTreeDataProvider('clojure', testResultDataProvidier); 44 | 45 | context.subscriptions.push(vscode.languages.registerDocumentRangeFormattingEditProvider(CLOJURE_MODE, new ClojureRangeFormattingEditProvider())); 46 | 47 | context.subscriptions.push(vscode.languages.registerCompletionItemProvider(CLOJURE_MODE, new ClojureCompletionItemProvider(), '.', '/')); 48 | context.subscriptions.push(vscode.languages.registerDefinitionProvider(CLOJURE_MODE, new ClojureDefinitionProvider())); 49 | context.subscriptions.push(vscode.languages.registerHoverProvider(CLOJURE_MODE, new ClojureHoverProvider())); 50 | context.subscriptions.push(vscode.languages.registerSignatureHelpProvider(CLOJURE_MODE, new ClojureSignatureProvider(), ' ', '\n')); 51 | 52 | vscode.workspace.registerTextDocumentContentProvider('jar', new JarContentProvider()); 53 | vscode.languages.setLanguageConfiguration(LANGUAGE, ClojureLanguageConfiguration); 54 | 55 | // events 56 | vscode.window.onDidChangeTextEditorSelection(event => { 57 | clearInlineResultDecorationOnMove(event); 58 | }, null, context.subscriptions); 59 | } 60 | 61 | export function deactivate() { } 62 | -------------------------------------------------------------------------------- /src/clojureMode.ts: -------------------------------------------------------------------------------- 1 | export const LANGUAGE = 'clojure'; 2 | 3 | export const CLOJURE_MODE = [ 4 | { language: LANGUAGE, scheme: 'file' }, 5 | { language: LANGUAGE, scheme: 'jar' } 6 | ]; 7 | -------------------------------------------------------------------------------- /src/clojureSignature.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from 'vscode'; 2 | 3 | import { cljConnection } from './cljConnection'; 4 | import { cljParser } from './cljParser'; 5 | import { nreplClient } from './nreplClient'; 6 | 7 | const PARAMETER_OPEN = `[`; 8 | const PARAMETER_CLOSE = `]`; 9 | const PARAMETER_REST = `&`; 10 | const SPECIAL_FORM_PARAMETER_REST = `*`; 11 | 12 | const SPECIAL_FORM_CUSTOM_ARGLISTS: Map = new Map([ 13 | [`fn`, `([name? params exprs*] [name? & [params & expr]])`], 14 | [`set!`, `([var-symbol expr] [[. instance-expr instanceFieldName-symbol] expr] [[. Classname-symbol staticFieldName-symbol] expr])`], 15 | [`.`, `([instance instanceMember args*] [Classname instanceMember args*] [instance -instanceField] [Classname staticMethod args*] [. Classname staticField])`], 16 | [`new`, `([Classname args*])`], 17 | ]); 18 | 19 | export class ClojureSignatureProvider implements vscode.SignatureHelpProvider { 20 | 21 | provideSignatureHelp(document: vscode.TextDocument, position: vscode.Position, token: vscode.CancellationToken): vscode.ProviderResult { 22 | if (!cljConnection.isConnected()) 23 | return Promise.reject('No nREPL connected.'); 24 | 25 | const textToGetInfo = document.getText(new vscode.Range(new vscode.Position(0, 0), position)); 26 | const exprInfo = cljParser.getExpressionInfo(textToGetInfo); 27 | if (!exprInfo) 28 | return Promise.reject('No expression found.'); 29 | 30 | const ns = cljParser.getNamespace(document.getText()); 31 | return cljConnection.sessionForFilename(document.fileName).then(session => { 32 | return nreplClient.info(exprInfo.functionName, ns, session.id).then(info => { 33 | if (!info.name) // sometimes info brings just a list of suggestions (example: .MAX_VALUE) 34 | return Promise.reject('No signature info found.'); 35 | 36 | if (!!info['special-form']) 37 | return Promise.resolve(getSpecialFormSignatureHelp(info, exprInfo.parameterPosition)); 38 | 39 | return Promise.resolve(getFunctionSignatureHelp(info, exprInfo.parameterPosition)); 40 | }); 41 | }); 42 | } 43 | } 44 | 45 | function getSpecialFormSignatureHelp(info: any, parameterPosition: number): vscode.SignatureHelp { 46 | const signatureLabel = `*special form* ${info.name}`; 47 | 48 | let arglists = SPECIAL_FORM_CUSTOM_ARGLISTS.get(info.name); 49 | if (!arglists) { 50 | const forms: string = info['forms-str']; 51 | const [functionName, ...parameters] = forms.substring(3, forms.length - 1).split(' '); 52 | arglists = `([${parameters.join(' ')}])`; 53 | } 54 | 55 | return getSignatureHelp(signatureLabel, info.doc, arglists, parameterPosition); 56 | } 57 | 58 | function getFunctionSignatureHelp(info: any, parameterPosition: number): vscode.SignatureHelp | undefined { 59 | const arglists = info['arglists-str']; 60 | if (!arglists) 61 | return; 62 | 63 | const signatureLabel = `${info.ns}/${info.name}`; 64 | return getSignatureHelp(signatureLabel, info.doc, arglists, parameterPosition); 65 | } 66 | 67 | function getSignatureHelp(signatureLabel: string, signatureDoc: string, arglists: string, parameterPosition: number): vscode.SignatureHelp { 68 | const signatures = getSignatureInfos(signatureLabel, signatureDoc, arglists); 69 | signatures.sort((sig1, sig2) => sig1.parameters.length - sig2.parameters.length); 70 | 71 | let activeSignature = signatures.findIndex(signature => signature.parameters.length >= parameterPosition + 1); 72 | if (activeSignature === -1) { 73 | activeSignature = signatures.findIndex(signature => signature.parameters.some(param => { 74 | if (param.label instanceof String) param.label.startsWith(PARAMETER_REST)})); 75 | 76 | if (activeSignature === -1) 77 | activeSignature = signatures.findIndex(signature => { 78 | const label = signature.parameters.slice(-1)[0].label; 79 | if (label instanceof String) label.endsWith(SPECIAL_FORM_PARAMETER_REST); 80 | }); 81 | 82 | if (activeSignature !== -1) 83 | parameterPosition = signatures[activeSignature].parameters.length - 1; 84 | } 85 | if (activeSignature === -1) 86 | activeSignature = 0; 87 | 88 | const signatureHelp = new vscode.SignatureHelp(); 89 | signatureHelp.signatures = signatures; 90 | signatureHelp.activeParameter = parameterPosition; 91 | signatureHelp.activeSignature = activeSignature; 92 | 93 | return signatureHelp; 94 | } 95 | 96 | function getSignatureInfos(signatureLabel: string, signatureDoc: string, arglists: string): vscode.SignatureInformation[] { 97 | const sigParamStarts: number[] = []; 98 | const sigParamStops: number[] = []; 99 | let nestingLevel = 0; 100 | for (let i = 0; i < arglists.length; i++) { 101 | if (arglists[i] === PARAMETER_OPEN) { 102 | if (nestingLevel === 0) 103 | sigParamStarts.push(i); 104 | nestingLevel++; 105 | } 106 | if (arglists[i] === PARAMETER_CLOSE) { 107 | nestingLevel--; 108 | if (nestingLevel === 0) 109 | sigParamStops.push(i); 110 | } 111 | } 112 | 113 | return sigParamStarts 114 | .map((sigParamStart, index) => arglists.substring(sigParamStart, sigParamStops[index] + 1)) 115 | .map(signatureParameter => { 116 | const parameterInfos = getParameterInfos(signatureParameter); 117 | const sigInfo = new vscode.SignatureInformation(`${signatureLabel} [${parameterInfos.map(pi => pi.label).join(`\n`)}]`); 118 | sigInfo.documentation = signatureDoc; 119 | sigInfo.parameters = parameterInfos; 120 | return sigInfo; 121 | }); 122 | } 123 | 124 | function getParameterInfos(signatureParameter: string): vscode.ParameterInformation[] { 125 | signatureParameter = signatureParameter.substring(1, signatureParameter.length - 1); // removing external brackets 126 | const paramStarts: number[] = []; 127 | const paramStops: number[] = []; 128 | let insideParameter = false; 129 | let bracketsNestingLevel = 0; 130 | for (let i = 0; i < signatureParameter.length; i++) { 131 | const char = signatureParameter[i]; 132 | 133 | if (!insideParameter) { 134 | insideParameter = true; 135 | paramStarts.push(i); 136 | if (char === PARAMETER_OPEN) 137 | bracketsNestingLevel++; 138 | if (char === PARAMETER_REST) 139 | break; 140 | } else { 141 | if (char === PARAMETER_OPEN) 142 | bracketsNestingLevel++; 143 | if (char === PARAMETER_CLOSE) 144 | bracketsNestingLevel--; 145 | if (char === PARAMETER_CLOSE && bracketsNestingLevel === 0) { 146 | paramStops.push(i); 147 | insideParameter = false; 148 | } 149 | if (cljParser.R_CLJ_WHITE_SPACE.test(char) && bracketsNestingLevel === 0) { 150 | paramStops.push(i); 151 | insideParameter = false; 152 | } 153 | } 154 | } 155 | paramStops.push(signatureParameter.length); 156 | 157 | return paramStarts 158 | .map((paramStart, index) => signatureParameter.substring(paramStart, paramStops[index] + 1)) 159 | .map(parameter => new vscode.ParameterInformation(parameter)); 160 | } 161 | -------------------------------------------------------------------------------- /src/clojureSuggest.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from 'vscode'; 2 | import { cljConnection } from './cljConnection'; 3 | import { cljParser } from './cljParser'; 4 | import { nreplClient } from './nreplClient'; 5 | 6 | const mappings: { 7 | [key: string]: vscode.CompletionItemKind 8 | } = { 9 | 'nil': vscode.CompletionItemKind.Value, 10 | 'macro': vscode.CompletionItemKind.Value, 11 | 'class': vscode.CompletionItemKind.Class, 12 | 'keyword': vscode.CompletionItemKind.Keyword, 13 | 'namespace': vscode.CompletionItemKind.Module, 14 | 'function': vscode.CompletionItemKind.Function, 15 | 'special-form': vscode.CompletionItemKind.Keyword, 16 | 'var': vscode.CompletionItemKind.Variable, 17 | 'method': vscode.CompletionItemKind.Method, 18 | } 19 | 20 | export class ClojureCompletionItemProvider implements vscode.CompletionItemProvider { 21 | 22 | public provideCompletionItems(document: vscode.TextDocument, position: vscode.Position, token: vscode.CancellationToken): Thenable { 23 | if (!cljConnection.isConnected()) 24 | return Promise.reject('No nREPL connected.'); 25 | 26 | const wordRange = document.getWordRangeAtPosition(position); 27 | if (!wordRange) 28 | return Promise.reject('No word selected.'); 29 | 30 | const line = document.lineAt(position.line), 31 | currentWord = line.text.slice(wordRange.start.character, wordRange.end.character), 32 | ns = cljParser.getNamespace(document.getText()); 33 | 34 | let buildInsertText = (suggestion: string) => { 35 | return suggestion[0] === '.' ? suggestion.slice(1) : suggestion; 36 | } 37 | 38 | return nreplClient.complete(currentWord, ns).then(completions => { 39 | if (!('completions' in completions)) 40 | return Promise.reject(undefined); 41 | 42 | let suggestions = completions.completions.map((element: any) => ({ 43 | label: element.candidate, 44 | kind: mappings[element.type] || vscode.CompletionItemKind.Text, 45 | insertText: buildInsertText(element.candidate) 46 | })); 47 | let completionList: vscode.CompletionList = new vscode.CompletionList(suggestions, false); 48 | return Promise.resolve(completionList); 49 | }); 50 | } 51 | 52 | public resolveCompletionItem(item: vscode.CompletionItem, token: vscode.CancellationToken): Thenable { 53 | const editor = vscode.window.activeTextEditor 54 | if (!editor) { 55 | return Promise.reject("No active editor"); 56 | } 57 | let document = editor.document; 58 | let ns = cljParser.getNamespace(document.getText()); 59 | return nreplClient.info(item.label, ns).then(info => { 60 | item.documentation = info.doc; 61 | return Promise.resolve(item); 62 | }); 63 | } 64 | 65 | } 66 | -------------------------------------------------------------------------------- /src/jarContentProvider.ts: -------------------------------------------------------------------------------- 1 | import * as fs from 'fs'; 2 | import * as os from 'os'; 3 | import * as vscode from 'vscode'; 4 | import * as JSZip from 'jszip'; 5 | 6 | export class JarContentProvider implements vscode.TextDocumentContentProvider { 7 | 8 | public provideTextDocumentContent(uri: vscode.Uri, token: vscode.CancellationToken): Thenable { 9 | return new Promise((resolve, reject) => { 10 | let rawPath = uri.path; 11 | let pathToFileInJar = rawPath.slice(rawPath.search('!/') + 2); 12 | let pathToJar = rawPath.slice('file:'.length); 13 | pathToJar = pathToJar.slice(0, pathToJar.search('!')); 14 | 15 | if (os.platform() === 'win32') { 16 | pathToJar = pathToJar.replace(/\//g, '\\').slice(1); 17 | } 18 | 19 | fs.readFile(pathToJar, (err, data) => { 20 | let zip = new JSZip(); 21 | zip.loadAsync(data).then((new_zip) => { 22 | new_zip.file(pathToFileInJar)?.async("text").then((value) => { 23 | resolve(value); 24 | }) 25 | }) 26 | }) 27 | }); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/nreplClient.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from 'vscode'; 2 | import * as net from 'net'; 3 | import { Buffer } from 'buffer'; 4 | 5 | import * as bencodeUtil from './bencodeUtil'; 6 | import { cljConnection, CljConnectionInformation } from './cljConnection'; 7 | 8 | interface nREPLCompleteMessage { 9 | op: string; 10 | symbol: string; 11 | ns?: string 12 | } 13 | 14 | interface nREPLInfoMessage { 15 | op: string; 16 | symbol: string; 17 | ns: string; 18 | session?: string; 19 | } 20 | 21 | type TestMessage = { 22 | op: "test" | "test-all" | "test-stacktrace" | "retest" 23 | ns?: string, 24 | 'load?'?: any 25 | } 26 | 27 | interface nREPLEvalMessage { 28 | op: string; 29 | file: string; 30 | 'file-path'?: string; 31 | session: string; 32 | } 33 | 34 | interface nREPLSingleEvalMessage { 35 | op: string; 36 | code: string; 37 | session: string; 38 | } 39 | 40 | interface nREPLStacktraceMessage { 41 | op: string; 42 | session: string; 43 | } 44 | 45 | interface nREPLCloneMessage { 46 | op: string; 47 | session?: string; 48 | } 49 | 50 | interface nREPLCloseMessage { 51 | op: string; 52 | session?: string; 53 | } 54 | 55 | const complete = (symbol: string, ns: string): Promise => { 56 | const msg: nREPLCompleteMessage = { op: 'complete', symbol, ns }; 57 | return send(msg).then(respObjs => respObjs[0]); 58 | }; 59 | 60 | const info = (symbol: string, ns: string, session?: string): Promise => { 61 | const msg: nREPLInfoMessage = { op: 'info', symbol, ns, session }; 62 | return send(msg).then(respObjs => respObjs[0]); 63 | }; 64 | 65 | const evaluate = (code: string, session?: string): Promise => clone(session).then((session_id) => { 66 | const msg: nREPLSingleEvalMessage = { op: 'eval', code: code, session: session_id }; 67 | return send(msg); 68 | }); 69 | 70 | const evaluateFile = (code: string, filepath: string, session?: string): Promise => clone(session).then((session_id) => { 71 | const msg: nREPLEvalMessage = { op: 'load-file', file: code, 'file-path': filepath, session: session_id }; 72 | return send(msg); 73 | }); 74 | 75 | const stacktrace = (session: string): Promise => send({ op: 'stacktrace', session: session }); 76 | 77 | const runTests = function (namespace: string | undefined): Promise { 78 | const message: TestMessage = { 79 | op: (namespace ? "test" : "test-all"), 80 | ns: namespace, 81 | 'load?': 1 82 | } 83 | return send(message); 84 | } 85 | 86 | 87 | const clone = (session?: string): Promise => send({ op: 'clone', session: session }).then(respObjs => respObjs[0]['new-session']); 88 | 89 | const test = (connectionInfo: CljConnectionInformation): Promise => { 90 | return send({ op: 'clone' }, connectionInfo) 91 | .then(respObjs => respObjs[0]) 92 | .then(response => { 93 | if (!('new-session' in response)) 94 | return Promise.reject(false); 95 | else { 96 | return Promise.resolve([]); 97 | } 98 | }); 99 | }; 100 | 101 | const close = (session?: string): Promise => send({ op: 'close', session: session }); 102 | 103 | const listSessions = (): Promise<[string]> => { 104 | return send({ op: 'ls-sessions' }).then(respObjs => { 105 | const response = respObjs[0]; 106 | if (response.status[0] == "done") { 107 | return Promise.resolve(response.sessions); 108 | } 109 | }); 110 | } 111 | 112 | type Message = TestMessage | nREPLCompleteMessage | nREPLInfoMessage | nREPLEvalMessage | nREPLStacktraceMessage | nREPLCloneMessage | nREPLCloseMessage | nREPLSingleEvalMessage; 113 | 114 | const send = (msg: Message, connection?: CljConnectionInformation): Promise => { 115 | 116 | console.log("nREPL: Sending op", msg); 117 | 118 | return new Promise((resolve, reject) => { 119 | connection = connection || cljConnection.getConnection(); 120 | 121 | if (!connection) 122 | return reject('No connection found.'); 123 | 124 | const client = net.createConnection(connection.port, connection.host); 125 | Object.keys(msg).forEach(key => (msg as any)[key] === undefined && delete (msg as any)[key]); 126 | client.write(bencodeUtil.encode(msg), 'binary'); 127 | 128 | client.on('error', error => { 129 | client.end(); 130 | client.removeAllListeners(); 131 | if ((error as any)['code'] === 'ECONNREFUSED') { 132 | vscode.window.showErrorMessage('Connection refused.'); 133 | cljConnection.disconnect(); 134 | } 135 | reject(error); 136 | }); 137 | 138 | let nreplResp = Buffer.from(''); 139 | const respObjects: any[] = []; 140 | client.on('data', data => { 141 | nreplResp = Buffer.concat([nreplResp, data]); 142 | const { decodedObjects, rest, isDone } = bencodeUtil.decodeBuffer(nreplResp); 143 | nreplResp = rest; 144 | respObjects.push(...decodedObjects); 145 | 146 | if (isDone) { 147 | client.end(); 148 | client.removeAllListeners(); 149 | resolve(respObjects); 150 | } 151 | }); 152 | }); 153 | }; 154 | 155 | 156 | export const nreplClient = { 157 | complete, 158 | info, 159 | evaluate, 160 | evaluateFile, 161 | stacktrace, 162 | clone, 163 | test, 164 | runTests, 165 | close, 166 | listSessions 167 | }; 168 | -------------------------------------------------------------------------------- /src/nreplController.ts: -------------------------------------------------------------------------------- 1 | import 'process'; 2 | import * as os from 'os'; 3 | import * as path from 'path'; 4 | import * as vscode from 'vscode'; 5 | import * as spawn from 'cross-spawn'; 6 | import { ChildProcess, exec } from 'child_process'; 7 | 8 | import { CljConnectionInformation } from './cljConnection'; 9 | 10 | const config = vscode.workspace.getConfiguration('clojureVSCode'); 11 | 12 | const LEIN_ARGS: string[] = [ 13 | 'update-in', 14 | ':dependencies', 15 | 'conj', 16 | `[cljfmt "${config.cljfmtVersion}"]`, 17 | '--', 18 | 'update-in', 19 | ':plugins', 20 | 'conj', 21 | `[cider/cider-nrepl "${config.ciderNReplVersion}"]`, 22 | '--', 23 | 'repl', 24 | ':headless' 25 | ]; 26 | 27 | const R_NREPL_CONNECTION_INFO = /nrepl:\/\/(.*?:.*?(?=[\n\r]))/; 28 | 29 | let nreplProcess: ChildProcess | null 30 | 31 | const isStarted = () => !!nreplProcess; 32 | 33 | // Create a channel in the Output window so that the user 34 | // can view output from the nREPL session. 35 | const nreplChannel = vscode.window.createOutputChannel('nREPL'); 36 | 37 | const start = (): Promise => { 38 | if (isStarted()) 39 | return Promise.reject({ nreplError: 'nREPL already started.' }); 40 | 41 | // Clear any output from previous nREPL sessions to help users focus 42 | // on the current session only. 43 | nreplChannel.clear(); 44 | 45 | return new Promise((resolve, reject) => { 46 | 47 | nreplProcess = spawn('lein', LEIN_ARGS, { 48 | cwd: getCwd(), // see the `getCwd` function documentation! 49 | detached: !(os.platform() === 'win32') 50 | }); 51 | 52 | nreplProcess.stdout.addListener('data', data => { 53 | const nreplConnectionMatch = data.toString().match(R_NREPL_CONNECTION_INFO); 54 | // Send any stdout messages to the output channel 55 | nreplChannel.append(data.toString()); 56 | 57 | if (nreplConnectionMatch && nreplConnectionMatch[1]) { 58 | const [host, port] = nreplConnectionMatch[1].split(':'); 59 | return resolve({ host, port: Number.parseInt(port) }); 60 | } 61 | }); 62 | 63 | nreplProcess.stderr.on('data', data => { 64 | // Send any stderr messages to the output channel 65 | nreplChannel.append(data.toString()); 66 | }); 67 | 68 | nreplProcess.on('exit', (code) => { 69 | // nREPL process has exited before we were able to read a host / port. 70 | const message = `nREPL exited with code ${code}` 71 | nreplChannel.appendLine(message); 72 | // Bring the output channel to the foreground so that the user can 73 | // use the output to debug the problem. 74 | nreplChannel.show(); 75 | return reject({ nreplError: message}); 76 | }); 77 | }); 78 | }; 79 | 80 | const stop = () => { 81 | if (nreplProcess) { 82 | // Workaround http://azimi.me/2014/12/31/kill-child_process-node-js.html 83 | nreplProcess.removeAllListeners(); 84 | 85 | try { 86 | // Killing the process will throw an error `kill ESRCH` this method 87 | // is invoked after the nREPL process has exited. This happens when 88 | // we try to gracefully clean up after spawning the nREPL fails. 89 | // We wrap the killing code in `try/catch` to handle this. 90 | if(os.platform() === 'win32'){ 91 | exec('taskkill /pid ' + nreplProcess.pid + ' /T /F') 92 | } 93 | else { 94 | process.kill(-nreplProcess.pid); 95 | } 96 | } catch (exception) { 97 | console.error("Error cleaning up nREPL process", exception); 98 | } 99 | 100 | nreplProcess = null; 101 | } 102 | }; 103 | 104 | const dispose = stop; 105 | 106 | /** 107 | * It's important to set the current working directory parameter when spawning 108 | * the nREPL process. Without the parameter been set, it can take quite a long 109 | * period of time for the nREPL to start accepting commands. This most likely 110 | * the result of one of the plugins we use doing something with the file 111 | * system (first time I've seen it, I was surprised why VSCode and Java 112 | * constantly asking for the permissions to access folders in my home directory). 113 | */ 114 | const getCwd = () => { 115 | let cwd = vscode.workspace.rootPath; 116 | 117 | if (cwd) return cwd; 118 | 119 | // Try to get folder name from the active editor. 120 | const document = vscode.window.activeTextEditor?.document; 121 | 122 | if (!document) return; 123 | 124 | return path.dirname(document.fileName); 125 | } 126 | 127 | export const nreplController = { 128 | start, 129 | stop, 130 | isStarted, 131 | dispose, 132 | }; 133 | -------------------------------------------------------------------------------- /src/testRunner.ts: -------------------------------------------------------------------------------- 1 | import { TreeDataProvider, EventEmitter, Event, TreeItemCollapsibleState, TreeItem, ProviderResult } from 'vscode'; 2 | 3 | // A map from namespace => var => boolean 4 | // true if the running the test was successful. 5 | type Results = { 6 | [key: string]: { 7 | [key: string]: boolean 8 | } 9 | } 10 | 11 | type NamespaceNode = { 12 | type: 'ns' 13 | ns: string 14 | } 15 | 16 | type VarNode = { 17 | type: 'var' 18 | varName: string 19 | nsName: string 20 | } 21 | 22 | // The test result tree-view has 2 type of node: 23 | // Namespace nodes and var nodes. 24 | // The (root) contains NamespaceNodes, which have VarNodes as children. 25 | type TestNode = NamespaceNode | VarNode 26 | 27 | export interface TestListener { 28 | onTestResult(ns: string, varName: string, success: boolean): void; 29 | } 30 | 31 | class ClojureTestDataProvider implements TreeDataProvider, TestListener { 32 | 33 | onTestResult(ns: string, varName: string, success: boolean): void { 34 | 35 | console.log(ns, varName, success); 36 | 37 | // Make a copy of result with the new result assoc'ed in. 38 | this.results = { 39 | ...this.results, 40 | [ns]: { 41 | ...this.results[ns], 42 | [varName]: success 43 | } 44 | } 45 | 46 | this.testsChanged.fire(); // Trigger the UI to update. 47 | } 48 | 49 | private testsChanged: EventEmitter = new EventEmitter(); 50 | readonly onDidChangeTreeData: Event = this.testsChanged.event; 51 | 52 | private results: Results = {} 53 | 54 | getNamespaceItem(element: NamespaceNode): TreeItem { 55 | return { 56 | label: element.ns, 57 | collapsibleState: TreeItemCollapsibleState.Expanded 58 | }; 59 | } 60 | 61 | getVarItem(element: VarNode): TreeItem { 62 | const passed: boolean = this.results[element.nsName][element.varName]; 63 | return { 64 | label: (passed ? "✅ " : "❌ ") + element.varName, 65 | collapsibleState: TreeItemCollapsibleState.None 66 | }; 67 | } 68 | 69 | getTreeItem(element: TestNode): TreeItem | Thenable { 70 | switch (element.type) { 71 | case 'ns': return this.getNamespaceItem(element); 72 | case 'var': return this.getVarItem(element); 73 | } 74 | } 75 | 76 | getChildren(element?: TestNode): ProviderResult { 77 | 78 | if (!element) { 79 | return Object.keys(this.results).map((ns) => { 80 | const node: NamespaceNode = { 81 | type: 'ns', 82 | ns: ns 83 | }; 84 | return node; 85 | }); 86 | 87 | } 88 | 89 | switch (element.type) { 90 | case 'ns': { 91 | const vars = Object.keys(this.results[element.ns]); 92 | 93 | return vars.map((varName) => { 94 | const node: VarNode = { 95 | type: 'var', 96 | nsName: element.ns, 97 | varName: varName 98 | }; 99 | return node; 100 | }); 101 | } 102 | } 103 | return null; 104 | } 105 | } 106 | 107 | export const buildTestProvider = function (): ClojureTestDataProvider { 108 | return new ClojureTestDataProvider(); 109 | }; 110 | -------------------------------------------------------------------------------- /test/bencodeUtil.test.ts: -------------------------------------------------------------------------------- 1 | import * as assert from 'assert'; 2 | import { decodeBuffer } from '../src/bencodeUtil'; 3 | 4 | interface DecodedResult { 5 | decodedObjects: any[]; 6 | rest: Buffer; 7 | isDone: boolean; 8 | } 9 | 10 | function decodeMessages(messages: string[]): DecodedResult { 11 | let nreplResp = Buffer.from(''), 12 | isDone = false; 13 | const respObjects: any[] = []; 14 | 15 | messages.forEach(item => { 16 | nreplResp = Buffer.concat([nreplResp, Buffer.from(item)]); 17 | const response = decodeBuffer(nreplResp); 18 | nreplResp = response.rest; 19 | isDone = response.isDone; 20 | respObjects.push(...response.decodedObjects); 21 | }); 22 | 23 | return { decodedObjects: respObjects, rest: nreplResp, isDone: isDone }; 24 | } 25 | 26 | suite('bencodeUtil.decodeBuffer', function () { 27 | test('create new session', () => { 28 | const input = Buffer.from( 29 | 'd11:new-session36:58d1e5dc-c717-4864-bf49-e7750ced6f28' 30 | + '7:session36:7fcd096b-4ee4-4142-bb6b-6fc09e5c41606:statusl4:doneee'), 31 | expected = { 32 | 'new-session': '58d1e5dc-c717-4864-bf49-e7750ced6f28', 33 | 'session': '7fcd096b-4ee4-4142-bb6b-6fc09e5c4160', 34 | 'status': ['done'] 35 | }, 36 | result = decodeBuffer(input); 37 | assert.ok(result.isDone); 38 | assert.deepEqual(result.decodedObjects, [expected]); 39 | assert.equal(result.rest.length, 0); 40 | }); 41 | 42 | test('close session', () => { 43 | const input = Buffer.from( 44 | 'd7:session36:9968ec29-b87d-4e1f-8444-076280357dd36:statusl4:done14:session-closedee'), 45 | expected = { 46 | 'session': '9968ec29-b87d-4e1f-8444-076280357dd3', 47 | 'status': ['done', 'session-closed'] 48 | }, 49 | result = decodeBuffer(input); 50 | assert.ok(result.isDone); 51 | assert.deepEqual(result.decodedObjects, [expected]); 52 | assert.equal(result.rest.length, 0); 53 | }); 54 | 55 | test('completion candidates', () => { 56 | const input = Buffer.from( 57 | 'd11:completionsld9:candidate5:slurp2:ns12:clojure.core4:type8:functioned' 58 | + '9:candidate14:slingshot.test4:type9:namespaceed9:candidate' 59 | + '17:slingshot.support4:type9:namespaceed9:candidate19:slingshot.slingshot' 60 | + '4:type9:namespaceee7:session36:4d32206b-5161-40d2-a4e7-d1be6ec777756:statusl4:doneee'), 61 | expected = { 62 | 'session': '4d32206b-5161-40d2-a4e7-d1be6ec77775', 63 | 'completions': [ 64 | { 65 | 'candidate': 'slurp', 66 | 'ns': 'clojure.core', 67 | 'type': 'function' 68 | }, 69 | { 70 | 'candidate': 'slingshot.test', 71 | 'type': 'namespace' 72 | }, 73 | { 74 | 'candidate': 'slingshot.support', 75 | 'type': 'namespace' 76 | }, 77 | { 78 | 'candidate': 'slingshot.slingshot', 79 | 'type': 'namespace' 80 | }, 81 | ], 82 | 'status': ['done'] 83 | }, 84 | result = decodeBuffer(input); 85 | assert.ok(result.isDone); 86 | assert.deepEqual(result.decodedObjects, [expected]); 87 | assert.equal(result.rest.length, 0); 88 | }); 89 | 90 | test('eval simple printing expression', () => { 91 | const messages = [ 92 | 'd3:out7:"test"\n7:session36:9968ec29-b87d-4e1f-8444-076280357dd3e', 93 | 'd7:session36:9968ec29-b87d-4e1f-8444-076280357dd35:value3:niled' 94 | + '7:session36:9968ec29-b87d-4e1f-8444-076280357dd36:statusl4:doneee' 95 | + '18:changed-namespacesd13:cheshire.cored7:aliasesd7:factory16:cheshire.factory' 96 | + '3:gen17:cheshire.generate7:gen-seq21:cheshire.generate-seq5:parse14:cheshire.parsee' 97 | + '7:internsd11:*generator*de9:*opt-map*de13:copy-arglistsd8:arglists11:([dst' 98 | ], 99 | expectedOut = { 100 | 'session': '9968ec29-b87d-4e1f-8444-076280357dd3', 101 | 'out': '"test"\n', 102 | }, 103 | result = decodeMessages(messages); 104 | assert.equal(result.decodedObjects.length, 3); 105 | assert.deepEqual(result.decodedObjects[0], expectedOut); 106 | assert.ok(result.isDone); 107 | assert.notEqual(result.rest.length, 0); 108 | }); 109 | 110 | test('eval expression with result divided by multiple messages', () => { 111 | const messages = [ 112 | 'd7:session36:9968ec29-b87d-4e1f-8444-076280357dd35:value184:' 113 | + 'Lorem ipsum dolor sit amet, consectetur adipiscing e', 114 | 'lit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.', 115 | ' Ipsum dolor sit amet consectetur adipiscing elit ut aliquam.e' 116 | + 'd7:session36:9968ec29-b87d-4e1f-8444-076280357dd36:statusl4:doneee' 117 | ], 118 | expectedWithValue = { 119 | 'session': '9968ec29-b87d-4e1f-8444-076280357dd3', 120 | 'value': 'Lorem ipsum dolor sit amet, consectetur adipiscing e' 121 | + 'lit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.' 122 | + ' Ipsum dolor sit amet consectetur adipiscing elit ut aliquam.' 123 | }, 124 | expectedWithDone = { 125 | 'session': '9968ec29-b87d-4e1f-8444-076280357dd3', 126 | 'status': ['done'] 127 | }, 128 | result = decodeMessages(messages); 129 | assert.equal(result.decodedObjects.length, 2); 130 | assert.deepEqual(result.decodedObjects[0], expectedWithValue); 131 | assert.deepEqual(result.decodedObjects[1], expectedWithDone); 132 | assert.ok(result.isDone); 133 | assert.equal(result.rest.length, 0); 134 | }); 135 | }); 136 | -------------------------------------------------------------------------------- /test/cljParser.test.ts: -------------------------------------------------------------------------------- 1 | import * as assert from 'assert'; 2 | import * as path from 'path'; 3 | import * as vscode from 'vscode'; 4 | import { cljParser } from '../src/cljParser'; 5 | import { setLongTimeout } from './utils'; 6 | 7 | const testFolderLocation = '/../../test/documents/'; 8 | 9 | suite('cljParser.getNamespace', function() { 10 | let cases = [ 11 | ['user', ''], 12 | ['foo', '(ns foo)'], 13 | ['foo', '\n(ns foo)'], 14 | ['foo', '\t(ns foo)'], 15 | ['foo', '\t(ns\tfoo)'], 16 | ['foo-bar', '(ns foo-bar)'], 17 | ['bar', '(ns bar)'], 18 | ['baz', '(ns baz "docstring")'], 19 | ['qux', `(ns qux 20 | "docstring")`], 21 | ['foo.bar', '(ns foo.bar)'], 22 | ['foo.bar-baz', '(ns foo.bar-baz)'], 23 | ['foo.bar', `(ns foo.bar 24 | (:refer-clojure :exclude [ancestors printf]) 25 | (:require (clojure.contrib sql combinatorics)) 26 | (:use (my.lib this that)) 27 | (:import (java.util Date Timer Random) 28 | (java.sql Connection Statement)))`], 29 | ['bar', '(in-ns \'bar)'], 30 | ]; 31 | for (let [want, input] of cases) { 32 | test(`getNamespace("${input}") should be "${want}"`, () => { 33 | assert.equal(cljParser.getNamespace(input), want); 34 | }); 35 | } 36 | }); 37 | 38 | suite('cljParser.getCurrentBlock', function() { 39 | // title, line, character, expected 40 | setLongTimeout(this); 41 | let cases: [string, number, number, string | undefined][] = [ 42 | ['Position on the same line', 16, 9, '(prn "test")'], 43 | ['Position at the middle of multiline block', 22, 5, 44 | '(->> numbers\n' + 45 | ' (map inc)\n' + 46 | ' (prn))'], 47 | ['Ignore inline clj comments', 19, 16, 48 | '(let [numbers [1 2 3]\n' + 49 | ' VAL (atom {:some "DATA"})]\n' + 50 | ' ; missing left bracket prn "hided text") in comment\n' + 51 | ' (prn [@VAL])\n' + 52 | ' (->> numbers\n' + 53 | ' (map inc)\n' + 54 | ' (prn)))'], 55 | ['Comment form will be evaluated', 27, 14, '(prn "COMMENT")'], 56 | ['Eval only outside bracket from right side', 23, 11, '(prn)'], 57 | ['Eval only outside bracket from left side', 28, 5, 58 | '(comp #(str % "!") name)'], 59 | ['Eval only round bracket block', 20, 12, '(prn [@VAL])'], 60 | ['Eval when only inside of the block', 24, 0, undefined], 61 | ['Begin of file', 0, 0, 62 | '(ns user\n' + 63 | ' (:require [clojure.tools.namespace.repl :refer [set-refresh-dirs]]\n' + 64 | ' [reloaded.repl :refer [system stop go reset]]\n' + 65 | ' [myproject.config :refer [config]]\n' + 66 | ' [myproject.core :refer [new-system]]))'], 67 | ['End of file', 37, 0, undefined] 68 | ]; 69 | testBlockSelection('Eval current block', cljParser.getCurrentBlock, 'evalBlock.clj', cases); 70 | }); 71 | 72 | suite('cljParser.getOuterBlock', function() { 73 | // title, line, character, expected 74 | setLongTimeout(this); 75 | let cases: [string, number, number, string | undefined][] = [ 76 | ['Get outermost block of function definition', 11, 20, 77 | '(defn new-system-dev\n' + 78 | ' []\n' + 79 | ' (let [_ 1]\n' + 80 | ' (new-system (config))))'], 81 | ['Outer block from outside left bracket', 8, 0, 82 | '(defn new-system-dev\n' + 83 | ' []\n' + 84 | ' (let [_ 1]\n' + 85 | ' (new-system (config))))'], 86 | ['Outer block from outside right bracket', 28, 38, 87 | '(comment\n' + 88 | ' (do\n' + 89 | ' #_(prn "COMMENT")\n' + 90 | ' ((comp #(str % "!") name) :test)))'], 91 | ['Outer block not found', 24, 0, undefined], 92 | ['Begin of file', 0, 0, 93 | '(ns user\n' + 94 | ' (:require [clojure.tools.namespace.repl :refer [set-refresh-dirs]]\n' + 95 | ' [reloaded.repl :refer [system stop go reset]]\n' + 96 | ' [myproject.config :refer [config]]\n' + 97 | ' [myproject.core :refer [new-system]]))'], 98 | ['End of file', 37, 0, undefined], 99 | ]; 100 | testBlockSelection('Eval outer block', cljParser.getOuterBlock, 'evalBlock.clj', cases); 101 | }); 102 | 103 | function testBlockSelection( 104 | title: string, 105 | getBlockFn: (editor: vscode.TextEditor) => vscode.Selection | undefined, 106 | fileName: string, 107 | cases: [string, number, number, string | undefined][]) { 108 | 109 | test(title, async () => { 110 | const editor = await getEditor(fileName); 111 | 112 | for (let [title, line, character, expected] of cases) { 113 | const currentPosition = new vscode.Position(line, character); 114 | editor.selection = new vscode.Selection(currentPosition, currentPosition); 115 | const blockSelection = getBlockFn(editor), 116 | text = blockSelection ? editor.document.getText(blockSelection) : blockSelection; 117 | assert.equal(text, expected, title) 118 | }; 119 | vscode.commands.executeCommand('workbench.action.closeActiveEditor'); 120 | }); 121 | }; 122 | 123 | function sleep(ms: number): Promise { 124 | return new Promise((resolve) => { 125 | setTimeout(resolve, ms); 126 | }); 127 | }; 128 | 129 | async function getEditor(fileName: string): Promise { 130 | const configuration = vscode.workspace.getConfiguration(); 131 | configuration.update("clojureVSCode.autoStartNRepl", false); 132 | const uri = vscode.Uri.file(path.join(__dirname + testFolderLocation + fileName)), 133 | document = await vscode.workspace.openTextDocument(uri), 134 | editor = await vscode.window.showTextDocument(document); 135 | await sleep(1500); 136 | return editor; 137 | }; 138 | -------------------------------------------------------------------------------- /test/documents/evalBlock.clj: -------------------------------------------------------------------------------- 1 | (ns user 2 | (:require [clojure.tools.namespace.repl :refer [set-refresh-dirs]] 3 | [reloaded.repl :refer [system stop go reset]] 4 | [myproject.config :refer [config]] 5 | [myproject.core :refer [new-system]])) 6 | 7 | (set-refresh-dirs "dev" "src" "test") 8 | 9 | (defn new-system-dev 10 | [] 11 | (let [_ 1] 12 | (new-system (config)))) 13 | 14 | (reloaded.repl/set-init! #(new-system-dev)) 15 | 16 | (comment 17 | (prn "test") ; block in comment (prn "comment") some extra text; 18 | (let [numbers [1 2 3] 19 | VAL (atom {:some "DATA"})] 20 | ; missing left bracket prn "hided text") in comment 21 | (prn [@VAL]) 22 | (->> numbers 23 | (map inc) 24 | (prn)))) 25 | 26 | (comment 27 | (do 28 | #_(prn "COMMENT") 29 | ((comp #(str % "!") name) :test))) 30 | 31 | (comment 32 | (go) 33 | (reset) 34 | (stop) 35 | (keys system)) 36 | 37 | (prn "THE WHOLE FILE HAS BEEN EVALUATED") 38 | -------------------------------------------------------------------------------- /test/extension.test.ts: -------------------------------------------------------------------------------- 1 | // 2 | // Note: This example test is leveraging the Mocha test framework. 3 | // Please refer to their documentation on https://mochajs.org/ for help. 4 | // 5 | 6 | // The module 'assert' provides assertion methods from node 7 | import * as assert from 'assert'; 8 | 9 | // You can import and use all API from the 'vscode' module 10 | // as well as import your extension to test it 11 | import * as vscode from 'vscode'; 12 | import * as myExtension from '../src/clojureMain'; 13 | 14 | // Defines a Mocha test suite to group tests of similar kind together 15 | suite("Extension Tests", () => { 16 | 17 | // Defines a Mocha unit test 18 | test("Something 1", () => { 19 | assert.equal(-1, [1, 2, 3].indexOf(5)); 20 | assert.equal(-1, [1, 2, 3].indexOf(0)); 21 | }); 22 | }); -------------------------------------------------------------------------------- /test/index.ts: -------------------------------------------------------------------------------- 1 | // 2 | // PLEASE DO NOT MODIFY / DELETE UNLESS YOU KNOW WHAT YOU ARE DOING 3 | // 4 | // This file is providing the test runner to use when running extension tests. 5 | // By default the test runner in use is Mocha based. 6 | // 7 | // You can provide your own test runner if you want to override it by exporting 8 | // a function run(testRoot: string, clb: (error:Error) => void) that the extension 9 | // host can call to run the tests. The test runner is expected to use console.log 10 | // to report the results back to the caller. When the tests are finished, return 11 | // a possible error to the callback or null if none. 12 | 13 | var testRunner = require('vscode/lib/testrunner'); 14 | 15 | // You can directly control Mocha options by uncommenting the following lines 16 | // See https://github.com/mochajs/mocha/wiki/Using-mocha-programmatically#set-options for more info 17 | testRunner.configure({ 18 | ui: 'tdd', // the TDD UI is being used in extension.test.ts (suite, test, etc.) 19 | useColors: true // colored output from test results 20 | }); 21 | 22 | module.exports = testRunner; -------------------------------------------------------------------------------- /test/utils.ts: -------------------------------------------------------------------------------- 1 | import { ISuiteCallbackContext } from "mocha"; 2 | 3 | const LONG_TIMEOUT: number = 5000; 4 | 5 | /** 6 | * Some functional tests that run the editor may require longer time to finish 7 | * than the default Mocha's timeout. Been called from a test suite body, this 8 | * helper increases the timeout to {@link LONG_TIMEOUT}. 9 | * 10 | * @param suite: A test suite instance. 11 | */ 12 | export const setLongTimeout = ( 13 | suite: Mocha.IContextDefinition | ISuiteCallbackContext): void => { 14 | suite.timeout(LONG_TIMEOUT); 15 | } 16 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "target": "es6", 5 | "outDir": "out", 6 | "lib": [ 7 | "es6", 8 | "dom" 9 | ], 10 | "sourceMap": true, 11 | "rootDir": ".", 12 | "alwaysStrict": true, 13 | "strict": true 14 | }, 15 | "exclude": [ 16 | "node_modules", 17 | ".vscode-test" 18 | ] 19 | } 20 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | //@ts-check 2 | 3 | 'use strict'; 4 | 5 | const path = require('path'); 6 | 7 | /**@type {import('webpack').Configuration}*/ 8 | const config = { 9 | target: 'node', // vscode extensions run in a Node.js-context 📖 -> https://webpack.js.org/configuration/node/ 10 | 11 | entry: './src/clojureMain.ts', // the entry point of this extension, 📖 -> https://webpack.js.org/configuration/entry-context/ 12 | output: { 13 | // the bundle is stored in the 'dist' folder (check package.json), 📖 -> https://webpack.js.org/configuration/output/ 14 | path: path.resolve(__dirname, 'dist'), 15 | filename: 'extension.js', 16 | libraryTarget: 'commonjs2', 17 | devtoolModuleFilenameTemplate: '../[resource-path]' 18 | }, 19 | devtool: 'source-map', 20 | externals: { 21 | vscode: 'commonjs vscode' // the vscode-module is created on-the-fly and must be excluded. Add other modules that cannot be webpack'ed, 📖 -> https://webpack.js.org/configuration/externals/ 22 | }, 23 | resolve: { 24 | // support reading TypeScript and JavaScript files, 📖 -> https://github.com/TypeStrong/ts-loader 25 | extensions: ['.ts', '.js'] 26 | }, 27 | module: { 28 | rules: [ 29 | { 30 | test: /\.ts$/, 31 | exclude: /node_modules/, 32 | use: [ 33 | { 34 | loader: 'ts-loader' 35 | } 36 | ] 37 | } 38 | ] 39 | } 40 | }; 41 | module.exports = config; 42 | --------------------------------------------------------------------------------