|
| 1 | +# Development Guide |
| 2 | + |
| 3 | +This document provides insight into the inner workings on this VS Code extension. For a higher-level, |
| 4 | +end-user documentation you can refer to the [extension's online documentation](https://doc.codiga.io/docs/code-analysis/ide/vscode/). |
| 5 | + |
| 6 | +## Architecture |
| 7 | + |
| 8 | +The extension consists of two main modules, client and server, along with the following project structure: |
| 9 | + |
| 10 | +``` |
| 11 | +vscode-plugin |
| 12 | + - client <-- Contains the sources for the VS Code extension itself |
| 13 | + - src |
| 14 | + - extension.ts <-- The VS Code extension's entry point |
| 15 | + - images <-- Assets for documentation and VS Code UI elements |
| 16 | + - out <-- Client-side compiled sources |
| 17 | + - server <-- A language server that provides diagnostics and quick fixes for the Rosie platform |
| 18 | + - out <-- Server-side compiled sources |
| 19 | + - src |
| 20 | + - server.ts <-- The language server's entry point |
| 21 | + - test-fixtures <-- Test data |
| 22 | + - webview <-- Generated folder for the Codiga Assistant webview |
| 23 | +``` |
| 24 | + |
| 25 | +## Client - VS Code Extension |
| 26 | + |
| 27 | +The client side of this project is a regular VS Code extension containing the logic for inline and shortcut completion, |
| 28 | +Codiga Assistant, snippet search, IDE configuration and Rosie default rulesets suggestions. |
| 29 | + |
| 30 | +It additionally consumes diagnostics and code actions (quick fixes) from the language server located in the `server` directory. |
| 31 | + |
| 32 | +The entry point and initialization logic of the extension is located in [`/client/src/extension.ts`](/client/src/extension.ts). |
| 33 | + |
| 34 | +## Server - Language Server |
| 35 | + |
| 36 | +This is a language server implementation based on the official [LSP specifications](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocument_publishDiagnostics) |
| 37 | +and VS Code's [Language Server Extension Guide](https://code.visualstudio.com/api/language-extensions/language-server-extension-guide). |
| 38 | + |
| 39 | +The language client is built in [`/client/src/extension.ts`](/client/src/extension.ts), which is then launched, and which in turn launches the language server at `/server/src/server.ts`. |
| 40 | + |
| 41 | +For a guide on how to implement the Rosie platform in a new IDE, you can refer to the [Rosie IDE Specification](https://doc.codiga.io/docs/rosie/ide-specification/) document. |
| 42 | + |
| 43 | +### codiga.yml configuration |
| 44 | + |
| 45 | +To enable the Rosie service in a project, one has to create and configure a `codiga.yml` file in the project's root directory. |
| 46 | +This is the file in which you can tell Rosie what rulesets you want to use, or ignore in that given project. |
| 47 | + |
| 48 | +Details on what the configuration can hold can be found at [Code Analysis for VS Code](https://doc.codiga.io/docs/code-analysis/ide/vscode/#the-configuration-file). |
| 49 | + |
| 50 | +### Rosie cache |
| 51 | + |
| 52 | +The language server incorporates an internal cache ([`rosieCache.ts`](/server/src/rosie/rosieCache.ts)) of all the rules from the rulesets that are specified in the rulesets property in `codiga.yml`, |
| 53 | +along with a periodic update mechanism. This caching makes it possible to provide better performance. |
| 54 | + |
| 55 | +The cache is initialized and the periodic update begins when the language server finished initialization (see `connection.onInitialized()` in [`server.ts`](/server/src/server.ts)). |
| 56 | + |
| 57 | +The periodic update is executed in every 10 seconds, and updates the cache if either the `codiga.yml` file has changed, |
| 58 | +or the configured rulesets (or underlying rules) have changed on Codiga Hub. |
| 59 | + |
| 60 | +The cache is updated according to the serialized content (a `CodigaYmlConfig` instance from `server.ts`) of the `codiga.yml` file. |
| 61 | + |
| 62 | +#### Document revalidation |
| 63 | + |
| 64 | +We also revalidate all open text documents when the cache is updated. |
| 65 | + |
| 66 | +This makes sure that both on IDE startup (when there is a document open, but the cache is not yet populated), |
| 67 | +and later, the editor shows diagnostics based on the up-to-date state of the cache. |
| 68 | + |
| 69 | +### Rosie client |
| 70 | + |
| 71 | +[`rosieClient.ts`](/server/src/rosie/rosieClient.ts) is responsible for the communication between the language server and the Rosie service. |
| 72 | + |
| 73 | +It sends information (document text, document language, Rosie rules cached for the given language, etc.) to Rosie, |
| 74 | +then processes the returned response (code violations, ranges, severities, etc.) and supplies it to the diagnostics functionality. |
| 75 | + |
| 76 | +The model objects for serializing the response data can be found in [`rosieTypes.ts`](/server/src/rosie/rosieTypes.ts). |
| 77 | + |
| 78 | +### Diagnostics and quick fixes |
| 79 | + |
| 80 | +Diagnostics are the language server specific objects that hold information about a code violation (range, description, etc.) |
| 81 | +which are then displayed in the IDE editor. |
| 82 | + |
| 83 | +Code actions, or quick fixes, are actions that can be invoked on a specific diagnostic in the editor to provide a fix, optimization, etc. |
| 84 | +for the given diagnostic. |
| 85 | + |
| 86 | +This language server provides one type of diagnostic and two types of quick fixes. |
| 87 | + |
| 88 | +#### Diagnostics |
| 89 | + |
| 90 | +The diagnostic is the actual code violation that is found by Rosie, and are provided by [`diagnostics.ts`](/server/src/diagnostics/diagnostics.ts). |
| 91 | + |
| 92 | +#### Quick fixes |
| 93 | + |
| 94 | +The quick fixes are: |
| 95 | +- **Fix: <fix description>**: applies an actual code fix for the violation |
| 96 | +- **Ignore rule <rule name>**: disables Codiga code analysis for the line on which the violation occurred |
| 97 | + |
| 98 | +The rule fix is provided by [`rosieFix.ts`](/server/src/rosie/rosiefix.ts), while the ignore one is provided by |
| 99 | +[`ignore-violation.ts`](/server/src/diagnostics/ignore-violation.ts). |
| 100 | + |
| 101 | +`CodeAction` objects returned by both providers are handled and propagated towards the client in the `connection.onCodeAction()` |
| 102 | +section of `server.ts`. |
| 103 | + |
| 104 | +### Configuration cache |
| 105 | + |
| 106 | +A handful of configuration and client side related data is cached on server side, so that the number of communication |
| 107 | +between the language client and the language server is minimized. |
| 108 | + |
| 109 | +You can find this cache at [`/server/src/utils/configurationCache.ts`](/server/src/utils/configurationCache.ts), and it stores: |
| 110 | +- the user fingerprint |
| 111 | +- the up-to-date Codiga API token, |
| 112 | +- the workspace folders currently open |
| 113 | + |
| 114 | +## GraphQL client, queries and mutations |
| 115 | + |
| 116 | +Both the client and the server uses a GraphQL client to send queries and mutations to Codiga. |
| 117 | + |
| 118 | +The queries are used to fetch timestamp-, snippet- and ruleset related data from Codiga, while mutations are used to send metrics to Codiga |
| 119 | +of the usage of certain functionality, for example when a Rosie fix is applied. |
| 120 | + |
| 121 | +These are available in both the client and the server at `/src/graphql-api/client.ts`, `/src/graphql-api/queries.ts` and `/src/graphql-api/mutations.ts` |
| 122 | + |
| 123 | +### User-Agent |
| 124 | + |
| 125 | +The User-Agent header is sent in order to identify the client application the GraphQL requests are sent from. |
| 126 | + |
| 127 | +It is in the form `<product>/<version>`, e.g. `VsCode/1.70.0`: |
| 128 | +- On client side the product name is fix (`VsCode`), while the version is fetched from the `vscode` api. |
| 129 | +- On server side both the product name and version are retrieved from the server connection `InitializeParams` in `connection.onInitialize()`. |
| 130 | + |
| 131 | +### User fingerprint |
| 132 | + |
| 133 | +In general, the fingerprint is a unique string generated when the extension is installed. |
| 134 | + |
| 135 | +#### Client side |
| 136 | + |
| 137 | +On client side this is achievable (see [`/client/src/utils/configurationUtils.ts`](/client/src/utils/configurationUtils.ts)), and the fingerprint |
| 138 | +is stored in VS Code's localstorage. |
| 139 | + |
| 140 | +#### Server side |
| 141 | + |
| 142 | +On server side, it is a bit limited at the moment. |
| 143 | + |
| 144 | +`server.ts` looks for a command line argument called `fingerprint`. If there is one present (i.e. it is generated in the client application and provided |
| 145 | +for the server), then we use that value on server side as well. |
| 146 | + |
| 147 | +If there is no such command line argument, we generate the fingerprint on server side, we don't have a solution to store its value permanently, |
| 148 | +and keep it same between user sessions and subsequent language server launches. |
| 149 | + |
| 150 | +### Codiga API Token |
| 151 | + |
| 152 | +Having a Codiga account registered, using this token, users can access and use to their private rulesets and rules in the IDE. |
| 153 | + |
| 154 | +The configuration is provided from client side, via the root `package.json` in the `contributes.configuration."codiga.api.token"` property. |
| 155 | + |
| 156 | +## Environments |
| 157 | + |
| 158 | +In case testing on different environments is necessary, you can use the following endpoints: |
| 159 | + |
| 160 | +| Environment | Codiga | Rosie | |
| 161 | +|-------------|---------------------------------------|--------------------------------------------| |
| 162 | +| Production | https://api.codiga.io/graphql | https://analysis.codiga.io/analyze | |
| 163 | +| Staging | https://api-staging.codiga.io/graphql | https://analysis-staging.codiga.io/analyze | |
| 164 | + |
| 165 | +## Testing |
| 166 | + |
| 167 | +There are dedicated npm scripts in the root `package.json` for testing: |
| 168 | +- client: `npm run test:client` |
| 169 | +- server: `npm run test:server` |
| 170 | +- client+server: `npm run test` |
| 171 | + |
| 172 | +### Client side |
| 173 | + |
| 174 | +On client side, tests are located in `/client/src/test`, and they are a mix of unit and integration tests, where integration tests are |
| 175 | +according to VS Code's [Test Extensions](https://code.visualstudio.com/api/working-with-extensions/testing-extension) guide. |
| 176 | + |
| 177 | +They are fundamentally Mocha tests, but integration tests also spin up a separate Extension Host instance of VS Code |
| 178 | +with a preselected workspace folder, which are stored in `/test-fixtures`. |
| 179 | + |
| 180 | +The structure of the test runners is the following: |
| 181 | +- [`runTest.ts`](/client/src/test/runTest.ts): the entry point for testing. |
| 182 | + - Declares the workspace and extension paths and initiates the test execution for different OS platforms. |
| 183 | + - Separate test runners and file paths are associated to each workspace folder, so that functionalities can be tested in better isolation. |
| 184 | +- [`testRunner.ts`](/client/src/test/testRunner.ts): runs one or more Mocha tests based on a file pattern |
| 185 | +- `*TestRunner.ts`: they specify file patterns for running a set of tests |
| 186 | + |
| 187 | +### Server side |
| 188 | + |
| 189 | +Server side tests are pure Mocha tests (located in `/server/src/test`), and they use a mix of real workspace folders and documents, |
| 190 | +and folder and document URIs without physical file system entries backing them. |
| 191 | + |
| 192 | +To avoid all the hassle initializing and using an actual server connection in `server.ts`, the tests use a mock connection object. |
| 193 | + |
| 194 | +First, in order to be able to decide whether the server is initialized from a test |
| 195 | +(i.e. the `connection` variable is imported in various tests), there is a global variable called `isInTestMode` |
| 196 | +declared in [`testConfiguration.ts`](/server/src/testConfiguration.ts) that can be set in each test. For further details, |
| 197 | +see the documentation in `testConfiguration.ts`. |
| 198 | + |
| 199 | +Then, if the `server.ts` is loaded from a test, instead of creating an actual `_Connection` object, we create a dedicated |
| 200 | +`MockConnection` (see [`connectionMocks.ts`](/server/src/test/connectionMocks.ts)). This connection type has no-op event handlers, |
| 201 | +and the customization that can be done, and is necessary at the moment, |
| 202 | +is the underlying project workspace that the server side functionality works with. |
| 203 | + |
| 204 | +When implementing tests, prefer using URI objects for folders and files where possible, without actually creating them |
| 205 | +on the file system. Since not all functionality requires physical files and/or folders to be present, this speeds up |
| 206 | +the tests and avoids handling file creation, cleanup, etc. |
| 207 | + |
| 208 | +## Outstanding issues |
| 209 | + |
| 210 | +- Currently, the Rosie quick fixes are displayed in ignore-apply order instead of the desired apply-ignore order. |
| 211 | + |
| 212 | +## Limitations |
| 213 | + |
| 214 | +- There is no configuration at the moment to debug the server-side code. |
| 215 | +- Neither console nor language client based logging emits any log entry, for some reason. |
| 216 | + |
| 217 | +## How to guide |
| 218 | + |
| 219 | +### Compile and run the extension |
| 220 | + |
| 221 | +In the extension's root directory: |
| 222 | +- Run `npm install` to install dependencies. |
| 223 | +- Run `npm run compile` to compile both the client and server side sources, as well as to generate the webview for the snippets. |
| 224 | +- Hit F5 in VS Code to launch the extension host instance. |
| 225 | + |
| 226 | +### Add support for a new Rosie language |
| 227 | + |
| 228 | +This needs changes on both client and server-side. |
| 229 | + |
| 230 | +#### Client |
| 231 | + |
| 232 | +To simply support a new language: |
| 233 | +- Add the language to `ROSIE_SUPPORTED_LANGUAGES` in [`/client/src/constants.ts`](/client/src/constants.ts). |
| 234 | + |
| 235 | +If default ruleset suggestions are also needed for the language, then: |
| 236 | +- create a new `DEFAULT_<languge>_RULESET_CONFIG` in [`/client/src/constants.ts`](/client/src/constants.ts) with the appropriate |
| 237 | +codiga.yml file content. |
| 238 | +- map the new config constant to the new language in `getCodigaFileContent(Language)` in [`/client/src/features/codiga-file-suggestion.ts`](/client/src/features/codiga-file-suggestion.ts). |
| 239 | + |
| 240 | +#### Server |
| 241 | + |
| 242 | +The steps are the following: |
| 243 | +- related the server, but still on client side, in `initializeLanguageClient()` in `extension.ts`, add the new language's identifier to the |
| 244 | +`LanguageClientOptions`' `documentSelector` property. |
| 245 | +- add a new Map entry to `GRAPHQL_LANGUAGE_TO_ROSIE_LANGUAGE` in [`/server/src/rosie/rosieConstants.ts`](/server/src/rosie/rosieConstants.ts) |
| 246 | +mapping the Rosie language string to the `Language` enum. |
| 247 | +- add a new branch/case in `getRosieRules(Language, Rule[], URI)` in [`server/src/rosie/rosieCache.ts`](/server/src/rosie/rosieCache.ts) to |
| 248 | +define what language(s) of rules will be returned for the new language. For example, currently both JavaScript and TypeScript rules are returned |
| 249 | +for TypeScript files. |
| 250 | + |
| 251 | +### Add a new AST type for Rosie |
| 252 | + |
| 253 | +The single place where the change must be applied is in [`/server/src/constants.ts`](/server/src/constants.ts): |
| 254 | +- create an `ELEMENT_CHECKED_<element name>` const |
| 255 | +- create a `ROSIE_ENTITY_<element name>` const |
| 256 | +- add a new mapping for them in the `ELEMENT_CHECKED_TO_ENTITY_CHECKED` map |
0 commit comments