├── .eslintignore ├── .eslintrc.json ├── .gitattributes ├── .gitignore ├── .vscode ├── launch.json ├── settings.json └── tasks.json ├── .vscodeignore ├── CHANGELOG.md ├── LICENSE ├── README.md ├── SECURITY.md ├── client ├── package-lock.json ├── package.json ├── src │ └── extension.ts └── tsconfig.json ├── package-lock.json ├── package.json ├── samples ├── jsonrpc.db └── jsonrpc.lsif ├── server ├── package-lock.json ├── package.json ├── src │ ├── blobStore.ts │ ├── database.ts │ ├── files.ts │ ├── graphStore.ts │ ├── is.ts │ ├── jsonStore.ts │ ├── lsifServer.ts │ ├── protocol.compress.ts │ └── test.ts └── tsconfig.json └── tsconfig.json /.eslintignore: -------------------------------------------------------------------------------- 1 | client\out 2 | client\node_modules 3 | server\out 4 | server\node_modules 5 | node_modules -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "parser": "@typescript-eslint/parser", 3 | "parserOptions": { 4 | "ecmaVersion": 6, 5 | "sourceType": "module" 6 | }, 7 | "env": { 8 | "node": true 9 | }, 10 | "rules": { 11 | "semi": "error", 12 | "no-extra-semi": "warn", 13 | "curly": "warn", 14 | "quotes": ["error", "single", { "allowTemplateLiterals": true } ], 15 | "eqeqeq": "error", 16 | "indent": ["warn", "tab", { "SwitchCase": 1 } ] 17 | } 18 | } -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Set default behavior to automatically normalize line endings. 2 | * text=auto 3 | 4 | LICENSE.txt eol=crlf 5 | ThirdPartyNotices.txt eol=crlf 6 | 7 | *.bat eol=crlf 8 | *.cmd eol=crlf 9 | *.ps1 eol=lf 10 | *.sh eol=lf -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | out 2 | node_modules 3 | .vscode-test/ 4 | *.vsix 5 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "type": "extensionHost", 6 | "request": "launch", 7 | "name": "Launch Client", 8 | "runtimeExecutable": "${execPath}", 9 | "args": ["--extensionDevelopmentPath=${workspaceRoot}"], 10 | "outFiles": ["${workspaceRoot}/client/out/**/*.js"], 11 | "preLaunchTask": { 12 | "type": "npm", 13 | "script": "watch" 14 | } 15 | }, 16 | { 17 | "type": "extensionHost", 18 | "request": "launch", 19 | "name": "Launch Client LSIF", 20 | "runtimeExecutable": "${execPath}", 21 | "args": ["--extensionDevelopmentPath=${workspaceRoot}", "--folder-uri=lsif:///c:/Users/dirkb/Projects/mseng/VSCode/vscode-lsif-extension/samples/jsonrpc.lsif"], 22 | "outFiles": ["${workspaceRoot}/client/out/**/*.js"], 23 | "preLaunchTask": { 24 | "type": "npm", 25 | "script": "watch" 26 | } 27 | }, 28 | { 29 | "type": "extensionHost", 30 | "request": "launch", 31 | "name": "Launch Client SQLite", 32 | "runtimeExecutable": "${execPath}", 33 | "args": ["--extensionDevelopmentPath=${workspaceRoot}", "--folder-uri=lsif:///c:/Users/dirkb/Projects/mseng/VSCode/vscode-lsif-extension/samples/jsonrpc.db"], 34 | "outFiles": ["${workspaceRoot}/client/out/**/*.js"], 35 | "preLaunchTask": { 36 | "type": "npm", 37 | "script": "watch" 38 | } 39 | }, 40 | { 41 | "type": "node", 42 | "request": "attach", 43 | "name": "Attach to Server", 44 | "port": 6029, 45 | "outFiles": ["${workspaceRoot}/server/out/**/*.js"] 46 | }, 47 | { 48 | "type": "node", 49 | "request": "launch", 50 | "name": "Testing DB", 51 | "program": "${workspaceFolder}/server/out/test.js", 52 | "cwd": "${workspaceFolder}/server", 53 | "outFiles": ["${workspaceRoot}/server/out/**/*.js"], 54 | "sourceMaps": true 55 | } 56 | ], 57 | "compounds": [ 58 | { 59 | "name": "Client + Server", 60 | "configurations": ["Launch Client", "Attach to Server"] 61 | } 62 | ] 63 | } 64 | -------------------------------------------------------------------------------- /.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 | }, 6 | "search.exclude": { 7 | "out": true // set this to false to include "out" folder in search results 8 | }, 9 | // Turn off tsc task auto detection since we have the necessary tasks as npm scripts 10 | "typescript.tsc.autoDetect": "off", 11 | "editor.codeActionsOnSave": { 12 | "source.fixAll.eslint": "explicit" 13 | } 14 | } -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | // See https://go.microsoft.com/fwlink/?LinkId=733558 2 | // for the documentation about the tasks.json format 3 | { 4 | "version": "2.0.0", 5 | "tasks": [ 6 | { 7 | "type": "npm", 8 | "script": "compile", 9 | "group": "build", 10 | "presentation": { 11 | "panel": "dedicated", 12 | "reveal": "never" 13 | }, 14 | "problemMatcher": [ 15 | "$tsc" 16 | ] 17 | }, 18 | { 19 | "type": "npm", 20 | "script": "watch", 21 | "problemMatcher": "$tsc-watch", 22 | "isBackground": true, 23 | "presentation": { 24 | "panel": "dedicated", 25 | "reveal": "never" 26 | }, 27 | "group": { 28 | "kind": "build", 29 | "isDefault": true 30 | } 31 | } 32 | ] 33 | } -------------------------------------------------------------------------------- /.vscodeignore: -------------------------------------------------------------------------------- 1 | .vscode/** 2 | .vscode-test/** 3 | .gitignore 4 | samples/** 5 | client/out/**/*.d.ts 6 | client/out/**/*.map 7 | client/src/** 8 | client/tsconfig.json 9 | client/node_modules/** 10 | client/yarn.lock 11 | !client/node_modules/vscode-jsonrpc/** 12 | !client/node_modules/vscode-languageclient/** 13 | !client/node_modules/vscode-languageserver-protocol/** 14 | !client/node_modules/vscode-languageserver-types/** 15 | !client/node_modules/semver/** 16 | server/out/**/*.d.ts 17 | server/out/**/*.map 18 | server/src/** 19 | server/tsconfig.json 20 | server/yarn.lock 21 | server/node_modules/@types/** 22 | server/node_modules/**/*.d.ts 23 | server/node_modules/**/*.map 24 | CHANGELOG.md 25 | tsconfig.json 26 | yarn.lock 27 | vsc-extension-quickstart.md 28 | tslint.json 29 | *.vsix -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | All notable changes to the "vscode-lsif-extension" extension will be documented in this file. 3 | 4 | Check [Keep a Changelog](http://keepachangelog.com/) for recommendations on how to structure this file. 5 | 6 | ## [Unreleased] 7 | - Initial release -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | Copyright (c) Microsoft Corporation 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and 5 | associated documentation files (the "Software"), to deal in the Software without restriction, 6 | including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, 7 | and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, 8 | subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all copies or substantial 11 | portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT 14 | NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 15 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 16 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 17 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Extension to run a language server from a Language Server Index Format file 2 | 3 | The extension allows to browse the content of a LSIF dump stored either in a SQLite DB or in a file using LSIF line json format. To open a dump use the command Open LSIF Database. 4 | 5 | The extension is currently not published to the market place due to its use of native node modules. You therefore need to run it out of source or generate your own platform dependent VSIX file using the vsce tool. -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ## Security 4 | 5 | Microsoft takes the security of our software products and services seriously, which includes all source code repositories managed through our GitHub organizations, which include [Microsoft](https://github.com/Microsoft), [Azure](https://github.com/Azure), [DotNet](https://github.com/dotnet), [AspNet](https://github.com/aspnet), [Xamarin](https://github.com/xamarin), and [our GitHub organizations](https://opensource.microsoft.com/). 6 | 7 | If you believe you have found a security vulnerability in any Microsoft-owned repository that meets [Microsoft's definition of a security vulnerability](https://docs.microsoft.com/en-us/previous-versions/tn-archive/cc751383(v=technet.10)), please report it to us as described below. 8 | 9 | ## Reporting Security Issues 10 | 11 | **Please do not report security vulnerabilities through public GitHub issues.** 12 | 13 | Instead, please report them to the Microsoft Security Response Center (MSRC) at [https://msrc.microsoft.com/create-report](https://msrc.microsoft.com/create-report). 14 | 15 | If you prefer to submit without logging in, send email to [secure@microsoft.com](mailto:secure@microsoft.com). If possible, encrypt your message with our PGP key; please download it from the [Microsoft Security Response Center PGP Key page](https://www.microsoft.com/en-us/msrc/pgp-key-msrc). 16 | 17 | You should receive a response within 24 hours. If for some reason you do not, please follow up via email to ensure we received your original message. Additional information can be found at [microsoft.com/msrc](https://www.microsoft.com/msrc). 18 | 19 | Please include the requested information listed below (as much as you can provide) to help us better understand the nature and scope of the possible issue: 20 | 21 | * Type of issue (e.g. buffer overflow, SQL injection, cross-site scripting, etc.) 22 | * Full paths of source file(s) related to the manifestation of the issue 23 | * The location of the affected source code (tag/branch/commit or direct URL) 24 | * Any special configuration required to reproduce the issue 25 | * Step-by-step instructions to reproduce the issue 26 | * Proof-of-concept or exploit code (if possible) 27 | * Impact of the issue, including how an attacker might exploit the issue 28 | 29 | This information will help us triage your report more quickly. 30 | 31 | If you are reporting for a bug bounty, more complete reports can contribute to a higher bounty award. Please visit our [Microsoft Bug Bounty Program](https://microsoft.com/msrc/bounty) page for more details about our active programs. 32 | 33 | ## Preferred Languages 34 | 35 | We prefer all communications to be in English. 36 | 37 | ## Policy 38 | 39 | Microsoft follows the principle of [Coordinated Vulnerability Disclosure](https://www.microsoft.com/en-us/msrc/cvd). 40 | 41 | -------------------------------------------------------------------------------- /client/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vscode-lsif-client", 3 | "version": "0.0.1", 4 | "lockfileVersion": 2, 5 | "requires": true, 6 | "packages": { 7 | "": { 8 | "name": "vscode-lsif-client", 9 | "version": "0.0.1", 10 | "license": "MIT", 11 | "dependencies": { 12 | "vscode-languageclient": "^9.0.1" 13 | }, 14 | "devDependencies": { 15 | "@types/vscode": "1.86.0" 16 | }, 17 | "engines": { 18 | "vscode": "^1.86.0" 19 | } 20 | }, 21 | "node_modules/@types/vscode": { 22 | "version": "1.86.0", 23 | "resolved": "https://registry.npmjs.org/@types/vscode/-/vscode-1.86.0.tgz", 24 | "integrity": "sha512-DnIXf2ftWv+9LWOB5OJeIeaLigLHF7fdXF6atfc7X5g2w/wVZBgk0amP7b+ub5xAuW1q7qP5YcFvOcit/DtyCQ==", 25 | "dev": true 26 | }, 27 | "node_modules/balanced-match": { 28 | "version": "1.0.2", 29 | "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", 30 | "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" 31 | }, 32 | "node_modules/brace-expansion": { 33 | "version": "2.0.1", 34 | "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", 35 | "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", 36 | "dependencies": { 37 | "balanced-match": "^1.0.0" 38 | } 39 | }, 40 | "node_modules/lru-cache": { 41 | "version": "6.0.0", 42 | "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", 43 | "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", 44 | "dependencies": { 45 | "yallist": "^4.0.0" 46 | }, 47 | "engines": { 48 | "node": ">=10" 49 | } 50 | }, 51 | "node_modules/minimatch": { 52 | "version": "5.1.6", 53 | "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", 54 | "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", 55 | "dependencies": { 56 | "brace-expansion": "^2.0.1" 57 | }, 58 | "engines": { 59 | "node": ">=10" 60 | } 61 | }, 62 | "node_modules/semver": { 63 | "version": "7.5.3", 64 | "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.3.tgz", 65 | "integrity": "sha512-QBlUtyVk/5EeHbi7X0fw6liDZc7BBmEaSYn01fMU1OUYbf6GPsbTtd8WmnqbI20SeycoHSeiybkE/q1Q+qlThQ==", 66 | "dependencies": { 67 | "lru-cache": "^6.0.0" 68 | }, 69 | "bin": { 70 | "semver": "bin/semver.js" 71 | }, 72 | "engines": { 73 | "node": ">=10" 74 | } 75 | }, 76 | "node_modules/vscode-jsonrpc": { 77 | "version": "8.2.0", 78 | "resolved": "https://registry.npmjs.org/vscode-jsonrpc/-/vscode-jsonrpc-8.2.0.tgz", 79 | "integrity": "sha512-C+r0eKJUIfiDIfwJhria30+TYWPtuHJXHtI7J0YlOmKAo7ogxP20T0zxB7HZQIFhIyvoBPwWskjxrvAtfjyZfA==", 80 | "engines": { 81 | "node": ">=14.0.0" 82 | } 83 | }, 84 | "node_modules/vscode-languageclient": { 85 | "version": "9.0.1", 86 | "resolved": "https://registry.npmjs.org/vscode-languageclient/-/vscode-languageclient-9.0.1.tgz", 87 | "integrity": "sha512-JZiimVdvimEuHh5olxhxkht09m3JzUGwggb5eRUkzzJhZ2KjCN0nh55VfiED9oez9DyF8/fz1g1iBV3h+0Z2EA==", 88 | "dependencies": { 89 | "minimatch": "^5.1.0", 90 | "semver": "^7.3.7", 91 | "vscode-languageserver-protocol": "3.17.5" 92 | }, 93 | "engines": { 94 | "vscode": "^1.82.0" 95 | } 96 | }, 97 | "node_modules/vscode-languageserver-protocol": { 98 | "version": "3.17.5", 99 | "resolved": "https://registry.npmjs.org/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.17.5.tgz", 100 | "integrity": "sha512-mb1bvRJN8SVznADSGWM9u/b07H7Ecg0I3OgXDuLdn307rl/J3A9YD6/eYOssqhecL27hK1IPZAsaqh00i/Jljg==", 101 | "dependencies": { 102 | "vscode-jsonrpc": "8.2.0", 103 | "vscode-languageserver-types": "3.17.5" 104 | } 105 | }, 106 | "node_modules/vscode-languageserver-types": { 107 | "version": "3.17.5", 108 | "resolved": "https://registry.npmjs.org/vscode-languageserver-types/-/vscode-languageserver-types-3.17.5.tgz", 109 | "integrity": "sha512-Ld1VelNuX9pdF39h2Hgaeb5hEZM2Z3jUrrMgWQAu82jMtZp7p3vJT3BzToKtZI7NgQssZje5o0zryOrhQvzQAg==" 110 | }, 111 | "node_modules/yallist": { 112 | "version": "4.0.0", 113 | "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", 114 | "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" 115 | } 116 | }, 117 | "dependencies": { 118 | "@types/vscode": { 119 | "version": "1.86.0", 120 | "resolved": "https://registry.npmjs.org/@types/vscode/-/vscode-1.86.0.tgz", 121 | "integrity": "sha512-DnIXf2ftWv+9LWOB5OJeIeaLigLHF7fdXF6atfc7X5g2w/wVZBgk0amP7b+ub5xAuW1q7qP5YcFvOcit/DtyCQ==", 122 | "dev": true 123 | }, 124 | "balanced-match": { 125 | "version": "1.0.2", 126 | "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", 127 | "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" 128 | }, 129 | "brace-expansion": { 130 | "version": "2.0.1", 131 | "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", 132 | "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", 133 | "requires": { 134 | "balanced-match": "^1.0.0" 135 | } 136 | }, 137 | "lru-cache": { 138 | "version": "6.0.0", 139 | "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", 140 | "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", 141 | "requires": { 142 | "yallist": "^4.0.0" 143 | } 144 | }, 145 | "minimatch": { 146 | "version": "5.1.6", 147 | "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", 148 | "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", 149 | "requires": { 150 | "brace-expansion": "^2.0.1" 151 | } 152 | }, 153 | "semver": { 154 | "version": "7.5.3", 155 | "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.3.tgz", 156 | "integrity": "sha512-QBlUtyVk/5EeHbi7X0fw6liDZc7BBmEaSYn01fMU1OUYbf6GPsbTtd8WmnqbI20SeycoHSeiybkE/q1Q+qlThQ==", 157 | "requires": { 158 | "lru-cache": "^6.0.0" 159 | } 160 | }, 161 | "vscode-jsonrpc": { 162 | "version": "8.2.0", 163 | "resolved": "https://registry.npmjs.org/vscode-jsonrpc/-/vscode-jsonrpc-8.2.0.tgz", 164 | "integrity": "sha512-C+r0eKJUIfiDIfwJhria30+TYWPtuHJXHtI7J0YlOmKAo7ogxP20T0zxB7HZQIFhIyvoBPwWskjxrvAtfjyZfA==" 165 | }, 166 | "vscode-languageclient": { 167 | "version": "9.0.1", 168 | "resolved": "https://registry.npmjs.org/vscode-languageclient/-/vscode-languageclient-9.0.1.tgz", 169 | "integrity": "sha512-JZiimVdvimEuHh5olxhxkht09m3JzUGwggb5eRUkzzJhZ2KjCN0nh55VfiED9oez9DyF8/fz1g1iBV3h+0Z2EA==", 170 | "requires": { 171 | "minimatch": "^5.1.0", 172 | "semver": "^7.3.7", 173 | "vscode-languageserver-protocol": "3.17.5" 174 | } 175 | }, 176 | "vscode-languageserver-protocol": { 177 | "version": "3.17.5", 178 | "resolved": "https://registry.npmjs.org/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.17.5.tgz", 179 | "integrity": "sha512-mb1bvRJN8SVznADSGWM9u/b07H7Ecg0I3OgXDuLdn307rl/J3A9YD6/eYOssqhecL27hK1IPZAsaqh00i/Jljg==", 180 | "requires": { 181 | "vscode-jsonrpc": "8.2.0", 182 | "vscode-languageserver-types": "3.17.5" 183 | } 184 | }, 185 | "vscode-languageserver-types": { 186 | "version": "3.17.5", 187 | "resolved": "https://registry.npmjs.org/vscode-languageserver-types/-/vscode-languageserver-types-3.17.5.tgz", 188 | "integrity": "sha512-Ld1VelNuX9pdF39h2Hgaeb5hEZM2Z3jUrrMgWQAu82jMtZp7p3vJT3BzToKtZI7NgQssZje5o0zryOrhQvzQAg==" 189 | }, 190 | "yallist": { 191 | "version": "4.0.0", 192 | "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", 193 | "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" 194 | } 195 | } 196 | } 197 | -------------------------------------------------------------------------------- /client/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vscode-lsif-client", 3 | "description": "VSCode Language Server Index Format client", 4 | "author": "Microsoft Corporation", 5 | "license": "MIT", 6 | "version": "0.0.1", 7 | "publisher": "vscode", 8 | "engines": { 9 | "vscode": "^1.86.0" 10 | }, 11 | "dependencies": { 12 | "vscode-languageclient": "^9.0.1" 13 | }, 14 | "devDependencies": { 15 | "@types/vscode": "1.86.0" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /client/src/extension.ts: -------------------------------------------------------------------------------- 1 | /* -------------------------------------------------------------------------------------------- 2 | * Copyright (c) Microsoft Corporation. All rights reserved. 3 | * Licensed under the MIT License. See License.txt in the project root for license information. 4 | * ------------------------------------------------------------------------------------------ */ 5 | import * as path from 'path'; 6 | 7 | import { workspace, ExtensionContext, FileType as VFileType, FileSystemProvider, Uri, Event, FileChangeEvent, EventEmitter, FileSystemError, commands, window } from 'vscode'; 8 | 9 | import { 10 | LanguageClient, 11 | LanguageClientOptions, 12 | ServerOptions, 13 | TransportKind, 14 | Disposable, 15 | RequestType, 16 | } from 'vscode-languageclient/node'; 17 | 18 | let client: LanguageClient; 19 | 20 | export function activate(context: ExtensionContext) { 21 | 22 | commands.registerCommand('lsif.openDatabase', () => { 23 | window.showOpenDialog( 24 | { 25 | openLabel: 'Select LSIF Database to open', 26 | canSelectFiles: true, 27 | canSelectFolders: false, 28 | canSelectMany: true, 29 | filters: { 'LSIF': ['db', 'lsif'] } 30 | } 31 | ).then((values: Uri[] | undefined) => { 32 | if (values === undefined || values.length === 0) { 33 | return; 34 | } 35 | let toAdd = values.map((uri) => { return { uri: uri.with({ scheme: 'lsif'}) }; }); 36 | workspace.updateWorkspaceFolders( 37 | workspace.workspaceFolders ? workspace.workspaceFolders.length : 0, 38 | 0, 39 | ...toAdd 40 | ); 41 | }); 42 | }); 43 | 44 | // The server is implemented in node 45 | let serverModule = context.asAbsolutePath( 46 | path.join('server', 'out', 'lsifServer.js') 47 | ); 48 | // The debug options for the server 49 | // --inspect=6019: runs the server in Node's Inspector mode so VS Code can attach to the server for debugging 50 | let debugOptions = { execArgv: ['--nolazy', '--inspect=6029'] }; 51 | 52 | // If the extension is launched in debug mode then the debug server options are used 53 | // Otherwise the run options are used 54 | let serverOptions: ServerOptions = { 55 | run: { module: serverModule, transport: TransportKind.ipc }, 56 | debug: { 57 | module: serverModule, 58 | transport: TransportKind.ipc, 59 | options: debugOptions 60 | } 61 | }; 62 | 63 | // Options to control the language client 64 | let clientOptions: LanguageClientOptions = { 65 | }; 66 | 67 | // Create the language client and start the client. 68 | client = new LanguageClient( 69 | 'lsif', 70 | 'Language Server Index Format', 71 | serverOptions, 72 | clientOptions 73 | ); 74 | 75 | // Start the client. This will also launch the server 76 | client.start(); 77 | 78 | let clientPromise = new Promise((resolve, reject) => { 79 | client.start().then(() => { resolve(client); }, reject); 80 | }); 81 | 82 | workspace.registerFileSystemProvider('lsif', new LsifFS(clientPromise), { isCaseSensitive: true, isReadonly: true}); 83 | } 84 | 85 | export function deactivate(): Thenable | undefined { 86 | if (!client) { 87 | return undefined; 88 | } 89 | return client.stop(); 90 | } 91 | 92 | namespace FileType { 93 | export const Unknown: 0 = 0; 94 | export const File: 1 = 1; 95 | export const Directory: 2 = 2; 96 | export const SymbolicLink: 64 = 64; 97 | } 98 | 99 | type FileType = 0 | 1 | 2 | 64; 100 | 101 | interface FileStat { 102 | type: FileType; 103 | ctime: number; 104 | mtime: number; 105 | size: number; 106 | } 107 | 108 | interface StatFileParams { 109 | uri: string; 110 | } 111 | 112 | namespace StatFileRequest { 113 | export const type = new RequestType('lsif/statFile'); 114 | } 115 | 116 | interface ReadFileParams { 117 | uri: string; 118 | } 119 | 120 | namespace ReadFileRequest { 121 | export const type = new RequestType('lsif/readfile'); 122 | } 123 | 124 | interface ReadDirectoryParams { 125 | uri: string; 126 | } 127 | 128 | namespace ReadDirectoryRequest { 129 | export const type = new RequestType('lsif/readDirectory'); 130 | } 131 | 132 | class LsifFS implements FileSystemProvider { 133 | 134 | private readonly client: Promise; 135 | 136 | private readonly emitter: EventEmitter; 137 | public readonly onDidChangeFile: Event; 138 | 139 | public constructor(client: Promise) { 140 | this.client = client; 141 | this.emitter = new EventEmitter(); 142 | this.onDidChangeFile = this.emitter.event; 143 | } 144 | 145 | watch(uri: Uri, options: { recursive: boolean; excludes: string[]; }): Disposable { 146 | // The LSIF file systrem never changes. 147 | return Disposable.create(():void => {}); 148 | } 149 | 150 | async stat(uri: Uri): Promise { 151 | let client = await this.client; 152 | return client.sendRequest(StatFileRequest.type, { uri: client.code2ProtocolConverter.asUri(uri) }).then((value) => { 153 | if (!value) { 154 | throw FileSystemError.FileNotFound(uri); 155 | } 156 | return value; 157 | }, (error) => { 158 | throw FileSystemError.FileNotFound(uri); 159 | }); 160 | } 161 | 162 | async readDirectory(uri: Uri): Promise<[string, VFileType][]> { 163 | let client = await this.client; 164 | let params: ReadDirectoryParams = { uri: client.code2ProtocolConverter.asUri(uri) }; 165 | return client.sendRequest(ReadDirectoryRequest.type, params).then((values) => { 166 | return values; 167 | }); 168 | } 169 | 170 | async readFile(uri: Uri): Promise { 171 | let client = await this.client; 172 | let params: ReadFileParams = { uri: client.code2ProtocolConverter.asUri(uri) }; 173 | return client.sendRequest(ReadFileRequest.type, params).then((value) => { 174 | let result = new Uint8Array(Buffer.from(value, 'base64')); 175 | return result; 176 | }); 177 | } 178 | 179 | createDirectory(uri: Uri): void | Thenable { 180 | throw new Error('File system is readonly.'); 181 | } 182 | 183 | writeFile(uri: Uri, content: Uint8Array, options: { create: boolean; overwrite: boolean; }): void | Thenable { 184 | throw new Error('File system is readonly.'); 185 | } 186 | 187 | delete(uri: Uri, options: { recursive: boolean; }): void | Thenable { 188 | throw new Error('File system is readonly.'); 189 | } 190 | 191 | rename(oldUri: Uri, newUri: Uri, options: { overwrite: boolean; }): void | Thenable { 192 | throw new Error('File system is readonly.'); 193 | } 194 | } -------------------------------------------------------------------------------- /client/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "target": "es6", 5 | "outDir": "out", 6 | "rootDir": "src", 7 | "lib": ["es6"], 8 | "sourceMap": true, 9 | "strict": true, 10 | "noImplicitAny": true, 11 | "noImplicitReturns": true 12 | }, 13 | "include": ["src"], 14 | "exclude": ["node_modules"] 15 | } 16 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "lsif-browser", 3 | "description": "Extension to browse LSIF databases", 4 | "author": "Microsoft Corporation", 5 | "publisher": "ms-vscode", 6 | "license": "MIT", 7 | "version": "0.6.0", 8 | "repository": { 9 | "type": "git", 10 | "url": "git+https://github.com/Microsoft/vscode-lsif-extension.git" 11 | }, 12 | "bugs": { 13 | "url": "https://github.com/Microsoft/vscode-lsif-extension/issues" 14 | }, 15 | "categories": [], 16 | "keywords": [ 17 | "multi-root ready" 18 | ], 19 | "engines": { 20 | "vscode": "^1.86.0" 21 | }, 22 | "activationEvents": [ 23 | "onFileSystem:lsif", 24 | "onCommand:lsif.openDatabase" 25 | ], 26 | "enabledApiProposals": [ 27 | "documentFiltersExclusive" 28 | ], 29 | "main": "./client/out/extension", 30 | "contributes": { 31 | "commands": [ 32 | { 33 | "command": "lsif.openDatabase", 34 | "title": "Open LSIF Database" 35 | } 36 | ], 37 | "configuration": { 38 | "type": "object", 39 | "title": "Index Format configuration", 40 | "properties": { 41 | "lsif.trace.server": { 42 | "scope": "window", 43 | "type": "string", 44 | "enum": [ 45 | "off", 46 | "messages", 47 | "verbose" 48 | ], 49 | "default": "off", 50 | "description": "Traces the communication between VS Code and the language server." 51 | } 52 | } 53 | } 54 | }, 55 | "scripts": { 56 | "vscode:prepublish": "npm run compile", 57 | "clean": "tsc -b --clean", 58 | "compile": "tsc -b", 59 | "watch": "tsc -b -w", 60 | "postinstall": "cd client && npm install && cd ../server && npm install && cd .." 61 | }, 62 | "devDependencies": { 63 | "@types/node": "^18.14.6", 64 | "@typescript-eslint/eslint-plugin": "^7.0.1", 65 | "@typescript-eslint/parser": "^7.0.1", 66 | "eslint": "^8.56.0", 67 | "typescript": "^4.9.5" 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /samples/jsonrpc.db: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/vscode-lsif-extension/2bcc92d7e97ff48f65eaf6e74daa29ea24d85d1b/samples/jsonrpc.db -------------------------------------------------------------------------------- /server/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "lsif-server", 3 | "description": "Generic Language Server Index Format Server", 4 | "author": "Microsoft Corporation", 5 | "license": "MIT", 6 | "version": "0.1.0", 7 | "engines": { 8 | "node": "*" 9 | }, 10 | "dependencies": { 11 | "better-sqlite3": "^9.4.1", 12 | "lsif-protocol": "0.6.0-next.7", 13 | "semver": "^7.6.0", 14 | "vscode-languageserver": "^9.0.1", 15 | "vscode-uri": "^3.0.8" 16 | }, 17 | "devDependencies": { 18 | "@types/better-sqlite3": "^7.6.9", 19 | "@types/semver": "^7.5.7", 20 | "electron-rebuild": "^3.2.9" 21 | }, 22 | "scripts": {} 23 | } 24 | -------------------------------------------------------------------------------- /server/src/blobStore.ts: -------------------------------------------------------------------------------- 1 | /* -------------------------------------------------------------------------------------------- 2 | * Copyright (c) Microsoft Corporation. All rights reserved. 3 | * Licensed under the MIT License. See License.txt in the project root for license information. 4 | * ------------------------------------------------------------------------------------------ */ 5 | import * as Sqlite from 'better-sqlite3'; 6 | 7 | import * as lsp from 'vscode-languageserver'; 8 | 9 | import { Database, UriTransformer } from './database'; 10 | import { 11 | Id, RangeBasedDocumentSymbol, Range, ReferenceResult, Moniker, MetaData 12 | } from 'lsif-protocol'; 13 | 14 | import { DocumentInfo } from './files'; 15 | import { URI } from 'vscode-uri'; 16 | 17 | interface MetaDataResult { 18 | id: number; 19 | value: string; 20 | } 21 | 22 | interface LiteralMap { 23 | [key: string]: T; 24 | [key: number]: T; 25 | } 26 | 27 | interface RangeData extends Pick { 28 | moniker?: Id; 29 | next?: Id; 30 | hoverResult?: Id; 31 | declarationResult?: Id; 32 | definitionResult?: Id; 33 | referenceResult?: Id; 34 | } 35 | 36 | interface ResultSetData { 37 | moniker?: Id; 38 | next?: Id; 39 | hoverResult?: Id; 40 | declarationResult?: Id; 41 | definitionResult?: Id; 42 | referenceResult?: Id; 43 | } 44 | 45 | interface DeclarationResultData { 46 | values: Id[]; 47 | } 48 | 49 | interface DefinitionResultData { 50 | values: Id[]; 51 | } 52 | 53 | interface ReferenceResultData { 54 | declarations?: Id[]; 55 | definitions?: Id[]; 56 | references?: Id[]; 57 | } 58 | 59 | type MonikerData = Pick; 60 | 61 | interface DocumentBlob { 62 | contents: string; 63 | ranges: LiteralMap; 64 | resultSets?: LiteralMap; 65 | monikers?: LiteralMap; 66 | hovers?: LiteralMap; 67 | declarationResults?: LiteralMap; 68 | definitionResults?: LiteralMap; 69 | referenceResults?: LiteralMap; 70 | foldingRanges?: lsp.FoldingRange[]; 71 | documentSymbols?: lsp.DocumentSymbol[] | RangeBasedDocumentSymbol[]; 72 | diagnostics?: lsp.Diagnostic[]; 73 | } 74 | 75 | interface DocumentsResult { 76 | documentHash: string; 77 | uri: string; 78 | } 79 | 80 | interface BlobResult { 81 | content: Buffer; 82 | } 83 | 84 | interface DocumentResult { 85 | id: Id; 86 | documentHash: string; 87 | } 88 | 89 | interface DefsResult { 90 | uri: string; 91 | startLine: number; 92 | startCharacter: number; 93 | endLine: number; 94 | endCharacter: number; 95 | } 96 | 97 | interface DeclsResult { 98 | uri: string; 99 | startLine: number; 100 | startCharacter: number; 101 | endLine: number; 102 | endCharacter: number; 103 | } 104 | 105 | interface RefsResult { 106 | uri: string; 107 | kind: number; 108 | startLine: number; 109 | startCharacter: number; 110 | endLine: number; 111 | endCharacter: number; 112 | } 113 | 114 | export class BlobStore extends Database { 115 | 116 | private db!: Sqlite.Database; 117 | 118 | private allDocumentsStmt!: Sqlite.Statement; 119 | private findDocumentStmt!: Sqlite.Statement; 120 | private findBlobStmt!: Sqlite.Statement; 121 | private findDeclsStmt!: Sqlite.Statement; 122 | private findDefsStmt!: Sqlite.Statement; 123 | private findRefsStmt!: Sqlite.Statement; 124 | private findHoverStmt!: Sqlite.Statement; 125 | 126 | private version!: string; 127 | private workspaceRoot!: URI; 128 | private blobs: Map; 129 | 130 | public constructor() { 131 | super(); 132 | this.version; 133 | this.blobs = new Map(); 134 | } 135 | 136 | public load(file: string, transformerFactory: (workspaceRoot: string) => UriTransformer): Promise { 137 | this.db = new Sqlite(file, { readonly: true }); 138 | this.readMetaData(); 139 | /* eslint-disable indent */ 140 | this.allDocumentsStmt = this.db.prepare([ 141 | 'Select d.documentHash, d.uri From documents d', 142 | 'Inner Join versions v On v.hash = d.documentHash', 143 | 'Where v.version = ?' 144 | ].join(' ')); 145 | this.findDocumentStmt = this.db.prepare([ 146 | 'Select d.documentHash From documents d', 147 | 'Inner Join versions v On v.hash = d.documentHash', 148 | 'Where v.version = $version and d.uri = $uri' 149 | ].join(' ')); 150 | this.findBlobStmt = this.db.prepare('Select content From blobs Where hash = ?'); 151 | this.findDeclsStmt = this.db.prepare([ 152 | 'Select doc.uri, d.startLine, d.startCharacter, d.endLine, d.endCharacter From decls d', 153 | 'Inner Join versions v On d.documentHash = v.hash', 154 | 'Inner Join documents doc On d.documentHash = doc.documentHash', 155 | 'Where v.version = $version and d.scheme = $scheme and d.identifier = $identifier' 156 | ].join(' ')); 157 | this.findDefsStmt = this.db.prepare([ 158 | 'Select doc.uri, d.startLine, d.startCharacter, d.endLine, d.endCharacter From defs d', 159 | 'Inner Join versions v On d.documentHash = v.hash', 160 | 'Inner Join documents doc On d.documentHash = doc.documentHash', 161 | 'Where v.version = $version and d.scheme = $scheme and d.identifier = $identifier' 162 | ].join(' ')); 163 | this.findRefsStmt = this.db.prepare([ 164 | 'Select doc.uri, r.kind, r.startLine, r.startCharacter, r.endLine, r.endCharacter From refs r', 165 | 'Inner Join versions v On r.documentHash = v.hash', 166 | 'Inner Join documents doc On r.documentHash = doc.documentHash', 167 | 'Where v.version = $version and r.scheme = $scheme and r.identifier = $identifier' 168 | ].join(' ')); 169 | this.findHoverStmt = this.db.prepare([ 170 | 'Select b.content From blobs b', 171 | 'Inner Join versions v On b.hash = v.hash', 172 | 'Inner Join hovers h On h.hoverHash = b.hash', 173 | 'Where v.version = $version and h.scheme = $scheme and h.identifier = $identifier' 174 | 175 | ].join(' ')); 176 | /* eslint-enable indent */ 177 | this.version = (this.db.prepare('Select * from versionTags Order by dateTime desc').get() as any).tag; 178 | if (typeof this.version !== 'string') { 179 | throw new Error('Version tag must be a string'); 180 | } 181 | this.initialize(transformerFactory); 182 | return Promise.resolve(); 183 | } 184 | 185 | private readMetaData(): void { 186 | let result: MetaDataResult[] = this.db.prepare('Select * from meta').all() as MetaDataResult[]; 187 | if (result === undefined || result.length !== 1) { 188 | throw new Error('Failed to read meta data record.'); 189 | } 190 | let metaData: MetaData = JSON.parse(result[0].value); 191 | } 192 | 193 | public getWorkspaceRoot(): URI { 194 | return this.workspaceRoot; 195 | } 196 | 197 | public close(): void { 198 | this.db.close(); 199 | } 200 | 201 | protected getDocumentInfos(): DocumentInfo[] { 202 | let result: DocumentsResult[] = this.allDocumentsStmt.all(this.version) as DocumentsResult[]; 203 | if (result === undefined) { 204 | return []; 205 | } 206 | return result.map((item) => { return { id: item.documentHash, uri: item.uri, hash: item.documentHash }; }); 207 | } 208 | 209 | private getBlob(documentId: Id): DocumentBlob { 210 | let result = this.blobs.get(documentId); 211 | if (result === undefined) { 212 | const blobResult: BlobResult = this.findBlobStmt.get(documentId) as BlobResult; 213 | result = JSON.parse(blobResult.content.toString('utf8')) as DocumentBlob; 214 | this.blobs.set(documentId, result); 215 | } 216 | return result; 217 | } 218 | 219 | protected findFile(uri: string): { id: Id, hash: string | undefined }| undefined { 220 | let result: DocumentResult = this.findDocumentStmt.get({ version: this.version, uri: uri }) as DocumentResult; 221 | return result !== undefined ? { id: result.id, hash: result.documentHash} : undefined; 222 | } 223 | 224 | protected fileContent(info: { id: Id; hash: string | undefined }): string { 225 | const blob = this.getBlob(info.id); 226 | return Buffer.from(blob.contents).toString('base64'); 227 | } 228 | 229 | public foldingRanges(uri: string): lsp.FoldingRange[] | undefined { 230 | return undefined; 231 | } 232 | 233 | public documentSymbols(uri: string): lsp.DocumentSymbol[] | undefined { 234 | return undefined; 235 | } 236 | 237 | public hover(uri: string, position: lsp.Position): lsp.Hover | undefined { 238 | const { range, blob } = this.findRangeFromPosition(this.toDatabase(uri), position); 239 | if (range === undefined || blob === undefined || blob.hovers === undefined) { 240 | return undefined; 241 | } 242 | let result = this.findResult(blob.resultSets, blob.hovers, range, 'hoverResult'); 243 | if (result !== undefined) { 244 | return result; 245 | } 246 | const moniker = this.findMoniker(blob.resultSets, blob.monikers, range); 247 | if (moniker === undefined) { 248 | return undefined; 249 | } 250 | const qResult: BlobResult = this.findHoverStmt.get({ version: this.version, scheme: moniker.scheme, identifier: moniker.identifier }) as BlobResult; 251 | if (qResult === undefined) { 252 | return undefined; 253 | } 254 | result = JSON.parse(qResult.content.toString()) as lsp.Hover; 255 | if (result.range === undefined) { 256 | result.range = lsp.Range.create(range.start.line, range.start.character, range.end.line, range.end.character); 257 | } 258 | return result; 259 | } 260 | 261 | public declarations(uri: string, position: lsp.Position): lsp.Location | lsp.Location[] | undefined { 262 | const { range, blob } = this.findRangeFromPosition(this.toDatabase(uri), position); 263 | if (range === undefined || blob === undefined || blob.declarationResults === undefined) { 264 | return undefined; 265 | } 266 | let resultData = this.findResult(blob.resultSets, blob.declarationResults, range, 'declarationResult'); 267 | if (resultData === undefined) { 268 | const moniker = this.findMoniker(blob.resultSets, blob.monikers, range); 269 | if (moniker === undefined) { 270 | return undefined; 271 | } 272 | return this.findDeclarationsInDB(moniker); 273 | } else { 274 | return BlobStore.asLocations(blob.ranges, uri, resultData.values); 275 | } 276 | } 277 | 278 | private findDeclarationsInDB(moniker: MonikerData): lsp.Location[] | undefined { 279 | let qResult: DeclsResult[] = this.findDeclsStmt.all({ version: this.version, scheme: moniker.scheme, identifier: moniker.identifier }) as DeclsResult[]; 280 | if (qResult === undefined || qResult.length === 0) { 281 | return undefined; 282 | } 283 | return qResult.map((item) => { 284 | return lsp.Location.create(this.fromDatabase(item.uri), lsp.Range.create(item.startLine, item.startCharacter, item.endLine, item.endCharacter)); 285 | }); 286 | } 287 | 288 | public definitions(uri: string, position: lsp.Position): lsp.Location | lsp.Location[] | undefined { 289 | const { range, blob } = this.findRangeFromPosition(this.toDatabase(uri), position); 290 | if (range === undefined || blob === undefined || blob.definitionResults === undefined) { 291 | return undefined; 292 | } 293 | let resultData = this.findResult(blob.resultSets, blob.definitionResults, range, 'definitionResult'); 294 | if (resultData === undefined) { 295 | const moniker = this.findMoniker(blob.resultSets, blob.monikers, range); 296 | if (moniker === undefined) { 297 | return undefined; 298 | } 299 | return this.findDefinitionsInDB(moniker); 300 | } else { 301 | return BlobStore.asLocations(blob.ranges, uri, resultData.values); 302 | } 303 | } 304 | 305 | private findDefinitionsInDB(moniker: MonikerData): lsp.Location[] | undefined { 306 | let qResult: DefsResult[] = this.findDefsStmt.all({ version: this.version, scheme: moniker.scheme, identifier: moniker.identifier }) as DefsResult[]; 307 | if (qResult === undefined || qResult.length === 0) { 308 | return undefined; 309 | } 310 | return qResult.map((item) => { 311 | return lsp.Location.create(this.fromDatabase(item.uri), lsp.Range.create(item.startLine, item.startCharacter, item.endLine, item.endCharacter)); 312 | }); 313 | } 314 | 315 | public references(uri: string, position: lsp.Position, context: lsp.ReferenceContext): lsp.Location[] | undefined { 316 | const { range, blob } = this.findRangeFromPosition(this.toDatabase(uri), position); 317 | if (range === undefined || blob === undefined || blob.referenceResults === undefined) { 318 | return undefined; 319 | } 320 | let resultData = this.findResult(blob.resultSets, blob.referenceResults, range, 'referenceResult'); 321 | if (resultData === undefined) { 322 | const moniker = this.findMoniker(blob.resultSets, blob.monikers, range); 323 | if (moniker === undefined) { 324 | return undefined; 325 | } 326 | return this.findReferencesInDB(moniker, context); 327 | } else { 328 | let result: lsp.Location[] = []; 329 | if (context.includeDeclaration && resultData.declarations !== undefined) { 330 | result.push(...BlobStore.asLocations(blob.ranges, uri, resultData.declarations)); 331 | } 332 | if (context.includeDeclaration && resultData.definitions !== undefined) { 333 | result.push(...BlobStore.asLocations(blob.ranges, uri, resultData.definitions)); 334 | } 335 | if (resultData.references !== undefined) { 336 | result.push(...BlobStore.asLocations(blob.ranges, uri, resultData.references)); 337 | } 338 | return result; 339 | } 340 | } 341 | 342 | private findReferencesInDB(moniker: MonikerData, context: lsp.ReferenceContext): lsp.Location[] | undefined { 343 | let qResult: RefsResult[] = this.findRefsStmt.all({ version: this.version, scheme: moniker.scheme, identifier: moniker.identifier }) as RefsResult[]; 344 | if (qResult === undefined || qResult.length === 0) { 345 | return undefined; 346 | } 347 | let result: lsp.Location[] = []; 348 | for (let item of qResult) { 349 | if (context.includeDeclaration || item.kind === 2) { 350 | result.push(lsp.Location.create(this.fromDatabase(item.uri), lsp.Range.create(item.startLine, item.startCharacter, item.endLine, item.endCharacter))); 351 | } 352 | } 353 | return result; 354 | } 355 | 356 | private findResult(resultSets: LiteralMap | undefined, map: LiteralMap, data: RangeData | ResultSetData, property: keyof (RangeData | ResultSetData)): T | undefined { 357 | let current: RangeData | ResultSetData | undefined = data; 358 | while (current !== undefined) { 359 | let value = current[property]; 360 | if (value !== undefined) { 361 | return map[value]; 362 | } 363 | current = current.next !== undefined 364 | ? (resultSets !== undefined ? resultSets[current.next] : undefined) 365 | : undefined; 366 | } 367 | return undefined; 368 | } 369 | 370 | private findMoniker(resultSets: LiteralMap | undefined, monikers: LiteralMap | undefined, data: RangeData | ResultSetData): MonikerData | undefined { 371 | if (monikers === undefined) { 372 | return undefined; 373 | } 374 | let current: RangeData | ResultSetData | undefined = data; 375 | let result: Id | undefined; 376 | while (current !== undefined) { 377 | if (current.moniker !== undefined) { 378 | result = current.moniker; 379 | } 380 | current = current.next !== undefined 381 | ? (resultSets !== undefined ? resultSets[current.next] : undefined) 382 | : undefined; 383 | } 384 | return result !== undefined ? monikers[result] : undefined; 385 | } 386 | 387 | private findRangeFromPosition(uri: string, position: lsp.Position): { range: RangeData | undefined, blob: DocumentBlob | undefined } { 388 | const documentId = this.findFile(uri); 389 | if (documentId === undefined) { 390 | return { range: undefined, blob: undefined }; 391 | } 392 | const blob = this.getBlob(documentId.id); 393 | let candidate: RangeData | undefined; 394 | for (let key of Object.keys(blob.ranges)) { 395 | let range = blob.ranges[key]; 396 | if (BlobStore.containsPosition(range, position)) { 397 | if (!candidate) { 398 | candidate = range; 399 | } else { 400 | if (BlobStore.containsRange(candidate, range)) { 401 | candidate = range; 402 | } 403 | } 404 | } 405 | 406 | } 407 | return { range: candidate, blob}; 408 | } 409 | 410 | private static asLocations(ranges: LiteralMap, uri: string, ids: Id[]): lsp.Location[] { 411 | return ids.map(id => { 412 | let range = ranges[id]; 413 | return lsp.Location.create(uri, lsp.Range.create(range.start.line, range.start.character, range.end.line, range.end.character)); 414 | }); 415 | } 416 | 417 | private static containsPosition(range: lsp.Range, position: lsp.Position): boolean { 418 | if (position.line < range.start.line || position.line > range.end.line) { 419 | return false; 420 | } 421 | if (position.line === range.start.line && position.character < range.start.character) { 422 | return false; 423 | } 424 | if (position.line === range.end.line && position.character > range.end.character) { 425 | return false; 426 | } 427 | return true; 428 | } 429 | 430 | /** 431 | * Test if `otherRange` is in `range`. If the ranges are equal, will return true. 432 | */ 433 | public static containsRange(range: lsp.Range, otherRange: lsp.Range): boolean { 434 | if (otherRange.start.line < range.start.line || otherRange.end.line < range.start.line) { 435 | return false; 436 | } 437 | if (otherRange.start.line > range.end.line || otherRange.end.line > range.end.line) { 438 | return false; 439 | } 440 | if (otherRange.start.line === range.start.line && otherRange.start.character < range.start.character) { 441 | return false; 442 | } 443 | if (otherRange.end.line === range.end.line && otherRange.end.character > range.end.character) { 444 | return false; 445 | } 446 | return true; 447 | } 448 | } -------------------------------------------------------------------------------- /server/src/database.ts: -------------------------------------------------------------------------------- 1 | /* -------------------------------------------------------------------------------------------- 2 | * Copyright (c) Microsoft Corporation. All rights reserved. 3 | * Licensed under the MIT License. See License.txt in the project root for license information. 4 | * ------------------------------------------------------------------------------------------ */ 5 | import { URI } from 'vscode-uri'; 6 | import * as lsp from 'vscode-languageserver'; 7 | import { Range, Id } from 'lsif-protocol'; 8 | 9 | import { FileType, FileSystem, DocumentInfo, FileStat } from './files'; 10 | 11 | export interface UriTransformer { 12 | toDatabase(uri: string): string; 13 | fromDatabase(uri: string): string; 14 | } 15 | 16 | export const noopTransformer: UriTransformer = { 17 | toDatabase: uri => uri, 18 | fromDatabase: uri => uri 19 | }; 20 | 21 | export abstract class Database { 22 | 23 | private fileSystem!: FileSystem; 24 | private uriTransformer!: UriTransformer; 25 | 26 | protected constructor() { 27 | } 28 | 29 | protected initialize(transformerFactory: (workspaceRoot: string) => UriTransformer): void { 30 | const workspaceRoot = this.getWorkspaceRoot().toString(true); 31 | this.uriTransformer = transformerFactory ? transformerFactory(workspaceRoot) : noopTransformer; 32 | this.fileSystem = new FileSystem(workspaceRoot, this.getDocumentInfos()); 33 | } 34 | 35 | public abstract load(file: string, transformerFactory: (workspaceRoot: string) => UriTransformer): Promise; 36 | 37 | public abstract close(): void; 38 | 39 | public abstract getWorkspaceRoot(): URI; 40 | 41 | protected abstract getDocumentInfos(): DocumentInfo[]; 42 | 43 | public stat(uri: string): FileStat | null { 44 | let transformed = this.uriTransformer.toDatabase(uri); 45 | let result = this.fileSystem.stat(transformed); 46 | if (result !== null) { 47 | return result; 48 | } 49 | let id = this.findFile(transformed); 50 | if (id === undefined) { 51 | return null; 52 | } 53 | return FileStat.createFile(); 54 | } 55 | 56 | public readDirectory(uri: string): [string, FileType][] { 57 | return this.fileSystem.readDirectory(this.uriTransformer.toDatabase(uri)); 58 | } 59 | 60 | public readFileContent(uri: string): string | null { 61 | let transformed = this.uriTransformer.toDatabase(uri); 62 | let info = this.fileSystem.getFileInfo(transformed); 63 | if (info === undefined) { 64 | info = this.findFile(transformed); 65 | } 66 | if (info === undefined) { 67 | return null; 68 | } 69 | let result = this.fileContent(info); 70 | if (result === undefined) { 71 | return null; 72 | } 73 | return result; 74 | } 75 | 76 | protected abstract findFile(uri: string):{ id: Id; hash: string | undefined; } | undefined; 77 | 78 | protected abstract fileContent( info: { id: Id; hash: string | undefined; } ) : string | undefined; 79 | 80 | public abstract foldingRanges(uri: string): lsp.FoldingRange[] | undefined; 81 | 82 | public abstract documentSymbols(uri: string): lsp.DocumentSymbol[] | undefined; 83 | 84 | public abstract hover(uri: string, position: lsp.Position): lsp.Hover | undefined; 85 | 86 | public abstract declarations(uri: string, position: lsp.Position): lsp.Location | lsp.Location[] | undefined; 87 | 88 | public abstract definitions(uri: string, position: lsp.Position): lsp.Location | lsp.Location[] | undefined; 89 | 90 | public abstract references(uri: string, position: lsp.Position, context: lsp.ReferenceContext): lsp.Location[] | undefined; 91 | 92 | protected asDocumentSymbol(range: Range): lsp.DocumentSymbol | undefined { 93 | let tag = range.tag; 94 | if (tag === undefined || !(tag.type === 'declaration' || tag.type === 'definition')) { 95 | return undefined; 96 | } 97 | return lsp.DocumentSymbol.create( 98 | tag.text, tag.detail || '', tag.kind, 99 | tag.fullRange, this.asRange(range) 100 | ); 101 | } 102 | 103 | protected asRange(value: Range): lsp.Range { 104 | return { 105 | start: { 106 | line: value.start.line, 107 | character: value.start.character 108 | }, 109 | end: { 110 | line: value.end.line, 111 | character: value.end.character 112 | } 113 | }; 114 | } 115 | 116 | protected toDatabase(uri: string): string { 117 | return this.uriTransformer.toDatabase(uri); 118 | } 119 | 120 | protected fromDatabase(uri: string): string { 121 | return this.uriTransformer.fromDatabase(uri); 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /server/src/files.ts: -------------------------------------------------------------------------------- 1 | /* -------------------------------------------------------------------------------------------- 2 | * Copyright (c) Microsoft Corporation. All rights reserved. 3 | * Licensed under the MIT License. See License.txt in the project root for license information. 4 | * ------------------------------------------------------------------------------------------ */ 5 | import * as path from 'path'; 6 | 7 | import { Id } from 'lsif-protocol'; 8 | 9 | const ctime = Date.now(); 10 | const mtime = Date.now(); 11 | 12 | export namespace FileType { 13 | export const Unknown: 0 = 0; 14 | export const File: 1 = 1; 15 | export const Directory: 2 = 2; 16 | export const SymbolicLink: 64 = 64; 17 | } 18 | 19 | export type FileType = 0 | 1 | 2 | 64; 20 | 21 | export interface FileStat { 22 | type: FileType; 23 | ctime: number; 24 | mtime: number; 25 | size: number; 26 | } 27 | 28 | export namespace FileStat { 29 | export function createFile(): FileStat { 30 | return { type: FileType.File, ctime: ctime, mtime: mtime, size: 0 }; 31 | } 32 | } 33 | 34 | export interface DocumentInfo { 35 | id: Id; 36 | uri: string; 37 | hash: string; 38 | } 39 | 40 | interface File extends FileStat { 41 | type: 1; 42 | name: string; 43 | id: Id; 44 | hash: string; 45 | } 46 | 47 | namespace File { 48 | export function create(name: string, id: Id, hash: string): File { 49 | return { type: FileType.File, ctime: ctime, mtime: mtime, size: 0, name, id, hash }; 50 | } 51 | } 52 | 53 | interface Directory extends FileStat { 54 | type: 2; 55 | name: string; 56 | children: Map; 57 | } 58 | 59 | namespace Directory { 60 | export function create(name: string): Directory { 61 | return { type: FileType.Directory, ctime: Date.now(), mtime: Date.now(), size: 0, name, children: new Map() }; 62 | } 63 | } 64 | 65 | export type Entry = File | Directory; 66 | 67 | export class FileSystem { 68 | 69 | private workspaceRoot: string; 70 | private workspaceRootWithSlash: string; 71 | private filesOutsideWorkspaceRoot: Map; 72 | private root: Directory; 73 | 74 | constructor(workspaceRoot: string, documents: DocumentInfo[]) { 75 | if (workspaceRoot.charAt(workspaceRoot.length - 1) === '/') { 76 | this.workspaceRoot = workspaceRoot.substr(0, workspaceRoot.length - 1); 77 | this.workspaceRootWithSlash = workspaceRoot; 78 | } else { 79 | this.workspaceRoot = workspaceRoot; 80 | this.workspaceRootWithSlash = workspaceRoot + '/'; 81 | } 82 | this.root = Directory.create(''); 83 | this.filesOutsideWorkspaceRoot = new Map(); 84 | for (let info of documents) { 85 | // Do not show file outside the workspaceRoot. 86 | if (!info.uri.startsWith(this.workspaceRootWithSlash)) { 87 | this.filesOutsideWorkspaceRoot.set(info.uri, info); 88 | continue; 89 | } 90 | let p = info.uri.substring(workspaceRoot.length); 91 | let dirname = path.posix.dirname(p); 92 | let basename = path.posix.basename(p); 93 | let entry = this.lookup(dirname, true); 94 | if (entry && entry.type === FileType.Directory) { 95 | entry.children.set(basename, File.create(basename, info.id, info.hash)); 96 | } 97 | } 98 | } 99 | 100 | public stat(uri: string): FileStat | null { 101 | if (this.filesOutsideWorkspaceRoot.has(uri)) { 102 | return { type: FileType.File, ctime, mtime, size: 0 }; 103 | } 104 | let isRoot = this.workspaceRoot === uri; 105 | if (!uri.startsWith(this.workspaceRootWithSlash) && !isRoot) { 106 | return null; 107 | } 108 | let p = isRoot ? '' : uri.substring(this.workspaceRootWithSlash.length); 109 | let entry = this.lookup(p, false); 110 | return entry ? entry : null; 111 | } 112 | 113 | public readDirectory(uri: string): [string, FileType][] { 114 | let isRoot = this.workspaceRoot === uri; 115 | if (!uri.startsWith(this.workspaceRootWithSlash) && !isRoot) { 116 | return []; 117 | } 118 | let p = isRoot ? '' : uri.substring(this.workspaceRootWithSlash.length); 119 | let entry = this.lookup(p, false); 120 | if (entry === undefined || entry.type !== FileType.Directory) { 121 | return []; 122 | } 123 | let result: [string, FileType][] = []; 124 | for (let child of entry.children.values()) { 125 | result.push([child.name, child.type]); 126 | } 127 | return result; 128 | } 129 | 130 | public getFileInfo(uri: string): { id: Id, hash: string | undefined } | undefined { 131 | let result = this.filesOutsideWorkspaceRoot.get(uri); 132 | if (result !== undefined) { 133 | return result; 134 | } 135 | let isRoot = this.workspaceRoot === uri; 136 | if (!uri.startsWith(this.workspaceRootWithSlash) && !isRoot) { 137 | return undefined; 138 | } 139 | let entry = this.lookup(isRoot ? '' : uri.substring(this.workspaceRootWithSlash.length)); 140 | return entry && entry.type === FileType.File ? entry : undefined; 141 | } 142 | 143 | private lookup(uri: string, create: boolean = false): Entry | undefined { 144 | let parts = uri.split('/'); 145 | let entry: Entry = this.root; 146 | for (const part of parts) { 147 | if (!part || part === '.') { 148 | continue; 149 | } 150 | let child: Entry | undefined; 151 | if (entry.type === FileType.Directory) { 152 | child = entry.children.get(part); 153 | if (child === undefined && create) { 154 | child = Directory.create(part); 155 | entry.children.set(part, child); 156 | } 157 | } 158 | if (!child) { 159 | return undefined; 160 | } 161 | entry = child; 162 | } 163 | return entry; 164 | } 165 | } -------------------------------------------------------------------------------- /server/src/graphStore.ts: -------------------------------------------------------------------------------- 1 | /* -------------------------------------------------------------------------------------------- 2 | * Copyright (c) Microsoft Corporation. All rights reserved. 3 | * Licensed under the MIT License. See License.txt in the project root for license information. 4 | * ------------------------------------------------------------------------------------------ */ 5 | import * as Sqlite from 'better-sqlite3'; 6 | 7 | import * as lsp from 'vscode-languageserver'; 8 | 9 | import { Database, UriTransformer } from './database'; 10 | import { 11 | Id, EdgeLabels, DefinitionResult, FoldingRangeResult, DocumentSymbolResult, RangeBasedDocumentSymbol, Range, HoverResult, 12 | ReferenceResult, ItemEdgeProperties, DeclarationResult, Moniker, MonikerKind, VertexLabels, Vertex, Source 13 | } from 'lsif-protocol'; 14 | import { MetaData, CompressorDescription, CompressionKind } from './protocol.compress'; 15 | import { DocumentInfo } from './files'; 16 | import { URI } from 'vscode-uri'; 17 | 18 | interface DecompressorPropertyDescription { 19 | name: string; 20 | index: number; 21 | compressionKind: CompressionKind 22 | longForm?: Map; 23 | } 24 | 25 | class Decompressor { 26 | 27 | public static all: Map = new Map(); 28 | 29 | public static get(id: number): Decompressor | undefined { 30 | return this.all.get(id); 31 | } 32 | 33 | private id: number; 34 | private parentId: number | undefined; 35 | private parent: Decompressor | undefined; 36 | private properties: DecompressorPropertyDescription[]; 37 | 38 | constructor(description: CompressorDescription) { 39 | this.id = description.id; 40 | this.parentId = description.parent; 41 | this.properties = []; 42 | for (let item of description.properties) { 43 | let propertyDescription: DecompressorPropertyDescription = { 44 | name: item.name, 45 | index: item.index, 46 | compressionKind: item.compressionKind, 47 | longForm: undefined 48 | }; 49 | if (item.shortForm !== undefined) { 50 | propertyDescription.longForm = new Map(); 51 | for (let element of item.shortForm) { 52 | propertyDescription.longForm.set(element[1], element[0]); 53 | } 54 | } 55 | this.properties.push(propertyDescription); 56 | } 57 | Decompressor.all.set(this.id, this); 58 | } 59 | 60 | public link(): void { 61 | if (this.parentId !== undefined) { 62 | this.parent = Decompressor.get(this.parentId); 63 | } 64 | } 65 | 66 | public getPropertyDescription(name: string): DecompressorPropertyDescription | undefined { 67 | for (let item of this.properties) { 68 | if (item.name === name) { 69 | return item; 70 | } 71 | } 72 | return undefined; 73 | } 74 | 75 | public decompress(compressed: any[]): T { 76 | let result = this.parent !== undefined ? this.parent.decompress(compressed) : Object.create(null); 77 | for (let property of this.properties) { 78 | let index = property.index; 79 | let value = compressed[index]; 80 | if (value === null || value === undefined) { 81 | continue; 82 | } 83 | let decompressor: Decompressor | undefined; 84 | switch (property.compressionKind) { 85 | case CompressionKind.id: 86 | result[property.name] = value; 87 | break; 88 | case CompressionKind.ids: 89 | result[property.name] = value; 90 | break; 91 | case CompressionKind.raw: 92 | result[property.name] = value; 93 | break; 94 | case CompressionKind.scalar: 95 | let convertedScalar = value; 96 | if (property.longForm !== undefined) { 97 | let long = property.longForm.get(value); 98 | if (long !== undefined) { 99 | convertedScalar = long; 100 | } 101 | } 102 | let dotIndex = property.name.indexOf('.'); 103 | if (dotIndex !== -1) { 104 | let container = property.name.substr(0, dotIndex); 105 | let name = property.name.substring(dotIndex + 1); 106 | if (result[container] === undefined) { 107 | result[container] = Object.create(null); 108 | } 109 | result[container][name] = convertedScalar; 110 | } else { 111 | result[property.name] = convertedScalar; 112 | } 113 | break; 114 | case CompressionKind.literal: 115 | if (!Array.isArray(value) || typeof value[0] !== 'number') { 116 | throw new Error(`Compression kind literal detected on non array value. The property is ${property.name}`); 117 | } 118 | let convertedLiteral: any; 119 | decompressor = Decompressor.get(value[0]); 120 | if (decompressor === undefined) { 121 | throw new Error(`No decompression found for property ${property.name} and id ${value[0]}`); 122 | } 123 | convertedLiteral = decompressor.decompress(value); 124 | result[property.name] = convertedLiteral; 125 | break; 126 | case CompressionKind.array: 127 | if (!Array.isArray(value)) { 128 | throw new Error(`Compression kind array detected on non array value. The property is ${property.name}`); 129 | } 130 | let convertedArray: any[] = []; 131 | for (let element of value) { 132 | let type = typeof element; 133 | if (type === 'string' || type === 'number' || type === 'boolean') { 134 | convertedArray.push(element); 135 | } else if (Array.isArray(element) && element.length > 0 && typeof element[0] === 'number') { 136 | decompressor = Decompressor.get(element[0]); 137 | if (decompressor === undefined) { 138 | throw new Error(`No decompression found for property ${property.name} and id ${element[0]}`); 139 | } 140 | convertedArray.push(decompressor.decompress(element)); 141 | } else { 142 | throw new Error(`The array element is neither a scalar nor an array.`); 143 | } 144 | } 145 | result[property.name] = convertedArray; 146 | break; 147 | case CompressionKind.any: 148 | let convertedAny: any; 149 | let type = typeof value; 150 | if (type === 'string' || type === 'number' || type === 'boolean') { 151 | convertedAny = value; 152 | } else if (Array.isArray(value)) { 153 | convertedAny = []; 154 | for (let element of value) { 155 | let type = typeof element; 156 | if (type === 'string' || type === 'number' || type === 'boolean') { 157 | (convertedAny as any[]).push(element); 158 | } else if (Array.isArray(element) && element.length > 0 && typeof element[0] === 'number') { 159 | decompressor = Decompressor.get(element[0]); 160 | if (decompressor === undefined) { 161 | throw new Error(`No decompression found for property ${property.name} and id ${element[0]}`); 162 | } 163 | (convertedAny as any[]).push(decompressor.decompress(element)); 164 | } else { 165 | throw new Error(`The array element is neither a scalar nor an array.`); 166 | } 167 | } 168 | } 169 | if (convertedAny === undefined) { 170 | throw new Error(`Comression kind any can't be handled for property ${property.name}. Value is ${JSON.stringify(value)}`); 171 | } 172 | result[property.name] = convertedAny; 173 | break; 174 | default: 175 | throw new Error(`Compression kind ${property.compressionKind} unknown.`); 176 | } 177 | } 178 | return result; 179 | } 180 | } 181 | 182 | interface RangeResult { 183 | id: number; 184 | belongsTo: number; 185 | startLine: number; 186 | startCharacter: number; 187 | endLine: number; 188 | endCharacter: number; 189 | } 190 | 191 | interface MetaDataResult { 192 | id: number; 193 | value: string; 194 | } 195 | 196 | interface IdResult { 197 | id: Id; 198 | } 199 | 200 | interface LocationResult extends IdResult { 201 | uri: string; 202 | startLine: number; 203 | startCharacter: number; 204 | endLine: number; 205 | endCharacter: number; 206 | } 207 | 208 | interface LocationResultWithProperty extends LocationResult { 209 | property: number; 210 | } 211 | 212 | interface DocumentResult extends IdResult { 213 | label: number; 214 | value: string; 215 | } 216 | 217 | interface VertexResult extends IdResult { 218 | label: number; 219 | value: string; 220 | } 221 | 222 | interface ContentResult extends IdResult { 223 | content: string; 224 | } 225 | 226 | interface NextResult { 227 | inV: number; 228 | } 229 | 230 | interface PreviousResult { 231 | outV: number; 232 | } 233 | 234 | interface DocumentInfoResult extends IdResult { 235 | projectId: Id; 236 | uri: string; 237 | documentHash: string; 238 | } 239 | 240 | abstract class Retriever { 241 | 242 | private values: Id[]; 243 | 244 | public constructor(private name: string, private db: Sqlite.Database, private batchSize: number) { 245 | this.values= []; 246 | } 247 | 248 | public clear(): void { 249 | this.values = []; 250 | } 251 | 252 | public get isEmpty(): boolean { 253 | return this.values.length === 0; 254 | } 255 | 256 | public add(id: Id): void { 257 | this.values.push(id); 258 | } 259 | 260 | public addMany(ids: Id[]): void { 261 | this.values.push(...ids); 262 | } 263 | 264 | public run(): T[] { 265 | let result: T[] = new Array(this.values.length); 266 | let batch: Id[] = []; 267 | let mapping: Map = new Map(); 268 | for (let i = 0; i < this.values.length; i++) { 269 | let value = this.values[i]; 270 | batch.push(value); 271 | mapping.set(value, i); 272 | if (batch.length === this.batchSize) { 273 | this.retrieveBatch(result, batch, mapping); 274 | batch = []; 275 | mapping.clear(); 276 | } 277 | } 278 | if (batch.length > 0) { 279 | this.retrieveBatch(result, batch, mapping); 280 | } 281 | this.values = []; 282 | return result; 283 | } 284 | 285 | private retrieveBatch(result: T[], batch: Id[], mapping: Map): void { 286 | let stmt = batch.length === this.batchSize 287 | ? this.getFullStatement(this.batchSize) 288 | : this.getRestStatement(batch.length); 289 | 290 | let data: T[] = stmt.all(batch) as T[]; 291 | if (batch.length !== data.length) { 292 | throw new Error(`Couldn't retrieve all data for retriever ${this.name}`); 293 | } 294 | for (let element of data) { 295 | result[mapping.get(element.id)!] = element; 296 | } 297 | } 298 | 299 | protected prepare(stmt: string, size: number): Sqlite.Statement { 300 | return this.db.prepare(`${stmt} (${new Array(size).fill('?').join(',')})`); 301 | } 302 | 303 | protected abstract getFullStatement(size: number): Sqlite.Statement; 304 | 305 | protected abstract getRestStatement(size: number): Sqlite.Statement; 306 | } 307 | 308 | class VertexRetriever extends Retriever { 309 | 310 | private static statement: string = [ 311 | 'Select v.id, v.label, v.value from vertices v', 312 | 'Where v.id in' 313 | ].join(' '); 314 | 315 | private static preparedStatements: Map = new Map(); 316 | 317 | public constructor(db: Sqlite.Database, batchSize: number = 16) { 318 | super('VertexRetriever', db, batchSize); 319 | } 320 | 321 | protected getFullStatement(size: number): Sqlite.Statement { 322 | let result = VertexRetriever.preparedStatements.get(size); 323 | if (!result) { 324 | result = this.prepare(VertexRetriever.statement, size); 325 | VertexRetriever.preparedStatements.set(size, result); 326 | } 327 | return result; 328 | } 329 | 330 | protected getRestStatement(size: number): Sqlite.Statement { 331 | return this.prepare(VertexRetriever.statement, size); 332 | } 333 | } 334 | 335 | class LocationRetriever extends Retriever { 336 | 337 | private static statement: string = [ 338 | 'Select r.id, r.startLine, r.startCharacter, r.endLine, r.endCharacter, d.uri from ranges r', 339 | 'Inner Join documents d On r.belongsTo = d.id', 340 | 'Where r.id in' 341 | ].join(' '); 342 | 343 | private static preparedStatements: Map = new Map(); 344 | 345 | public constructor(db: Sqlite.Database, batchSize: number = 16) { 346 | super('LocationRetriever', db, batchSize); 347 | } 348 | 349 | protected getFullStatement(size: number): Sqlite.Statement { 350 | let result = LocationRetriever.preparedStatements.get(size); 351 | if (!result) { 352 | result = this.prepare(LocationRetriever.statement, size); 353 | LocationRetriever.preparedStatements.set(size, result); 354 | } 355 | return result; 356 | } 357 | 358 | protected getRestStatement(size: number): Sqlite.Statement { 359 | return this.prepare(LocationRetriever.statement, size); 360 | } 361 | } 362 | 363 | export class GraphStore extends Database { 364 | 365 | private db!: Sqlite.Database; 366 | 367 | private allDocumentsStmt!: Sqlite.Statement; 368 | private getDocumentContentStmt!: Sqlite.Statement; 369 | private findRangeStmt!: Sqlite.Statement; 370 | private findDocumentStmt!: Sqlite.Statement; 371 | private findResultStmt!: Sqlite.Statement; 372 | private findMonikerStmt!: Sqlite.Statement; 373 | private findMatchingMonikersStmt!: Sqlite.Statement; 374 | private findAttachedMonikersStmt!: Sqlite.Statement; 375 | private findNextMonikerStmt!: Sqlite.Statement; 376 | private findVertexIdForMonikerStmt!: Sqlite.Statement; 377 | private findNextVertexStmt!: Sqlite.Statement; 378 | private findPreviousVertexStmt!: Sqlite.Statement; 379 | private findResultForDocumentStmt!: Sqlite.Statement; 380 | private findRangeFromReferenceResult!: Sqlite.Statement; 381 | private findResultFromReferenceResult!: Sqlite.Statement; 382 | private findCascadesFromReferenceResult!: Sqlite.Statement; 383 | private findRangeFromResult!: Sqlite.Statement; 384 | 385 | private workspaceRoot!: URI; 386 | private vertexLabels: Map | undefined; 387 | private edgeLabels: Map | undefined; 388 | private itemEdgeProperties: Map | undefined; 389 | 390 | public constructor() { 391 | super(); 392 | } 393 | 394 | public load(file: string, transformerFactory: (workspaceRoot: string) => UriTransformer): Promise { 395 | this.db = new Sqlite(file, { readonly: true }); 396 | this.readMetaData(); 397 | this.readSource(); 398 | this.allDocumentsStmt = this.db.prepare('Select id, uri, documentHash From documents'); 399 | this.getDocumentContentStmt = this.db.prepare('Select content From contents Where documentHash = ?'); 400 | this.findDocumentStmt = this.db.prepare('Select id From documents Where uri = ?'); 401 | /* eslint-disable indent */ 402 | this.findRangeStmt = this.db.prepare([ 403 | 'Select r.id, r.belongsTo, r.startLine, r.startCharacter, r.endline, r.endCharacter From ranges r', 404 | 'Inner Join documents d On r.belongsTo = d.id', 405 | 'where', 406 | 'd.uri = $uri and (', 407 | '(r.startLine < $line and $line < r.endline) or', 408 | '(r.startLine = $line and r.startCharacter <= $character and $line < r.endline) or', 409 | '(r.startLine < $line and r.endLine = $line and $character <= r.endCharacter) or', 410 | '(r.startLine = $line and r.endLine = $line and r.startCharacter <= $character and $character <= r.endCharacter)', 411 | ')' 412 | ].join(' ')); 413 | /* eslint-enable indent */ 414 | const nextLabel = this.edgeLabels !== undefined ? this.edgeLabels.get(EdgeLabels.next)! : EdgeLabels.next; 415 | const monikerEdgeLabel = this.edgeLabels !== undefined ? this.edgeLabels.get(EdgeLabels.moniker)! : EdgeLabels.moniker; 416 | const monikerAttachLabel = this.edgeLabels !== undefined ? this.edgeLabels.get(EdgeLabels.attach)! : EdgeLabels.attach; 417 | 418 | this.findResultStmt = this.db.prepare([ 419 | 'Select v.id, v.label, v.value From vertices v', 420 | 'Inner Join edges e On e.inV = v.id', 421 | 'Where e.outV = $source and e.label = $label' 422 | ].join(' ')); 423 | 424 | this.findMonikerStmt = this.db.prepare([ 425 | 'Select v.id, v.label, v.value From vertices v', 426 | 'Inner Join edges e On e.inV = v.id', 427 | `Where e.outV = $source and e.label = ${monikerEdgeLabel}` 428 | ].join(' ')); 429 | this.findMatchingMonikersStmt = this.db.prepare([ 430 | 'Select v.id, v.label, v.value From vertices v', 431 | 'Inner Join monikers m on v.id = m.id', 432 | 'Where m.identifier = $identifier and m.scheme = $scheme and m.id != $exclude' 433 | ].join(' ')); 434 | this.findAttachedMonikersStmt = this.db.prepare([ 435 | 'Select v.id, v.label, v.value from vertices v', 436 | 'Inner Join edges e On e.outV = v.id', 437 | `Where e.inV = $source and e.label = ${monikerAttachLabel}` 438 | ].join(' ')); 439 | this.findNextMonikerStmt = this.db.prepare([ 440 | 'Select e.inV From edges e', 441 | `Where e.outV = $source and e.label = ${monikerAttachLabel}` 442 | ].join(' ')); 443 | this.findVertexIdForMonikerStmt = this.db.prepare([ 444 | 'Select v.id from vertices v', 445 | 'INNER Join edges e On v.id = e.outV', 446 | `Where e.label = ${monikerEdgeLabel} and e.inV = $id` 447 | ].join(' ')); 448 | 449 | this.findNextVertexStmt = this.db.prepare([ 450 | 'Select e.inV From edges e', 451 | `Where e.outV = $source and e.label = ${nextLabel}` 452 | ].join(' ')); 453 | this.findPreviousVertexStmt = this.db.prepare([ 454 | 'Select e.outV From edges e', 455 | `Where e.inV = $source and e.label = ${nextLabel}` 456 | ].join(' ')); 457 | 458 | this.findResultForDocumentStmt = this.db.prepare([ 459 | 'Select v.id, v.label, v.value from vertices v', 460 | 'Inner Join edges e On e.inV = v.id', 461 | 'Inner Join documents d On d.id = e.outV', 462 | 'Where d.uri = $uri and e.label = $label' 463 | ].join(' ')); 464 | 465 | this.findRangeFromResult = this.db.prepare([ 466 | 'Select r.id, r.startLine, r.startCharacter, r.endLine, r.endCharacter, d.uri from ranges r', 467 | 'Inner Join items i On i.inV = r.id', 468 | 'Inner Join documents d On r.belongsTo = d.id', 469 | 'Where i.outV = $id' 470 | ].join(' ')); 471 | this.findRangeFromReferenceResult = this.db.prepare([ 472 | 'Select r.id, r.startLine, r.startCharacter, r.endLine, r.endCharacter, i.property, d.uri from ranges r', 473 | 'Inner Join items i On i.inV = r.id', 474 | 'Inner Join documents d On r.belongsTo = d.id', 475 | 'Where i.outV = $id and (i.property in (1, 2, 3))' 476 | ].join(' ')); 477 | this.findResultFromReferenceResult = this.db.prepare([ 478 | 'Select v.id, v.label, v.value from vertices v', 479 | 'Inner Join items i On i.inV = v.id', 480 | 'Where i.outV = $id and i.property = 4' 481 | ].join(' ')); 482 | this.findCascadesFromReferenceResult = this.db.prepare([ 483 | 'Select v.id, v.label, v.value from vertices v', 484 | 'Inner Join items i On i.inV = v.id', 485 | 'Where i.outV = $id and i.property = 5' 486 | ].join(' ')); 487 | this.initialize(transformerFactory); 488 | return Promise.resolve(); 489 | } 490 | 491 | private readMetaData(): void { 492 | let result: MetaDataResult[] = this.db.prepare('Select * from meta').all() as MetaDataResult[]; 493 | if (result === undefined || result.length !== 1) { 494 | throw new Error('Failed to read meta data record.'); 495 | } 496 | let metaData: MetaData = JSON.parse(result[0].value); 497 | if (metaData.compressors !== undefined) { 498 | this.vertexLabels = new Map(); 499 | this.edgeLabels = new Map(); 500 | this.itemEdgeProperties = new Map(); 501 | for (let decription of metaData.compressors.all) { 502 | new Decompressor(decription); 503 | } 504 | for (let element of Decompressor.all.values()) { 505 | element.link(); 506 | } 507 | // Vertex Compressor 508 | let decompressor = Decompressor.get(metaData.compressors.vertexCompressor); 509 | if (decompressor === undefined) { 510 | throw new Error('No vertex decompressor found.'); 511 | } 512 | let description = decompressor.getPropertyDescription('label'); 513 | if (description === undefined || description.longForm === undefined) { 514 | throw new Error('No vertex label property description found.'); 515 | } 516 | for (let item of description.longForm) { 517 | this.vertexLabels.set(item[1], item[0] as number); 518 | } 519 | // Edge Compressor 520 | decompressor = Decompressor.get(metaData.compressors.edgeCompressor); 521 | if (decompressor === undefined) { 522 | throw new Error('No edge decompressor found.'); 523 | } 524 | description = decompressor.getPropertyDescription('label'); 525 | if (description === undefined || description.longForm === undefined) { 526 | throw new Error('No edge label property description found.'); 527 | } 528 | for (let item of description.longForm) { 529 | this.edgeLabels.set(item[1], item[0] as number); 530 | } 531 | // Item edge Compressor 532 | decompressor = Decompressor.get(metaData.compressors.itemEdgeCompressor); 533 | if (decompressor === undefined) { 534 | throw new Error('No item edge decompressor found.'); 535 | } 536 | description = decompressor.getPropertyDescription('property'); 537 | if (description === undefined || description.longForm === undefined) { 538 | throw new Error('No item property description found.'); 539 | } 540 | for (let item of description.longForm) { 541 | this.itemEdgeProperties.set(item[1], item[0] as number); 542 | } 543 | } 544 | } 545 | 546 | private readSource(): void { 547 | const sourceLabel = this.vertexLabels !== undefined ? this.vertexLabels.get(VertexLabels.source): VertexLabels.source; 548 | const source: Source = this.decompress(JSON.parse((this.db.prepare(`Select v.value from vertices v where v.label = ${sourceLabel}`).get() as any).value)); 549 | if (source !== undefined) { 550 | this.workspaceRoot = URI.parse(source.workspaceRoot); 551 | } 552 | } 553 | 554 | public getWorkspaceRoot(): URI { 555 | return this.workspaceRoot; 556 | } 557 | 558 | public close(): void { 559 | this.db.close(); 560 | } 561 | 562 | protected getDocumentInfos(): DocumentInfo[] { 563 | let result: DocumentInfoResult[] = this.allDocumentsStmt.all() as DocumentInfoResult[]; 564 | if (result === undefined) { 565 | return []; 566 | } 567 | return result.map((item) => { return { id: item.id, uri: item.uri, hash: item.documentHash }; }); 568 | } 569 | 570 | protected findFile(uri: string): { id: Id, hash: string | undefined } | undefined { 571 | let result = this.findDocumentStmt.get(uri) as any; 572 | return result; 573 | } 574 | 575 | protected fileContent(info: { id: Id, hash: string | undefined }): string { 576 | let result: ContentResult = this.getDocumentContentStmt.get(info.hash) as ContentResult; 577 | if (!result || !result.content) { 578 | return ''; 579 | } 580 | return Buffer.from(result.content).toString('base64'); 581 | } 582 | 583 | public foldingRanges(uri: string): lsp.FoldingRange[] | undefined { 584 | let foldingResult = this.getResultForDocument(this.toDatabase(uri), EdgeLabels.textDocument_foldingRange); 585 | if (foldingResult === undefined) { 586 | return undefined; 587 | } 588 | return foldingResult.result; 589 | } 590 | 591 | public documentSymbols(uri: string): lsp.DocumentSymbol[] | undefined { 592 | let symbolResult = this.getResultForDocument(this.toDatabase(uri), EdgeLabels.textDocument_documentSymbol); 593 | if (symbolResult === undefined) { 594 | return undefined; 595 | } 596 | if (symbolResult.result.length === 0) { 597 | return []; 598 | } 599 | if (lsp.DocumentSymbol.is(symbolResult.result[0])) { 600 | return symbolResult.result as lsp.DocumentSymbol[]; 601 | } else { 602 | const vertexRetriever = new VertexRetriever(this.db, 16); 603 | let collectRanges = (element: RangeBasedDocumentSymbol) => { 604 | vertexRetriever.add(element.id); 605 | if (element.children) { 606 | element.children.forEach(collectRanges); 607 | } 608 | }; 609 | let convert = (result: lsp.DocumentSymbol[], elements: RangeBasedDocumentSymbol[], ranges: Map) => { 610 | for (let element of elements) { 611 | let range = ranges.get(element.id); 612 | if (range !== undefined) { 613 | let symbol: lsp.DocumentSymbol | undefined = this.asDocumentSymbol(range); 614 | if (symbol) { 615 | result.push(symbol); 616 | if (element.children !== undefined && element.children.length > 0) { 617 | symbol.children = []; 618 | convert(symbol.children, element.children, ranges); 619 | } 620 | } 621 | } 622 | } 623 | }; 624 | (symbolResult.result as RangeBasedDocumentSymbol[]).forEach(collectRanges); 625 | let data = vertexRetriever.run(); 626 | let ranges: Map = new Map(); 627 | for (let element of data) { 628 | let range: Range = this.decompress(JSON.parse(element.value)); 629 | if (range) { 630 | ranges.set(range.id, range); 631 | } 632 | } 633 | let result: lsp.DocumentSymbol[] = []; 634 | convert(result, symbolResult.result as RangeBasedDocumentSymbol[], ranges); 635 | return result; 636 | } 637 | } 638 | 639 | public hover(uri: string, position: lsp.Position): lsp.Hover | undefined { 640 | const ranges = this.findRange(this.toDatabase(uri), position); 641 | if (ranges === undefined) { 642 | return undefined; 643 | } 644 | 645 | const findHover = (range: RangeResult): lsp.Hover | undefined => { 646 | const [hoverResult, anchorId] = this.getResultForId(range.id, EdgeLabels.textDocument_hover); 647 | if (hoverResult === undefined || hoverResult.result === undefined) { 648 | return undefined; 649 | } 650 | const result: lsp.Hover = Object.assign(Object.create(null), hoverResult.result); 651 | if (result.range === undefined) { 652 | result.range = { 653 | start: { 654 | line: range.startLine, 655 | character: range.startCharacter 656 | }, 657 | end: { 658 | line: range.endLine, 659 | character: range.endCharacter 660 | } 661 | }; 662 | } 663 | return result; 664 | }; 665 | 666 | let result: lsp.Hover | undefined; 667 | for (const range of ranges) { 668 | result = findHover(range); 669 | if (result !== undefined) { 670 | break; 671 | } 672 | } 673 | if (result === undefined) { 674 | return undefined; 675 | } 676 | 677 | // Workaround to remove empty object. Need to find out why they are in the dump 678 | // in the first place. 679 | if (Array.isArray(result.contents)) { 680 | for (let i = 0; i < result.contents.length;) { 681 | const elem = result.contents[i]; 682 | if (typeof elem !== 'string' && elem.language === undefined && elem.value === undefined) { 683 | result.contents.splice(i, 1); 684 | } else { 685 | i++; 686 | } 687 | } 688 | } 689 | return result; 690 | } 691 | 692 | public declarations(uri: string, position: lsp.Position): lsp.Location | lsp.Location[] | undefined { 693 | const ranges = this.findRange(this.toDatabase(uri), position); 694 | if (ranges === undefined) { 695 | return undefined; 696 | } 697 | 698 | const findDeclaration = (range: RangeResult): lsp.Location | lsp.Location[] | undefined => { 699 | const [declarationResult] = this.getResultForId(range.id, EdgeLabels.textDocument_declaration); 700 | if (declarationResult === undefined) { 701 | return undefined; 702 | } 703 | 704 | const result: lsp.Location[] = []; 705 | const queryResult: LocationResult[] = this.findRangeFromResult.all({ id: declarationResult.id }) as LocationResult[]; 706 | if (queryResult && queryResult.length > 0) { 707 | for(let item of queryResult) { 708 | result.push(this.createLocation(item)); 709 | } 710 | } 711 | return result; 712 | }; 713 | 714 | for (const range of ranges) { 715 | const result = findDeclaration(range); 716 | if (result !== undefined) { 717 | return result; 718 | } 719 | } 720 | return undefined; 721 | } 722 | 723 | public definitions(uri: string, position: lsp.Position): lsp.Location | lsp.Location[] | undefined { 724 | const ranges = this.findRange(this.toDatabase(uri), position); 725 | if (ranges === undefined) { 726 | return undefined; 727 | } 728 | 729 | const findDefinitions = (range: RangeResult): lsp.Location | lsp.Location[] | undefined => { 730 | const [definitionResult] = this.getResultForId(range.id, EdgeLabels.textDocument_definition); 731 | if (definitionResult === undefined) { 732 | return undefined; 733 | } 734 | 735 | const result: lsp.Location[] = []; 736 | const queryResult: LocationResult[] = this.findRangeFromResult.all({ id: definitionResult.id }) as LocationResult[]; 737 | if (queryResult && queryResult.length > 0) { 738 | for(let item of queryResult) { 739 | result.push(this.createLocation(item)); 740 | } 741 | } 742 | return result; 743 | }; 744 | 745 | for (const range of ranges) { 746 | const result = findDefinitions(range); 747 | if (result !== undefined) { 748 | return result; 749 | } 750 | } 751 | return undefined; 752 | } 753 | 754 | public references(uri: string, position: lsp.Position, context: lsp.ReferenceContext): lsp.Location[] | undefined { 755 | const ranges = this.findRange(this.toDatabase(uri), position); 756 | if (ranges === undefined) { 757 | return undefined; 758 | } 759 | 760 | const result: lsp.Location[] = []; 761 | const monikers: Map = new Map(); 762 | const dedupRanges = new Set(); 763 | 764 | const findReferences = (result: lsp.Location[], dedupRanges: Set, monikers: Map, range: RangeResult): void => { 765 | const [referenceResult, anchorId] = this.getResultForId(range.id, EdgeLabels.textDocument_references); 766 | if (referenceResult === undefined) { 767 | return undefined; 768 | } 769 | 770 | this.resolveReferenceResult(result, dedupRanges, monikers, referenceResult, context); 771 | this.findMonikersForVertex(monikers, anchorId); 772 | for (const moniker of monikers.values()) { 773 | if (moniker.kind === MonikerKind.local) { 774 | continue; 775 | } 776 | const matchingMonikers = this.findMatchingMonikers(moniker); 777 | for (const matchingMoniker of matchingMonikers) { 778 | const vertexId = this.findVertexIdForMoniker(matchingMoniker); 779 | if (vertexId === undefined) { 780 | continue; 781 | } 782 | const [referenceResult] = this.getResultForId(vertexId, EdgeLabels.textDocument_references); 783 | if (referenceResult === undefined) { 784 | continue; 785 | } 786 | this.resolveReferenceResult(result, dedupRanges, monikers, referenceResult, context); 787 | } 788 | } 789 | }; 790 | 791 | for (const range of ranges) { 792 | findReferences(result, dedupRanges, monikers, range); 793 | } 794 | 795 | return result; 796 | } 797 | 798 | private resolveReferenceResult(result: lsp.Location[], dedupRanges: Set, monikers: Map, referenceResult: ReferenceResult, context: lsp.ReferenceContext): void { 799 | const qr: LocationResultWithProperty[] = this.findRangeFromReferenceResult.all({ id: referenceResult.id }) as LocationResultWithProperty[]; 800 | if (qr && qr.length > 0) { 801 | const refLabel = this.getItemEdgeProperty(ItemEdgeProperties.references); 802 | for (const item of qr) { 803 | if (item.property === refLabel || context.includeDeclaration && !dedupRanges.has(item.id)) { 804 | dedupRanges.add(item.id); 805 | result.push(this.createLocation(item)); 806 | } 807 | } 808 | } 809 | 810 | const mr: VertexResult[] = this.findCascadesFromReferenceResult.all({ id: referenceResult.id }) as VertexResult[]; 811 | if (mr) { 812 | for (const moniker of mr) { 813 | if (!monikers.has(moniker.id)) { 814 | monikers.set(moniker.id, this.decompress(JSON.parse(moniker.value))); 815 | } 816 | } 817 | } 818 | 819 | const rqr: VertexResult[] = this.findResultFromReferenceResult.all({ id: referenceResult.id }) as VertexResult[]; 820 | if (rqr && rqr.length > 0) { 821 | for (const item of rqr) { 822 | this.resolveReferenceResult(result, dedupRanges, monikers, this.decompress(JSON.parse(item.value)), context); 823 | } 824 | } 825 | } 826 | 827 | private findMonikersForVertex(monikers: Map, id: Id): void { 828 | let currentId: Id = id; 829 | let moniker: VertexResult | undefined; 830 | do { 831 | moniker = this.findMonikerStmt.get({ source: currentId }) as VertexResult | undefined; 832 | if (moniker !== undefined) { 833 | break; 834 | } 835 | const previous: PreviousResult = this.findPreviousVertexStmt.get({ source: currentId }) as PreviousResult; 836 | if (previous === undefined) { 837 | moniker = undefined; 838 | break; 839 | } 840 | currentId = previous.outV; 841 | } while (currentId !== undefined); 842 | if (moniker === undefined) { 843 | return; 844 | } 845 | const result: Moniker[] = [this.decompress(JSON.parse(moniker.value))]; 846 | for (const moniker of result) { 847 | monikers.set(moniker.id, moniker); 848 | const attachedMonikersResult: VertexResult[] = this.findAttachedMonikersStmt.all({ source: moniker.id }) as VertexResult[]; 849 | for (const attachedMonikerResult of attachedMonikersResult) { 850 | const attachedMoniker: Moniker = this.decompress(JSON.parse(attachedMonikerResult.value)); 851 | monikers.set(attachedMoniker.id, attachedMoniker); 852 | } 853 | } 854 | } 855 | 856 | private findMatchingMonikers(moniker: Moniker): Moniker[] { 857 | const results: VertexResult[] = this.findMatchingMonikersStmt.all({ identifier: moniker.identifier, scheme: moniker.scheme, exclude: moniker.id }) as VertexResult[]; 858 | return results.map(vertex => this.decompress(JSON.parse(vertex.value))); 859 | } 860 | 861 | private findVertexIdForMoniker(moniker: Moniker): Id | undefined { 862 | let currentId: Id = moniker.id; 863 | do { 864 | const next: NextResult = this.findNextMonikerStmt.get({ source: currentId }) as NextResult; 865 | if (next === undefined) { 866 | break; 867 | } 868 | currentId = next.inV; 869 | } while (currentId !== undefined); 870 | if (currentId === undefined) { 871 | return; 872 | } 873 | const result: IdResult = this.findVertexIdForMonikerStmt.get({ id: currentId }) as IdResult; 874 | return result !== undefined ? result.id : undefined; 875 | } 876 | 877 | private findRange(uri: string, position: lsp.Position): RangeResult[] | undefined { 878 | let dbResult: RangeResult[] = this.findRangeStmt.all({ uri: uri, line: position.line, character: position.character}) as RangeResult[]; 879 | if (dbResult === undefined || dbResult.length === 0) { 880 | return undefined; 881 | } 882 | function sameRange(a: RangeResult, b: RangeResult): boolean { 883 | return a.startLine === b.startLine && a.startCharacter === b.startCharacter && a.endLine === b.endLine && a.endCharacter === b.endCharacter; 884 | } 885 | // Do to the indecies we use the items in the db result are sorted descending. 886 | const result: RangeResult[] = []; 887 | const last = dbResult[dbResult.length - 1]; 888 | const belongsTo: Set = new Set(); 889 | result.push(last); 890 | belongsTo.add(last.belongsTo); 891 | for (let i = result.length - 2; i >= 0; i--) { 892 | const candidate = dbResult[i]; 893 | if (!belongsTo.has(candidate.belongsTo) && sameRange(last, candidate)) { 894 | result.push(candidate); 895 | } else { 896 | break; 897 | } 898 | } 899 | return result; 900 | } 901 | 902 | private getResultForId(id: Id, label: EdgeLabels.textDocument_hover): [HoverResult | undefined, Id]; 903 | private getResultForId(id: Id, label: EdgeLabels.textDocument_declaration): [DeclarationResult | undefined, Id]; 904 | private getResultForId(id: Id, label: EdgeLabels.textDocument_definition): [DefinitionResult | undefined, Id]; 905 | private getResultForId(id: Id, label: EdgeLabels.textDocument_references): [ReferenceResult | undefined, Id]; 906 | private getResultForId(id: Id, label: EdgeLabels): [any | undefined, Id] { 907 | let currentId = id; 908 | let result: VertexResult | undefined; 909 | do { 910 | result = this.findResultStmt.get({ source: currentId, label: this.getEdgeLabel(label)}) as VertexResult | undefined; 911 | if (result !== undefined) { 912 | break; 913 | } 914 | const next: NextResult = this.findNextVertexStmt.get({ source: currentId }) as NextResult; 915 | if (next === undefined) { 916 | result = undefined; 917 | break; 918 | } 919 | currentId = next.inV; 920 | } while (currentId !== undefined); 921 | if (result === undefined) { 922 | return [undefined, currentId]; 923 | } 924 | return [this.decompress(JSON.parse(result.value)), currentId]; 925 | } 926 | 927 | private getEdgeLabel(label: EdgeLabels): EdgeLabels | number { 928 | if (this.edgeLabels === undefined) { 929 | return label; 930 | } 931 | let result = this.edgeLabels.get(label); 932 | return result !== undefined ? result : label; 933 | } 934 | 935 | private getItemEdgeProperty(prop: ItemEdgeProperties): ItemEdgeProperties | number { 936 | if (this.itemEdgeProperties === undefined) { 937 | return prop; 938 | } 939 | let result = this.itemEdgeProperties.get(prop); 940 | return result !== undefined ? result : prop; 941 | } 942 | 943 | private asLocations(values: (Id | lsp.Location)[]): lsp.Location[] { 944 | let mapping: Map = new Map(); 945 | let ids: Id[] = []; 946 | let result: lsp.Location[] = new Array(values.length); 947 | for (let i = 0; i < values.length; i++) { 948 | let element = values[i]; 949 | if (lsp.Location.is(element)) { 950 | result[i] = element; 951 | } else { 952 | mapping.set(element, i); 953 | ids.push(element); 954 | } 955 | } 956 | if (ids.length > 0) { 957 | const locationRetriever = new LocationRetriever(this.db); 958 | locationRetriever.addMany(ids); 959 | let data: LocationResult[] = locationRetriever.run(); 960 | for (let element of data) { 961 | result[mapping.get(element.id)!] = this.createLocation(element); 962 | } 963 | } 964 | return result; 965 | } 966 | 967 | private asLocation(value: Id | lsp.Location): lsp.Location { 968 | if (lsp.Location.is(value)) { 969 | return { range: value.range, uri: this.fromDatabase(value.uri)}; 970 | } else { 971 | const locationRetriever = new LocationRetriever(this.db, 1); 972 | locationRetriever.add(value); 973 | let data: LocationResult = locationRetriever.run()[0]; 974 | return this.createLocation(data); 975 | } 976 | } 977 | 978 | private createLocation(data: LocationResult): lsp.Location { 979 | return lsp.Location.create(this.fromDatabase(data.uri), lsp.Range.create(data.startLine, data.startCharacter, data.endLine, data.endCharacter)); 980 | } 981 | 982 | private getResultForDocument(uri: string, label: EdgeLabels.textDocument_documentSymbol): DocumentSymbolResult | undefined; 983 | private getResultForDocument(uri: string, label: EdgeLabels.textDocument_foldingRange): FoldingRangeResult | undefined; 984 | private getResultForDocument(uri: string, label: EdgeLabels): any | undefined { 985 | let data: DocumentResult = this.findResultForDocumentStmt.get({ uri, label: this.getEdgeLabel(label) }) as DocumentResult; 986 | if (data === undefined) { 987 | return undefined; 988 | } 989 | return this.decompress(JSON.parse(data.value)); 990 | } 991 | 992 | private decompress(value: any): any { 993 | if (Array.isArray(value)) { 994 | let decompressor = Decompressor.get(value[0]); 995 | if (decompressor) { 996 | return decompressor.decompress(value); 997 | } 998 | } 999 | return value; 1000 | } 1001 | } -------------------------------------------------------------------------------- /server/src/is.ts: -------------------------------------------------------------------------------- 1 | /* -------------------------------------------------------------------------------------------- 2 | * Copyright (c) Microsoft Corporation. All rights reserved. 3 | * Licensed under the MIT License. See License.txt in the project root for license information. 4 | * ------------------------------------------------------------------------------------------ */ 5 | 'use strict'; 6 | 7 | export function string(value: any): value is string { 8 | return typeof value === 'string' || value instanceof String; 9 | } 10 | 11 | export function number(value: any): value is number { 12 | return typeof value === 'number' || value instanceof Number; 13 | } -------------------------------------------------------------------------------- /server/src/jsonStore.ts: -------------------------------------------------------------------------------- 1 | /* -------------------------------------------------------------------------------------------- 2 | * Copyright (c) Microsoft Corporation. All rights reserved. 3 | * Licensed under the MIT License. See License.txt in the project root for license information. 4 | * ------------------------------------------------------------------------------------------ */ 5 | import * as fs from 'fs'; 6 | import * as crypto from 'crypto'; 7 | import * as readline from 'readline'; 8 | 9 | import { URI } from 'vscode-uri'; 10 | import * as SemVer from 'semver'; 11 | 12 | import * as lsp from 'vscode-languageserver'; 13 | import { 14 | Id, Vertex, Project, Document, Range, DiagnosticResult, DocumentSymbolResult, FoldingRangeResult, DocumentLinkResult, DefinitionResult, 15 | TypeDefinitionResult, HoverResult, ReferenceResult, ImplementationResult, Edge, RangeBasedDocumentSymbol, DeclarationResult, ResultSet, 16 | ElementTypes, VertexLabels, EdgeLabels, ItemEdgeProperties, EventScope, EventKind, ProjectEvent, Moniker as PMoniker, moniker, MonikerKind 17 | } from 'lsif-protocol'; 18 | 19 | import { DocumentInfo } from './files'; 20 | import { Database, UriTransformer } from './database'; 21 | 22 | interface Moniker extends PMoniker { 23 | key: string; 24 | } 25 | 26 | interface Vertices { 27 | all: Map; 28 | projects: Map; 29 | documents: Map; 30 | ranges: Map; 31 | } 32 | 33 | type ItemTarget = 34 | Range | 35 | { type: ItemEdgeProperties.declarations; range: Range; } | 36 | { type: ItemEdgeProperties.definitions; range: Range; } | 37 | { type: ItemEdgeProperties.references; range: Range; } | 38 | { type: ItemEdgeProperties.referenceResults; result: ReferenceResult; } | 39 | { type: ItemEdgeProperties.referenceLinks; result: Moniker; }; 40 | 41 | interface Out { 42 | contains: Map; 43 | item: Map; 44 | next: Map; 45 | moniker: Map; 46 | documentSymbol: Map; 47 | foldingRange: Map; 48 | documentLink: Map; 49 | diagnostic: Map; 50 | declaration: Map; 51 | definition: Map; 52 | typeDefinition: Map; 53 | hover: Map; 54 | references: Map; 55 | implementation: Map; 56 | } 57 | 58 | interface In { 59 | contains: Map; 60 | moniker: Map; 61 | } 62 | 63 | interface Indices { 64 | monikers: Map; 65 | contents: Map; 66 | documents: Map; 67 | } 68 | 69 | interface ResultPath { 70 | path: { vertex: Id, moniker: Moniker | undefined }[]; 71 | result: { value: T, moniker: Moniker | undefined } | undefined; 72 | } 73 | 74 | namespace Locations { 75 | export function makeKey(location: lsp.Location): string { 76 | const range = location.range; 77 | return crypto.createHash('md5').update(JSON.stringify({ d: location.uri, sl: range.start.line, sc: range.start.character, el: range.end.line, ec: range.end.character }, undefined, 0)).digest('base64'); 78 | } 79 | } 80 | 81 | export class JsonStore extends Database { 82 | 83 | private version: string | undefined; 84 | private workspaceRoot!: URI; 85 | private activeGroup: Id | undefined; 86 | private activeProject: Id | undefined; 87 | 88 | private vertices: Vertices; 89 | private indices: Indices; 90 | private out: Out; 91 | private in: In; 92 | 93 | constructor() { 94 | super(); 95 | this.vertices = { 96 | all: new Map(), 97 | projects: new Map(), 98 | documents: new Map(), 99 | ranges: new Map() 100 | }; 101 | 102 | this.indices = { 103 | contents: new Map(), 104 | documents: new Map(), 105 | monikers: new Map(), 106 | }; 107 | 108 | this.out = { 109 | contains: new Map(), 110 | item: new Map(), 111 | next: new Map(), 112 | moniker: new Map(), 113 | documentSymbol: new Map(), 114 | foldingRange: new Map(), 115 | documentLink: new Map(), 116 | diagnostic: new Map(), 117 | declaration: new Map(), 118 | definition: new Map(), 119 | typeDefinition: new Map(), 120 | hover: new Map(), 121 | references: new Map(), 122 | implementation: new Map() 123 | }; 124 | 125 | this.in = { 126 | contains: new Map(), 127 | moniker: new Map() 128 | }; 129 | } 130 | 131 | public load(file: string, transformerFactory: (workspaceRoot: string) => UriTransformer): Promise { 132 | return new Promise((resolve, reject) => { 133 | const input: fs.ReadStream = fs.createReadStream(file, { encoding: 'utf8'}); 134 | input.on('error', reject); 135 | const rd = readline.createInterface(input); 136 | rd.on('line', (line: string) => { 137 | if (!line || line.length === 0) { 138 | return; 139 | } 140 | try { 141 | const element: Edge | Vertex = JSON.parse(line); 142 | switch (element.type) { 143 | case ElementTypes.vertex: 144 | this.processVertex(element); 145 | break; 146 | case ElementTypes.edge: 147 | this.processEdge(element); 148 | break; 149 | } 150 | } catch (error) { 151 | input.destroy(); 152 | reject(error); 153 | } 154 | }); 155 | rd.on('close', () => { 156 | if (this.workspaceRoot === undefined) { 157 | reject(new Error('No project root provided.')); 158 | return; 159 | } 160 | if (this.version === undefined) { 161 | reject(new Error('No version found.')); 162 | return; 163 | } else { 164 | const semVer = SemVer.parse(this.version); 165 | if (!semVer) { 166 | reject(new Error(`No valid semantic version string. The version is: ${this.version}`)); 167 | return; 168 | } 169 | const range: SemVer.Range = new SemVer.Range('>0.5.99 <=0.6.0-next.4'); 170 | range.includePrerelease = true; 171 | if (!SemVer.satisfies(semVer, range)) { 172 | reject(new Error(`Requires version range >0.5.99 <=0.6.0-next.4 but received: ${this.version}`)); 173 | return; 174 | } 175 | } 176 | resolve(); 177 | }); 178 | }).then(() => { 179 | this.initialize(transformerFactory); 180 | }); 181 | } 182 | 183 | public getWorkspaceRoot(): URI { 184 | return this.workspaceRoot; 185 | } 186 | 187 | public close(): void { 188 | } 189 | 190 | private processVertex(vertex: Vertex): void { 191 | this.vertices.all.set(vertex.id, vertex); 192 | switch(vertex.label) { 193 | case VertexLabels.metaData: 194 | this.version = vertex.version; 195 | break; 196 | case VertexLabels.source: 197 | this.workspaceRoot = URI.parse(vertex.workspaceRoot); 198 | break; 199 | case VertexLabels.project: 200 | this.vertices.projects.set(vertex.id, vertex); 201 | break; 202 | case VertexLabels.event: 203 | if (vertex.kind === EventKind.begin) { 204 | switch (vertex.scope) { 205 | case EventScope.project: 206 | this.activeProject = (vertex as ProjectEvent).data; 207 | break; 208 | } 209 | } 210 | break; 211 | case VertexLabels.document: 212 | this.doProcessDocument(vertex); 213 | break; 214 | case VertexLabels.moniker: 215 | if (vertex.kind !== MonikerKind.local) { 216 | const key = crypto.createHash('md5').update(JSON.stringify({ s: vertex.scheme, i: vertex.identifier }, undefined, 0)).digest('base64'); 217 | (vertex as Moniker).key = key; 218 | let values = this.indices.monikers.get(key); 219 | if (values === undefined) { 220 | values = []; 221 | this.indices.monikers.set(key, values); 222 | } 223 | values.push(vertex as Moniker); 224 | } 225 | break; 226 | case VertexLabels.range: 227 | this.vertices.ranges.set(vertex.id, vertex); 228 | break; 229 | } 230 | } 231 | 232 | private doProcessDocument(document: Document): void { 233 | const contents = document.contents !== undefined ? document.contents : 'No content provided.'; 234 | this.vertices.documents.set(document.id, document); 235 | const hash = crypto.createHash('md5').update(contents).digest('base64'); 236 | this.indices.contents.set(hash, contents); 237 | 238 | let value = this.indices.documents.get(document.uri); 239 | if (value === undefined) { 240 | value = { hash, documents: [] }; 241 | this.indices.documents.set(document.uri, value); 242 | } 243 | if (hash !== value.hash) { 244 | console.error(`Document ${document.uri} has different content.`); 245 | } 246 | value.documents.push(document); 247 | } 248 | 249 | private processEdge(edge: Edge): void { 250 | let property: ItemEdgeProperties | undefined; 251 | if (edge.label === 'item') { 252 | property = edge.property; 253 | } 254 | if (Edge.is11(edge)) { 255 | this.doProcessEdge(edge.label, edge.outV, edge.inV, property); 256 | } else if (Edge.is1N(edge)) { 257 | for (let inV of edge.inVs) { 258 | this.doProcessEdge(edge.label, edge.outV, inV, property); 259 | } 260 | } 261 | } 262 | 263 | private doProcessEdge(label: EdgeLabels, outV: Id, inV: Id, property?: ItemEdgeProperties): void { 264 | const from: Vertex | undefined = this.vertices.all.get(outV); 265 | const to: Vertex | undefined = this.vertices.all.get(inV); 266 | if (from === undefined) { 267 | throw new Error(`No vertex found for Id ${outV}`); 268 | } 269 | if (to === undefined) { 270 | throw new Error(`No vertex found for Id ${inV}`); 271 | } 272 | let values: any[] | undefined; 273 | switch (label) { 274 | case EdgeLabels.contains: 275 | values = this.out.contains.get(from.id); 276 | if (values === undefined) { 277 | values = [ to as any ]; 278 | this.out.contains.set(from.id, values); 279 | } else { 280 | values.push(to); 281 | } 282 | this.in.contains.set(to.id, from as any); 283 | break; 284 | case EdgeLabels.item: 285 | values = this.out.item.get(from.id); 286 | let itemTarget: ItemTarget | undefined; 287 | if (property !== undefined) { 288 | switch (property) { 289 | case ItemEdgeProperties.references: 290 | itemTarget = { type: property, range: to as Range }; 291 | break; 292 | case ItemEdgeProperties.declarations: 293 | itemTarget = { type: property, range: to as Range }; 294 | break; 295 | case ItemEdgeProperties.definitions: 296 | itemTarget = { type: property, range: to as Range }; 297 | break; 298 | case ItemEdgeProperties.referenceResults: 299 | itemTarget = { type: property, result: to as ReferenceResult }; 300 | break; 301 | case ItemEdgeProperties.referenceLinks: 302 | itemTarget = { type: property, result: to as Moniker }; 303 | } 304 | } else { 305 | itemTarget = to as Range; 306 | } 307 | if (itemTarget !== undefined) { 308 | if (values === undefined) { 309 | values = [ itemTarget ]; 310 | this.out.item.set(from.id, values); 311 | } else { 312 | values.push(itemTarget); 313 | } 314 | } 315 | break; 316 | case EdgeLabels.next: 317 | this.out.next.set(from.id, to); 318 | break; 319 | case EdgeLabels.moniker: 320 | this.out.moniker.set(from.id, to as Moniker); 321 | values = this.in.moniker.get(to.id); 322 | if (values === undefined) { 323 | values = []; 324 | this.in.moniker.set(to.id, values); 325 | } 326 | values.push(from); 327 | break; 328 | case EdgeLabels.textDocument_documentSymbol: 329 | this.out.documentSymbol.set(from.id, to as DocumentSymbolResult); 330 | break; 331 | case EdgeLabels.textDocument_foldingRange: 332 | this.out.foldingRange.set(from.id, to as FoldingRangeResult); 333 | break; 334 | case EdgeLabels.textDocument_documentLink: 335 | this.out.documentLink.set(from.id, to as DocumentLinkResult); 336 | break; 337 | case EdgeLabels.textDocument_diagnostic: 338 | this.out.diagnostic.set(from.id, to as DiagnosticResult); 339 | break; 340 | case EdgeLabels.textDocument_definition: 341 | this.out.definition.set(from.id, to as DefinitionResult); 342 | break; 343 | case EdgeLabels.textDocument_typeDefinition: 344 | this.out.typeDefinition.set(from.id, to as TypeDefinitionResult); 345 | break; 346 | case EdgeLabels.textDocument_hover: 347 | this.out.hover.set(from.id, to as HoverResult); 348 | break; 349 | case EdgeLabels.textDocument_references: 350 | this.out.references.set(from.id, to as ReferenceResult); 351 | break; 352 | } 353 | } 354 | 355 | public getDocumentInfos(): DocumentInfo[] { 356 | const result: DocumentInfo[] = []; 357 | this.indices.documents.forEach((value, key) => { 358 | // We take the id of the first document. 359 | result.push({ uri: key, id: value.documents[0].id, hash: value.hash }); 360 | }); 361 | return result; 362 | } 363 | 364 | protected findFile(uri: string): { id: Id; hash: string; } | undefined { 365 | const result = this.indices.documents.get(uri); 366 | if (result === undefined) { 367 | return undefined; 368 | } 369 | return { id: result.documents[0].id, hash: result.hash }; 370 | } 371 | 372 | protected fileContent(info: { id: Id, hash: string }): string | undefined { 373 | return this.indices.contents.get(info.hash); 374 | } 375 | 376 | public foldingRanges(uri: string): lsp.FoldingRange[] | undefined { 377 | const value = this.indices.documents.get(this.toDatabase(uri)); 378 | if (value === undefined) { 379 | return undefined; 380 | } 381 | // Take the id of the first document with that content. We assume that 382 | // all documents with the same content have the same folding ranges. 383 | const id = value.documents[0].id; 384 | const foldingRangeResult = this.out.foldingRange.get(id); 385 | if (foldingRangeResult === undefined) { 386 | return undefined; 387 | } 388 | let result: lsp.FoldingRange[] = []; 389 | for (let item of foldingRangeResult.result) { 390 | result.push(Object.assign(Object.create(null), item)); 391 | } 392 | return result; 393 | } 394 | 395 | public documentSymbols(uri: string): lsp.DocumentSymbol[] | undefined { 396 | const value = this.indices.documents.get(this.toDatabase(uri)); 397 | if (value === undefined) { 398 | return undefined; 399 | } 400 | // Take the id of the first document with that content. We assume that 401 | // all documents with the same content have the same document symbols. 402 | const id = value.documents[0].id; 403 | let documentSymbolResult = this.out.documentSymbol.get(id); 404 | if (documentSymbolResult === undefined || documentSymbolResult.result.length === 0) { 405 | return undefined; 406 | } 407 | let first = documentSymbolResult.result[0]; 408 | let result: lsp.DocumentSymbol[] = []; 409 | if (lsp.DocumentSymbol.is(first)) { 410 | for (let item of documentSymbolResult.result) { 411 | result.push(Object.assign(Object.create(null), item)); 412 | } 413 | } else { 414 | for (let item of (documentSymbolResult.result as RangeBasedDocumentSymbol[])) { 415 | let converted = this.toDocumentSymbol(item); 416 | if (converted !== undefined) { 417 | result.push(converted); 418 | } 419 | } 420 | } 421 | return result; 422 | } 423 | 424 | private toDocumentSymbol(value: RangeBasedDocumentSymbol): lsp.DocumentSymbol | undefined { 425 | let range = this.vertices.ranges.get(value.id)!; 426 | let tag = range.tag; 427 | if (tag === undefined || !(tag.type === 'declaration' || tag.type === 'definition')) { 428 | return undefined; 429 | } 430 | let result: lsp.DocumentSymbol = lsp.DocumentSymbol.create( 431 | tag.text, tag.detail || '', tag.kind, 432 | tag.fullRange, this.asRange(range) 433 | ); 434 | if (value.children && value.children.length > 0) { 435 | result.children = []; 436 | for (let child of value.children) { 437 | let converted = this.toDocumentSymbol(child); 438 | if (converted !== undefined) { 439 | result.children.push(converted); 440 | } 441 | } 442 | } 443 | return result; 444 | } 445 | 446 | public hover(uri: string, position: lsp.Position): lsp.Hover | undefined { 447 | const ranges = this.findRangesFromPosition(this.toDatabase(uri), position); 448 | if (ranges === undefined) { 449 | return undefined; 450 | } 451 | 452 | // We assume that for the same document URI the same position results in the same 453 | // hover. So we take the first range. 454 | const range = ranges[0]; 455 | const hoverResult = this.getResultPath(range.id, this.out.hover).result?.value; 456 | if (hoverResult === undefined) { 457 | return undefined; 458 | } 459 | 460 | let hoverRange = hoverResult.result.range !== undefined ? hoverResult.result.range : range; 461 | return { 462 | contents: hoverResult.result.contents, 463 | range: hoverRange 464 | }; 465 | } 466 | 467 | public declarations(uri: string, position: lsp.Position): lsp.Location | lsp.Location[] | undefined { 468 | return this.findTargets(uri, position, this.out.declaration); 469 | } 470 | 471 | public definitions(uri: string, position: lsp.Position): lsp.Location | lsp.Location[] | undefined { 472 | return this.findTargets(uri, position, this.out.definition); 473 | } 474 | 475 | private findTargets(uri: string, position: lsp.Position, edges: Map): lsp.Location | lsp.Location[] | undefined { 476 | const ranges = this.findRangesFromPosition(this.toDatabase(uri), position); 477 | if (ranges === undefined) { 478 | return undefined; 479 | } 480 | 481 | const resolveTargets = (result: lsp.Location[], dedupLocations: Set, targetResult: T): void => { 482 | const ranges = this.item(targetResult); 483 | if (ranges === undefined) { 484 | return undefined; 485 | } 486 | for (const element of ranges) { 487 | this.addLocation(result, element, dedupLocations); 488 | } 489 | }; 490 | 491 | const _findTargets = (result: lsp.Location[], dedupLocations: Set, dedupMonikers: Set, range: Range): void => { 492 | const resultPath = this.getResultPath(range.id, edges); 493 | if (resultPath.result === undefined) { 494 | return undefined; 495 | } 496 | 497 | const mostSpecificMoniker = this.getMostSpecificMoniker(resultPath); 498 | const monikers: Moniker[] = mostSpecificMoniker !== undefined ? [mostSpecificMoniker] : []; 499 | 500 | resolveTargets(result, dedupLocations, resultPath.result.value); 501 | for (const moniker of monikers) { 502 | if (dedupMonikers.has(moniker.key)) { 503 | continue; 504 | } 505 | dedupMonikers.add(moniker.key); 506 | const matchingMonikers = this.indices.monikers.get(moniker.key); 507 | if (matchingMonikers !== undefined) { 508 | for (const matchingMoniker of matchingMonikers) { 509 | const vertices = this.findVerticesForMoniker(matchingMoniker); 510 | if (vertices !== undefined) { 511 | for (const vertex of vertices) { 512 | const resultPath = this.getResultPath(vertex.id, edges); 513 | if (resultPath.result === undefined) { 514 | continue; 515 | } 516 | resolveTargets(result, dedupLocations, resultPath.result.value); 517 | } 518 | } 519 | } 520 | } 521 | } 522 | }; 523 | 524 | const result: lsp.Location[] = []; 525 | const dedupLocations: Set = new Set(); 526 | const dedupMonikers: Set = new Set(); 527 | for (const range of ranges) { 528 | _findTargets(result, dedupLocations, dedupMonikers, range); 529 | } 530 | return result; 531 | } 532 | 533 | public references(uri: string, position: lsp.Position, context: lsp.ReferenceContext): lsp.Location[] | undefined { 534 | let ranges = this.findRangesFromPosition(this.toDatabase(uri), position); 535 | if (ranges === undefined) { 536 | return undefined; 537 | } 538 | 539 | const findReferences = (result: lsp.Location[], dedupLocations: Set, dedupMonikers: Set, range: Range): void => { 540 | const resultPath = this.getResultPath(range.id, this.out.references); 541 | if (resultPath.result === undefined) { 542 | return; 543 | } 544 | const mostSpecificMoniker = this.getMostSpecificMoniker(resultPath); 545 | const monikers: Moniker[] = mostSpecificMoniker !== undefined ? [mostSpecificMoniker] : []; 546 | this.resolveReferenceResult(result, dedupLocations, monikers, resultPath.result.value, context); 547 | for (const moniker of monikers) { 548 | if (dedupMonikers.has(moniker.key)) { 549 | continue; 550 | } 551 | dedupMonikers.add(moniker.key); 552 | const matchingMonikers = this.indices.monikers.get(moniker.key); 553 | if (matchingMonikers !== undefined) { 554 | for (const matchingMoniker of matchingMonikers) { 555 | if (moniker.id === matchingMoniker.id) { 556 | continue; 557 | } 558 | const vertices = this.findVerticesForMoniker(matchingMoniker); 559 | if (vertices !== undefined) { 560 | for (const vertex of vertices) { 561 | const resultPath = this.getResultPath(vertex.id, this.out.references); 562 | if (resultPath.result === undefined) { 563 | continue; 564 | } 565 | this.resolveReferenceResult(result, dedupLocations, monikers, resultPath.result.value, context); 566 | } 567 | } 568 | } 569 | } 570 | } 571 | }; 572 | 573 | const result: lsp.Location[] = []; 574 | const dedupLocations: Set = new Set(); 575 | const dedupMonikers: Set = new Set(); 576 | for (const range of ranges) { 577 | findReferences(result, dedupLocations, dedupMonikers, range); 578 | } 579 | 580 | return result; 581 | } 582 | 583 | private getResultPath(start: Id, edges: Map): ResultPath { 584 | let currentId = start; 585 | const result: ResultPath = { path: [], result: undefined }; 586 | do { 587 | const value: T | undefined = edges.get(currentId); 588 | const moniker: Moniker | undefined = this.out.moniker.get(currentId); 589 | if (value !== undefined) { 590 | result.result = { value, moniker }; 591 | return result; 592 | } 593 | result.path.push({ vertex: currentId, moniker }); 594 | const next = this.out.next.get(currentId); 595 | if (next === undefined) { 596 | return result; 597 | } 598 | currentId = next.id; 599 | } while (true); 600 | } 601 | 602 | private getMostSpecificMoniker(result: ResultPath): Moniker | undefined { 603 | if (result.result?.moniker !== undefined) { 604 | return result.result.moniker; 605 | } 606 | for (let i = result.path.length - 1; i >= 0; i--) { 607 | if (result.path[i].moniker !== undefined) { 608 | return result.path[i].moniker; 609 | } 610 | } 611 | return undefined; 612 | } 613 | 614 | private findVerticesForMoniker(moniker: Moniker): Vertex[] | undefined { 615 | return this.in.moniker.get(moniker.id); 616 | } 617 | 618 | private resolveReferenceResult(locations: lsp.Location[], dedupLocations: Set, monikers: Moniker[], referenceResult: ReferenceResult, context: lsp.ReferenceContext): void { 619 | const targets = this.item(referenceResult); 620 | if (targets === undefined) { 621 | return undefined; 622 | } 623 | for (let target of targets) { 624 | if (target.type === ItemEdgeProperties.declarations && context.includeDeclaration) { 625 | this.addLocation(locations, target.range, dedupLocations); 626 | } else if (target.type === ItemEdgeProperties.definitions && context.includeDeclaration) { 627 | this.addLocation(locations, target.range, dedupLocations); 628 | } else if (target.type === ItemEdgeProperties.references) { 629 | this.addLocation(locations, target.range, dedupLocations); 630 | } else if (target.type === ItemEdgeProperties.referenceResults) { 631 | this.resolveReferenceResult(locations, dedupLocations, monikers, target.result, context); 632 | } else if (target.type === ItemEdgeProperties.referenceLinks) { 633 | monikers.push(target.result); 634 | } 635 | } 636 | } 637 | 638 | private item(value: DefinitionResult | DeclarationResult): Range[]; 639 | private item(value: ReferenceResult): ItemTarget[]; 640 | private item(value: DeclarationResult | DefinitionResult | ReferenceResult): Range[] | ItemTarget[] | undefined { 641 | if (value.label === 'declarationResult') { 642 | return this.out.item.get(value.id) as Range[]; 643 | } else if (value.label === 'definitionResult') { 644 | return this.out.item.get(value.id) as Range[]; 645 | } else if (value.label === 'referenceResult') { 646 | return this.out.item.get(value.id) as ItemTarget[]; 647 | } else { 648 | return undefined; 649 | } 650 | } 651 | 652 | private addLocation(result: lsp.Location[], value: Range | lsp.Location, dedup: Set): void { 653 | let location: lsp.Location; 654 | if (lsp.Location.is(value)) { 655 | location = value; 656 | } else { 657 | let document = this.in.contains.get(value.id)!; 658 | location = lsp.Location.create(this.fromDatabase((document as Document).uri), this.asRange(value)); 659 | } 660 | const key = Locations.makeKey(location); 661 | if (!dedup.has(key)) { 662 | dedup.add(key); 663 | result.push(location); 664 | } 665 | } 666 | 667 | private findRangesFromPosition(file: string, position: lsp.Position): Range[] | undefined { 668 | const value = this.indices.documents.get(file); 669 | if (value === undefined) { 670 | return undefined; 671 | } 672 | let result: Range[] = []; 673 | for (const document of value.documents) { 674 | const id = document.id; 675 | let contains = this.out.contains.get(id); 676 | if (contains === undefined || contains.length === 0) { 677 | return undefined; 678 | } 679 | 680 | let candidate: Range | undefined; 681 | for (let item of contains) { 682 | if (item.label !== VertexLabels.range) { 683 | continue; 684 | } 685 | if (JsonStore.containsPosition(item, position)) { 686 | if (!candidate) { 687 | candidate = item; 688 | } else { 689 | if (JsonStore.containsRange(candidate, item)) { 690 | candidate = item; 691 | } 692 | } 693 | } 694 | } 695 | if (candidate !== undefined) { 696 | result.push(candidate); 697 | } 698 | } 699 | return result.length > 0 ? result : undefined; 700 | } 701 | 702 | private asLocation(value: Range | lsp.Location): lsp.Location { 703 | if (lsp.Location.is(value)) { 704 | return value; 705 | } else { 706 | let document = this.in.contains.get(value.id)!; 707 | return lsp.Location.create(this.fromDatabase((document as Document).uri), this.asRange(value)); 708 | } 709 | } 710 | 711 | private static containsPosition(range: lsp.Range, position: lsp.Position): boolean { 712 | if (position.line < range.start.line || position.line > range.end.line) { 713 | return false; 714 | } 715 | if (position.line === range.start.line && position.character < range.start.character) { 716 | return false; 717 | } 718 | if (position.line === range.end.line && position.character > range.end.character) { 719 | return false; 720 | } 721 | return true; 722 | } 723 | 724 | /** 725 | * Test if `otherRange` is in `range`. If the ranges are equal, will return true. 726 | */ 727 | public static containsRange(range: lsp.Range, otherRange: lsp.Range): boolean { 728 | if (otherRange.start.line < range.start.line || otherRange.end.line < range.start.line) { 729 | return false; 730 | } 731 | if (otherRange.start.line > range.end.line || otherRange.end.line > range.end.line) { 732 | return false; 733 | } 734 | if (otherRange.start.line === range.start.line && otherRange.start.character < range.start.character) { 735 | return false; 736 | } 737 | if (otherRange.end.line === range.end.line && otherRange.end.character > range.end.character) { 738 | return false; 739 | } 740 | return true; 741 | } 742 | } -------------------------------------------------------------------------------- /server/src/lsifServer.ts: -------------------------------------------------------------------------------- 1 | /* -------------------------------------------------------------------------------------------- 2 | * Copyright (c) Microsoft Corporation. All rights reserved. 3 | * Licensed under the MIT License. See License.txt in the project root for license information. 4 | * ------------------------------------------------------------------------------------------ */ 5 | 6 | import { promisify } from 'util'; 7 | import * as path from 'path'; 8 | import * as fs from 'fs'; 9 | 10 | import { URI } from 'vscode-uri'; 11 | import { 12 | createConnection, ProposedFeatures, InitializeParams, TextDocumentSyncKind, WorkspaceFolder, 13 | BulkUnregistration, BulkRegistration, DocumentSymbolRequest, DocumentSelector, FoldingRangeRequest, 14 | HoverRequest, DefinitionRequest, ReferencesRequest, RequestType, DeclarationRequest, DocumentFilter 15 | } from 'vscode-languageserver/node'; 16 | 17 | import { Database, UriTransformer } from './database'; 18 | import { FileType, FileStat } from './files'; 19 | 20 | const LSIF_SCHEME = 'lsif'; 21 | 22 | interface StatFileParams { 23 | uri: string; 24 | } 25 | 26 | namespace StatFileRequest { 27 | export const type = new RequestType('lsif/statFile'); 28 | } 29 | 30 | interface ReadFileParams { 31 | uri: string; 32 | } 33 | 34 | namespace ReadFileRequest { 35 | export const type = new RequestType('lsif/readfile'); 36 | } 37 | 38 | interface ReadDirectoryParams { 39 | uri: string; 40 | } 41 | 42 | namespace ReadDirectoryRequest { 43 | export const type = new RequestType('lsif/readDirectory'); 44 | } 45 | 46 | let connection = createConnection(ProposedFeatures.all); 47 | 48 | class Transformer implements UriTransformer { 49 | 50 | private lsif: string; 51 | private workspaceRoot: string; 52 | 53 | constructor(lsif: URI, workspaceRoot: string) { 54 | this.lsif = lsif.toString(); 55 | this.workspaceRoot = workspaceRoot; 56 | } 57 | public toDatabase(uri: string): string { 58 | if (uri.startsWith(this.lsif)) { 59 | let p = uri.substring(this.lsif.length); 60 | return `${this.workspaceRoot}${p}`; 61 | } else { 62 | let parsed = URI.parse(uri); 63 | if (parsed.scheme === LSIF_SCHEME && parsed.query) { 64 | return parsed.with( { scheme: 'file', query: '' } ).toString(true); 65 | } else { 66 | return uri; 67 | } 68 | } 69 | } 70 | public fromDatabase(uri: string): string { 71 | if (uri.startsWith(this.workspaceRoot)) { 72 | let p = uri.substring(this.workspaceRoot.length); 73 | return `${this.lsif}${p}`; 74 | } else { 75 | let file = URI.parse(uri); 76 | return file.with( { scheme: LSIF_SCHEME, query: this.lsif }).toString(true); 77 | } 78 | } 79 | } 80 | 81 | const workspaceFolders: Map = new Map(); 82 | let _sortedDatabaseKeys: string[] | undefined; 83 | function sortedDatabaseKeys(): string[] { 84 | if (_sortedDatabaseKeys === undefined) { 85 | _sortedDatabaseKeys = []; 86 | for (let key of databases.keys()) { 87 | _sortedDatabaseKeys.push(key); 88 | } 89 | _sortedDatabaseKeys.sort( 90 | (a, b) => { 91 | return a.length - b.length; 92 | } 93 | ); 94 | } 95 | return _sortedDatabaseKeys; 96 | } 97 | 98 | const databases: Map> = new Map(); 99 | function getDatabaseKey(uri: string): string { 100 | return uri.charAt(uri.length - 1) !== '/' ? `${uri}/` : uri; 101 | } 102 | 103 | async function createDatabase(folder: WorkspaceFolder): Promise { 104 | let uri: URI = URI.parse(folder.uri); 105 | const fsPath = uri.fsPath; 106 | const extName = path.extname(fsPath); 107 | if (fs.existsSync(fsPath)) { 108 | try { 109 | let database: Database | undefined; 110 | if (extName === '.db') { 111 | const Sqlite = await import('better-sqlite3'); 112 | const db = new Sqlite(fsPath, { readonly: true }); 113 | let format = 'graph'; 114 | try { 115 | format = (db.prepare('Select * from format f').get() as any).format; 116 | } catch (err) { 117 | // Old DBs have no format. Treat is as graph 118 | } finally { 119 | db.close(); 120 | } 121 | if (format === 'blob') { 122 | const module = await import('./blobStore'); 123 | database = new module.BlobStore(); 124 | } else { 125 | const module = await import ('./graphStore'); 126 | database = new module.GraphStore(); 127 | } 128 | } else if (extName === '.lsif') { 129 | const module = await import('./jsonStore'); 130 | database = new module.JsonStore(); 131 | } 132 | if (database !== undefined) { 133 | let promise = database.load(fsPath, (workspaceRoot: string) => { 134 | return new Transformer(uri, workspaceRoot); 135 | }).then(() => { 136 | return database!; 137 | }); 138 | databases.set(getDatabaseKey(folder.uri), promise); 139 | return promise; 140 | } 141 | } catch (error) { 142 | throw error; 143 | } 144 | } 145 | return Promise.reject(new Error(`Can't create database for ${folder.uri}`)); 146 | } 147 | 148 | function findDatabase(uri: string): Promise | undefined { 149 | let sorted = sortedDatabaseKeys(); 150 | let parsed = URI.parse(uri); 151 | if (parsed.query) { 152 | // The LSIF URIs are encoded. 153 | uri = URI.parse(parsed.query).toString(); 154 | } 155 | if (uri.charAt(uri.length - 1) !== '/') { 156 | uri = uri + '/'; 157 | } 158 | for (let element of sorted) { 159 | if (uri.startsWith(element)) { 160 | return databases.get(element); 161 | } 162 | } 163 | return undefined; 164 | } 165 | 166 | let registrations: Thenable | undefined; 167 | async function checkRegistrations(): Promise { 168 | if (databases.size === 0 && registrations !== undefined) { 169 | registrations.then(unregister => unregister.dispose(), error => connection.console.error('Failed to unregister listeners.')); 170 | registrations = undefined; 171 | return; 172 | } 173 | if (databases.size >= 1 && registrations === undefined) { 174 | let documentSelector: DocumentSelector = [ 175 | { scheme: 'lsif', exclusive: true } as DocumentFilter 176 | ]; 177 | let toRegister: BulkRegistration = BulkRegistration.create(); 178 | toRegister.add(DocumentSymbolRequest.type, { 179 | documentSelector 180 | }); 181 | toRegister.add(FoldingRangeRequest.type, { 182 | documentSelector 183 | }); 184 | toRegister.add(DefinitionRequest.type, { 185 | documentSelector 186 | }); 187 | toRegister.add(DeclarationRequest.type, { 188 | documentSelector 189 | }); 190 | toRegister.add(HoverRequest.type, { 191 | documentSelector 192 | }); 193 | toRegister.add(ReferencesRequest.type, { 194 | documentSelector 195 | }); 196 | registrations = connection.client.register(toRegister); 197 | } 198 | } 199 | 200 | connection.onInitialize((params: InitializeParams) => { 201 | if (params.workspaceFolders) { 202 | for (let folder of params.workspaceFolders) { 203 | workspaceFolders.set(folder.uri, folder); 204 | } 205 | } 206 | return { 207 | capabilities: { 208 | textDocumentSync: TextDocumentSyncKind.None, 209 | workspace: { 210 | workspaceFolders: { 211 | supported: true 212 | } 213 | } 214 | } 215 | }; 216 | }); 217 | 218 | connection.onInitialized(async () => { 219 | try { 220 | for (let folder of workspaceFolders.values()) { 221 | const uri: URI = URI.parse(folder.uri); 222 | if (uri.scheme === LSIF_SCHEME) { 223 | try { 224 | await createDatabase(folder); 225 | } catch (err: any) { 226 | connection.console.error(err.message); 227 | } 228 | } 229 | } 230 | } finally { 231 | _sortedDatabaseKeys = undefined; 232 | checkRegistrations(); 233 | } 234 | // handle updates. 235 | connection.workspace.onDidChangeWorkspaceFolders(async (event) => { 236 | for (let removed of event.removed) { 237 | const uri: URI = URI.parse(removed.uri); 238 | if (uri.scheme === LSIF_SCHEME) { 239 | const dbKey = getDatabaseKey(removed.uri); 240 | const promise = databases.get(dbKey); 241 | if (promise) { 242 | promise.then((database) => { 243 | try { 244 | database.close(); 245 | } finally { 246 | databases.delete(dbKey); 247 | } 248 | }); 249 | } 250 | } 251 | } 252 | for (let added of event.added) { 253 | const uri: URI = URI.parse(added.uri); 254 | if (uri.scheme === LSIF_SCHEME) { 255 | await createDatabase(added); 256 | } 257 | } 258 | _sortedDatabaseKeys = undefined; 259 | checkRegistrations(); 260 | }); 261 | }); 262 | 263 | connection.onShutdown(() => { 264 | try { 265 | for (let promise of databases.values()) { 266 | promise.then((database) => database.close()); 267 | } 268 | } finally { 269 | _sortedDatabaseKeys = undefined; 270 | databases.clear(); 271 | } 272 | }); 273 | 274 | connection.onRequest(StatFileRequest.type, async (params) => { 275 | let promise = findDatabase(params.uri); 276 | if (promise === undefined) { 277 | return null; 278 | } 279 | let database = await promise; 280 | return database.stat(params.uri); 281 | }); 282 | 283 | connection.onRequest(ReadDirectoryRequest.type, async (params) => { 284 | let promise = findDatabase(params.uri); 285 | if (promise === undefined) { 286 | return []; 287 | } 288 | let database = await promise; 289 | return database.readDirectory(params.uri); 290 | }); 291 | 292 | connection.onRequest(ReadFileRequest.type, async (params) => { 293 | let promise = findDatabase(params.uri); 294 | if (promise === undefined) { 295 | return null; 296 | } 297 | let database = await promise; 298 | return database.readFileContent(params.uri); 299 | }); 300 | 301 | connection.onDocumentSymbol(async (params) => { 302 | let promise = findDatabase(params.textDocument.uri); 303 | if (promise === undefined) { 304 | return null; 305 | } 306 | let database = await promise; 307 | return database.documentSymbols(params.textDocument.uri); 308 | }); 309 | 310 | connection.onFoldingRanges(async (params) => { 311 | let promise = findDatabase(params.textDocument.uri); 312 | if (promise === undefined) { 313 | return null; 314 | } 315 | let database = await promise; 316 | return database.foldingRanges(params.textDocument.uri); 317 | }); 318 | 319 | connection.onHover(async (params) => { 320 | let promise = findDatabase(params.textDocument.uri); 321 | if (promise === undefined) { 322 | return null; 323 | } 324 | let database = await promise; 325 | return database.hover(params.textDocument.uri, params.position); 326 | }); 327 | 328 | connection.onDeclaration(async (params) => { 329 | let promise = findDatabase(params.textDocument.uri); 330 | if (promise === undefined) { 331 | return null; 332 | } 333 | let database = await promise; 334 | return database.declarations(params.textDocument.uri, params.position); 335 | }); 336 | 337 | connection.onDefinition(async (params) => { 338 | let promise = findDatabase(params.textDocument.uri); 339 | if (promise === undefined) { 340 | return null; 341 | } 342 | let database = await promise; 343 | return database.definitions(params.textDocument.uri, params.position); 344 | }); 345 | 346 | connection.onReferences(async (params) => { 347 | let promise = findDatabase(params.textDocument.uri); 348 | if (promise === undefined) { 349 | return null; 350 | } 351 | let database = await promise; 352 | return database.references(params.textDocument.uri, params.position, params.context); 353 | }); 354 | 355 | connection.listen(); -------------------------------------------------------------------------------- /server/src/protocol.compress.ts: -------------------------------------------------------------------------------- 1 | /* -------------------------------------------------------------------------------------------- 2 | * Copyright (c) Microsoft Corporation. All rights reserved. 3 | * Licensed under the MIT License. See License.txt in the project root for license information. 4 | * ------------------------------------------------------------------------------------------ */ 5 | 'use strict'; 6 | 7 | import * as protocol from 'lsif-protocol'; 8 | 9 | export enum CompressionKind { 10 | id = 'id', 11 | ids = 'ids', 12 | scalar = 'scalar', 13 | literal = 'literal', 14 | array = 'array', 15 | any = 'any', 16 | raw = 'raw' 17 | } 18 | 19 | export interface CompressorPropertyDescription { 20 | /** 21 | * The name of the property. 22 | */ 23 | name: string; 24 | 25 | /** 26 | * It's index in the array. 27 | */ 28 | index: number; 29 | 30 | /** 31 | * Whether the value is raw in case it was an object literal. 32 | */ 33 | compressionKind: CompressionKind; 34 | 35 | /** 36 | * Short form if the value is a string. 37 | */ 38 | shortForm?: [string, string | number][]; 39 | 40 | } 41 | 42 | export interface CompressorData { 43 | vertexCompressor: number; 44 | edgeCompressor: number; 45 | itemEdgeCompressor: number; 46 | all: CompressorDescription[]; 47 | } 48 | 49 | export interface CompressorDescription { 50 | /** 51 | * The compressor id. 52 | */ 53 | id: number; 54 | 55 | /** 56 | * The parent compressor or undefined. 57 | */ 58 | parent: number | undefined; 59 | 60 | /** 61 | * The compressed propeties. 62 | */ 63 | properties: CompressorPropertyDescription[]; 64 | } 65 | 66 | /** 67 | * The meta data vertex. 68 | */ 69 | export interface MetaData extends protocol.MetaData { 70 | 71 | /** 72 | * A description of the compressor used. 73 | */ 74 | compressors?: CompressorData; 75 | } -------------------------------------------------------------------------------- /server/src/test.ts: -------------------------------------------------------------------------------- 1 | /* -------------------------------------------------------------------------------------------- 2 | * Copyright (c) Microsoft Corporation. All rights reserved. 3 | * Licensed under the MIT License. See License.txt in the project root for license information. 4 | * ------------------------------------------------------------------------------------------ */ 5 | 6 | import { GraphStore } from './graphStore'; 7 | import { noopTransformer } from './database'; 8 | 9 | const db = new GraphStore(); 10 | db.load('jsonrpc.db', () => noopTransformer); 11 | 12 | // let definitions = db.definitions('file:///c:/Users/dirkb/Projects/mseng/LanguageServer/Node/jsonrpc/src/events.ts', { line: 6, character: 21}); 13 | // console.log(JSON.stringify(definitions)); 14 | 15 | // let folding = db.foldingRanges('file:///c:/Users/dirkb/Projects/mseng/LanguageServer/Node/jsonrpc/src/events.ts'); 16 | // console.log(JSON.stringify(folding)); 17 | 18 | let symbols = db.documentSymbols('file:///c:/Users/dirkb/Projects/mseng/LanguageServer/Node/jsonrpc/src/events.ts'); 19 | console.log(JSON.stringify(symbols)); -------------------------------------------------------------------------------- /server/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es6", 4 | "module": "commonjs", 5 | "moduleResolution": "node", 6 | "sourceMap": true, 7 | "outDir": "out", 8 | "rootDir": "src", 9 | "lib": ["es6"], 10 | "strict": true, 11 | "noImplicitAny": true, 12 | "noImplicitReturns": true 13 | }, 14 | "include": ["src"], 15 | "exclude": ["node_modules"] 16 | } 17 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "target": "es6", 5 | "outDir": "out", 6 | "rootDir": "src", 7 | "lib": [ "es6" ], 8 | "sourceMap": true 9 | }, 10 | "include": [ 11 | "src" 12 | ], 13 | "exclude": [ 14 | "node_modules", 15 | ".vscode-test" 16 | ], 17 | "references": [ 18 | { "path": "./client" }, 19 | { "path": "./server" } 20 | ] 21 | } --------------------------------------------------------------------------------