Skip to content
This repository was archived by the owner on May 4, 2023. It is now read-only.

Commit 012b00f

Browse files
Merge pull request #79 from codiga/rosie-migration-to-language-server
Rosie migration to language server
2 parents 735fdd5 + cf0849d commit 012b00f

File tree

147 files changed

+8820
-2432
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

147 files changed

+8820
-2432
lines changed

.gitignore

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,4 +5,4 @@ node_modules
55
*.vsix
66
.DS_Store
77
*~
8-
webview
8+
/webview
File renamed without changes.
File renamed without changes.
File renamed without changes.

DEVELOPMENT.md

Lines changed: 256 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,256 @@
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: &lt;fix description>**: applies an actual code fix for the violation
96+
- **Ignore rule &lt;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

Comments
 (0)