Skip to content

Commit 382aee5

Browse files
Merge pull request #68 from v4rgas/feat/dart
Feat/dart
2 parents 79fdaf2 + c33215c commit 382aee5

File tree

10 files changed

+349
-4
lines changed

10 files changed

+349
-4
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -402,6 +402,7 @@ src/multilspy/language_servers/gopls/static/
402402
src/multilspy/language_servers/omnisharp/static/
403403
src/multilspy/language_servers/rust_analyzer/static/
404404
src/multilspy/language_servers/typescript_language_server/static/
405+
src/multilspy/language_servers/dart_language_server/static/
405406

406407
# Virtual Environment
407408
.venv/

README.md

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
## Introduction
55
This repository hosts `multilspy`, a library developed as part of research conducted for NeruIPS 2023 paper titled ["Monitor-Guided Decoding of Code LMs with Static Analysis of Repository Context"](https://neurips.cc/virtual/2023/poster/70362) (["Guiding Language Models of Code with Global Context using Monitors"](https://arxiv.org/abs/2306.10763) on Arxiv). The paper introduces Monitor-Guided Decoding (MGD) for code generation using Language Models, where a monitor uses static analysis to guide the decoding, ensuring that the generated code follows various correctness properties, like absence of hallucinated symbol names, valid order of method calls, etc. For further details about Monitor-Guided Decoding, please refer to the paper and GitHub repository [microsoft/monitors4codegen](https://github.com/microsoft/monitors4codegen).
66

7-
`multilspy` is a cross-platform library designed to simplify the process of creating language server clients to query and obtain results of various static analyses from a wide variety of language servers that communicate over the [Language Server Protocol](https://microsoft.github.io/language-server-protocol/). It is easily extensible to support any [language that has a Language Server](https://microsoft.github.io/language-server-protocol/implementors/servers/) and currently supports Java, Rust, C#, Go, JavaScript and Python. We aim to continuously add support for more language servers and languages.
7+
`multilspy` is a cross-platform library designed to simplify the process of creating language server clients to query and obtain results of various static analyses from a wide variety of language servers that communicate over the [Language Server Protocol](https://microsoft.github.io/language-server-protocol/). It is easily extensible to support any [language that has a Language Server](https://microsoft.github.io/language-server-protocol/implementors/servers/) and we aim to continuously add support for more language servers and languages.
88

99
[Language servers]((https://microsoft.github.io/language-server-protocol/overviews/lsp/overview/)) are tools that perform a variety of static analyses on code repositories and provide useful information such as type-directed code completion suggestions, symbol definition locations, symbol references, etc., over the [Language Server Protocol (LSP)](https://microsoft.github.io/language-server-protocol/overviews/lsp/overview/). Since LSP is language-agnostic, `multilspy` can provide the results for static analyses of code in different languages over a common interface.
1010

@@ -35,14 +35,29 @@ To install `multilspy` using pip, execute the following command:
3535
pip install multilspy
3636
```
3737

38+
## Supported Languages
39+
`multilspy` currently supports the following languages:
40+
| Code Language | Language Server |
41+
| --- | --- |
42+
| java | Eclipse JDTLS |
43+
| python | jedi-language-server |
44+
| rust | Rust Analyzer |
45+
| csharp | OmniSharp / RazorSharp |
46+
| typescript | TypeScriptLanguageServer |
47+
| javascript | TypeScriptLanguageServer |
48+
| go | gopls |
49+
| dart | Dart |
50+
| ruby | Solargraph |
51+
52+
3853
## Usage
3954
Example usage:
4055
```python
4156
from multilspy import SyncLanguageServer
4257
from multilspy.multilspy_config import MultilspyConfig
4358
from multilspy.multilspy_logger import MultilspyLogger
4459
...
45-
config = MultilspyConfig.from_dict({"code_language": "java"}) # Also supports "python", "rust", "csharp", "typescript", "javascript", "go"
60+
config = MultilspyConfig.from_dict({"code_language": "java"}) # Also supports "python", "rust", "csharp", "typescript", "javascript", "go", "dart", "ruby"
4661
logger = MultilspyLogger()
4762
lsp = SyncLanguageServer.create(config, logger, "/abs/path/to/project/root/")
4863
with lsp.start_server():

pyproject.toml

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ version = "0.0.13"
99
authors = [
1010
{ name="Lakshya A Agrawal", email="[email protected]" },
1111
]
12-
description = "A language-agnostic LSP client in Python, with a library interface. Intended to be used to build applications around language servers. Currently multilspy supports language servers for Python, Rust, Java, Go, JavaScript, Ruby and C#. Originally appeared as part of Monitor-Guided Decoding (https://github.com/microsoft/monitors4codegen)"
12+
description = "A language-agnostic LSP client in Python, with a library interface. Intended to be used to build applications around language servers. Currently multilspy supports language servers for Python, Rust, Java, Go, JavaScript, Ruby, C# and Dart. Originally appeared as part of Monitor-Guided Decoding (https://github.com/microsoft/monitors4codegen)"
1313
readme = "README.md"
1414
requires-python = ">=3.8, <4.0"
1515
classifiers = [
@@ -22,7 +22,10 @@ classifiers = [
2222
"Programming Language :: Java",
2323
"Programming Language :: Python",
2424
"Programming Language :: Rust",
25-
"Programming Language :: JavaScript"
25+
"Programming Language :: JavaScript",
26+
"Programming Language :: Go",
27+
"Programming Language :: Ruby",
28+
"Programming Language :: Dart",
2629
]
2730

2831
dependencies = [

src/multilspy/language_server.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,10 @@ def create(cls, config: MultilspyConfig, logger: MultilspyLogger, repository_roo
108108
from multilspy.language_servers.solargraph.solargraph import Solargraph
109109

110110
return Solargraph(config, logger, repository_root_path)
111+
elif config.code_language == Language.DART:
112+
from multilspy.language_servers.dart_language_server.dart_language_server import DartLanguageServer
113+
114+
return DartLanguageServer(config, logger, repository_root_path)
111115
else:
112116
logger.log(f"Language {config.code_language} is not supported", logging.ERROR)
113117
raise MultilspyException(f"Language {config.code_language} is not supported")
Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
from contextlib import asynccontextmanager
2+
import logging
3+
import os
4+
import pathlib
5+
import shutil
6+
import stat
7+
from typing import AsyncIterator
8+
from multilspy.language_server import LanguageServer
9+
from multilspy.lsp_protocol_handler.server import ProcessLaunchInfo
10+
import json
11+
from multilspy.multilspy_utils import FileUtils, PlatformUtils
12+
13+
14+
class DartLanguageServer(LanguageServer):
15+
"""
16+
Provides Dart specific instantiation of the LanguageServer class. Contains various configurations and settings specific to Dart.
17+
"""
18+
19+
def __init__(self, config, logger, repository_root_path):
20+
"""
21+
Creates a DartServer instance. This class is not meant to be instantiated directly. Use LanguageServer.create() instead.
22+
"""
23+
24+
executable_path = self.setup_runtime_dependencies(logger)
25+
super().__init__(
26+
config,
27+
logger,
28+
repository_root_path,
29+
ProcessLaunchInfo(cmd=executable_path, cwd=repository_root_path),
30+
"dart",
31+
)
32+
33+
def setup_runtime_dependencies(self, logger: "MultilspyLogger") -> str:
34+
platform_id = PlatformUtils.get_platform_id()
35+
36+
with open(os.path.join(os.path.dirname(__file__), "runtime_dependencies.json"), "r") as f:
37+
d = json.load(f)
38+
del d["_description"]
39+
40+
runtime_dependencies = d["runtimeDependencies"]
41+
runtime_dependencies = [
42+
dependency for dependency in runtime_dependencies if dependency["platformId"] == platform_id.value
43+
]
44+
45+
assert len(runtime_dependencies) == 1
46+
dependency = runtime_dependencies[0]
47+
48+
dart_ls_dir = os.path.join(os.path.dirname(__file__), "static", "dart-language-server")
49+
dart_executable_path = os.path.join(dart_ls_dir, dependency["binaryName"])
50+
51+
if not os.path.exists(dart_ls_dir):
52+
os.makedirs(dart_ls_dir)
53+
FileUtils.download_and_extract_archive(
54+
logger, dependency["url"], dart_ls_dir, dependency["archiveType"]
55+
)
56+
57+
58+
assert os.path.exists(dart_executable_path)
59+
os.chmod(dart_executable_path, stat.S_IEXEC)
60+
61+
return f"{dart_executable_path} language-server --client-id multilspy.dart --client-version 1.2"
62+
63+
64+
def _get_initialize_params(self, repository_absolute_path: str):
65+
"""
66+
Returns the initialize params for the Dart Language Server.
67+
"""
68+
with open(
69+
os.path.join(os.path.dirname(__file__), "initialize_params.json"), "r"
70+
) as f:
71+
d = json.load(f)
72+
73+
del d["_description"]
74+
75+
d["processId"] = os.getpid()
76+
assert d["rootPath"] == "$rootPath"
77+
d["rootPath"] = repository_absolute_path
78+
79+
assert d["rootUri"] == "$rootUri"
80+
d["rootUri"] = pathlib.Path(repository_absolute_path).as_uri()
81+
82+
assert d["workspaceFolders"][0]["uri"] == "$uri"
83+
d["workspaceFolders"][0]["uri"] = pathlib.Path(
84+
repository_absolute_path
85+
).as_uri()
86+
87+
assert d["workspaceFolders"][0]["name"] == "$name"
88+
d["workspaceFolders"][0]["name"] = os.path.basename(repository_absolute_path)
89+
90+
return d
91+
92+
@asynccontextmanager
93+
async def start_server(self) -> AsyncIterator["DartLanguageServer"]:
94+
"""
95+
Start the language server and yield when the server is ready.
96+
"""
97+
98+
async def execute_client_command_handler(params):
99+
return []
100+
101+
async def do_nothing(params):
102+
return
103+
104+
async def check_experimental_status(params):
105+
pass
106+
107+
async def window_log_message(msg):
108+
self.logger.log(f"LSP: window/logMessage: {msg}", logging.INFO)
109+
110+
self.server.on_request("client/registerCapability", do_nothing)
111+
self.server.on_notification("language/status", do_nothing)
112+
self.server.on_notification("window/logMessage", window_log_message)
113+
self.server.on_request(
114+
"workspace/executeClientCommand", execute_client_command_handler
115+
)
116+
self.server.on_notification("$/progress", do_nothing)
117+
self.server.on_notification("textDocument/publishDiagnostics", do_nothing)
118+
self.server.on_notification("language/actionableNotification", do_nothing)
119+
self.server.on_notification(
120+
"experimental/serverStatus", check_experimental_status
121+
)
122+
123+
async with super().start_server():
124+
self.logger.log(
125+
"Starting dart-language-server server process", logging.INFO
126+
)
127+
await self.server.start()
128+
initialize_params = self._get_initialize_params(self.repository_root_path)
129+
self.logger.log(
130+
"Sending initialize request to dart-language-server",
131+
logging.DEBUG,
132+
)
133+
init_response = await self.server.send_request(
134+
"initialize", initialize_params
135+
)
136+
self.logger.log(
137+
f"Received initialize response from dart-language-server: {init_response}",
138+
logging.INFO,
139+
)
140+
141+
self.server.notify.initialized({})
142+
143+
yield self
144+
145+
await self.server.shutdown()
146+
await self.server.stop()
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
{
2+
"_description": "This file contains the initialization parameters for the Dart Language Server.",
3+
"processId": "$processId",
4+
"rootPath": "$rootPath",
5+
"rootUri": "$rootUri",
6+
"capabilities": {},
7+
"initializationOptions": {
8+
"onlyAnalyzeProjectsWithOpenFiles": false,
9+
"suggestFromUnimportedLibraries": true,
10+
"closingLabels": false,
11+
"outline": false,
12+
"flutterOutline": false,
13+
"allowOpenUri": false
14+
},
15+
"trace": "verbose",
16+
"workspaceFolders": [
17+
{
18+
"uri": "$uri",
19+
"name": "$name"
20+
}
21+
]
22+
23+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
{
2+
"_description": "Used to download the runtime dependencies for running Dart Language Server, downloaded from https://dart.dev/get-dart/archive",
3+
"runtimeDependencies": [
4+
{
5+
"id": "DartLanguageServer",
6+
"description": "Dart Language Server for Linux (x64)",
7+
"url": "https://storage.googleapis.com/dart-archive/channels/stable/release/3.7.1/sdk/dartsdk-linux-x64-release.zip",
8+
"platformId": "linux-x64",
9+
"archiveType": "zip",
10+
"binaryName": "dart-sdk/bin/dart"
11+
}
12+
]
13+
}

src/multilspy/multilspy_config.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ class Language(str, Enum):
1818
JAVASCRIPT = "javascript"
1919
GO = "go"
2020
RUBY = "ruby"
21+
DART = "dart"
2122

2223
def __str__(self) -> str:
2324
return self.value
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
"""
2+
This file contains tests for running the Dart Language Server.
3+
"""
4+
5+
import pytest
6+
from multilspy import LanguageServer
7+
from multilspy.multilspy_config import Language
8+
from tests.test_utils import create_test_context
9+
from pathlib import PurePath
10+
11+
pytest_plugins = ("pytest_asyncio",)
12+
13+
14+
@pytest.mark.asyncio
15+
async def test_multilspy_dart_open_nutri_tracker():
16+
"""
17+
Test the working of multilspy with a Dart repository.
18+
"""
19+
code_language = Language.DART
20+
params = {
21+
"code_language": code_language,
22+
"repo_url": "https://github.com/simonoppowa/OpenNutriTracker/",
23+
"repo_commit": "2df39185bdd822dec6a0e521f4c14e3eab6b0805",
24+
}
25+
with create_test_context(params) as context:
26+
lsp = LanguageServer.create(
27+
context.config, context.logger, context.source_directory
28+
)
29+
30+
async with lsp.start_server():
31+
result = await lsp.request_document_symbols(
32+
str(
33+
PurePath("lib/core/presentation/widgets/copy_or_delete_dialog.dart")
34+
)
35+
)
36+
37+
assert isinstance(result, tuple)
38+
assert len(result) == 2
39+
40+
symbols = result[0]
41+
for symbol in symbols:
42+
del symbol["deprecated"]
43+
del symbol["kind"]
44+
del symbol["location"]["uri"]
45+
46+
assert symbols == [
47+
{
48+
"location": {
49+
"range": {
50+
"end": {"character": 24, "line": 3},
51+
"start": {"character": 6, "line": 3},
52+
},
53+
},
54+
"name": "CopyOrDeleteDialog",
55+
},
56+
{
57+
"containerName": "CopyOrDeleteDialog",
58+
"location": {
59+
"range": {
60+
"end": {"character": 26, "line": 4},
61+
"start": {"character": 8, "line": 4},
62+
},
63+
},
64+
"name": "CopyOrDeleteDialog",
65+
},
66+
{
67+
"containerName": "CopyOrDeleteDialog",
68+
"location": {
69+
"range": {
70+
"end": {"character": 14, "line": 7},
71+
"start": {"character": 9, "line": 7},
72+
},
73+
},
74+
"name": "build",
75+
},
76+
]

0 commit comments

Comments
 (0)