Skip to content

Commit 5cfb4b4

Browse files
chrisfarmsnikola-matic
authored andcommitted
lsp: support setting remappings on init
support remappings in the lsp server by allowing reading the required configuration during lsp "initialize". the format expected is: ``` { "remappings": [{context: "", prefix: "foo", target: "bar"}] } ```
1 parent 2cc6610 commit 5cfb4b4

File tree

5 files changed

+128
-6
lines changed

5 files changed

+128
-6
lines changed

Changelog.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ Compiler Features:
99
* Standard JSON: Add a boolean field `settings.metadata.appendCBOR` that skips CBOR metadata from getting appended at the end of the bytecode.
1010
* Yul Optimizer: Allow replacing the previously hard-coded cleanup sequence by specifying custom steps after a colon delimiter (``:``) in the sequence string.
1111
* Language Server: Add basic document hover support.
12+
* Language Server: Add configuration option to apply custom remappings via ``remappings`` in the LSP settings object.
1213

1314

1415
Bugfixes:

libsolidity/lsp/FileRepository.cpp

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
*/
1717
// SPDX-License-Identifier: GPL-3.0
1818

19+
#include <libsolidity/interface/ImportRemapper.h>
1920
#include <libsolidity/lsp/FileRepository.h>
2021
#include <libsolidity/lsp/Transport.h>
2122
#include <libsolidity/lsp/Utils.h>
@@ -43,9 +44,10 @@ using solidity::util::readFileAsString;
4344
using solidity::util::joinHumanReadable;
4445
using solidity::util::Result;
4546

46-
FileRepository::FileRepository(boost::filesystem::path _basePath, std::vector<boost::filesystem::path> _includePaths):
47+
FileRepository::FileRepository(boost::filesystem::path _basePath, std::vector<boost::filesystem::path> _includePaths, vector<frontend::ImportRemapper::Remapping> _remappings):
4748
m_basePath(std::move(_basePath)),
48-
m_includePaths(std::move(_includePaths))
49+
m_includePaths(std::move(_includePaths)),
50+
m_remappings(std::move(_remappings))
4951
{
5052
}
5153

@@ -54,6 +56,11 @@ void FileRepository::setIncludePaths(std::vector<boost::filesystem::path> _paths
5456
m_includePaths = std::move(_paths);
5557
}
5658

59+
void FileRepository::setRemappings(vector<frontend::ImportRemapper::Remapping> _remappings)
60+
{
61+
m_remappings = std::move(_remappings);
62+
}
63+
5764
string FileRepository::sourceUnitNameToUri(string const& _sourceUnitName) const
5865
{
5966
regex const windowsDriveLetterPath("^[a-zA-Z]:/");

libsolidity/lsp/FileRepository.h

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
#pragma once
1919

2020
#include <libsolidity/interface/FileReader.h>
21+
#include <libsolidity/interface/ImportRemapper.h>
2122
#include <libsolutil/Result.h>
2223

2324
#include <string>
@@ -29,11 +30,14 @@ namespace solidity::lsp
2930
class FileRepository
3031
{
3132
public:
32-
FileRepository(boost::filesystem::path _basePath, std::vector<boost::filesystem::path> _includePaths);
33+
FileRepository(boost::filesystem::path _basePath, std::vector<boost::filesystem::path> _includePaths, std::vector<frontend::ImportRemapper::Remapping> _remappings);
3334

3435
std::vector<boost::filesystem::path> const& includePaths() const noexcept { return m_includePaths; }
3536
void setIncludePaths(std::vector<boost::filesystem::path> _paths);
3637

38+
std::vector<frontend::ImportRemapper::Remapping> const& remappings() const noexcept { return m_remappings; }
39+
void setRemappings(std::vector<frontend::ImportRemapper::Remapping> _remappings);
40+
3741
boost::filesystem::path const& basePath() const { return m_basePath; }
3842

3943
/// Translates a compiler-internal source unit name to an LSP client path.
@@ -64,6 +68,9 @@ class FileRepository
6468
/// Additional directories used for resolving relative paths in imports.
6569
std::vector<boost::filesystem::path> m_includePaths;
6670

71+
/// Remappings of imports.
72+
std::vector<frontend::ImportRemapper::Remapping> m_remappings;
73+
6774
/// Mapping of source unit names to their URIs as understood by the client.
6875
StringMap m_sourceUnitNamesToUri;
6976

libsolidity/lsp/LanguageServer.cpp

Lines changed: 39 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
#include <libsolidity/ast/AST.h>
1919
#include <libsolidity/ast/ASTUtils.h>
2020
#include <libsolidity/ast/ASTVisitor.h>
21+
#include <libsolidity/interface/ImportRemapper.h>
2122
#include <libsolidity/interface/ReadFile.h>
2223
#include <libsolidity/interface/StandardCompiler.h>
2324
#include <libsolidity/lsp/LanguageServer.h>
@@ -151,7 +152,7 @@ LanguageServer::LanguageServer(Transport& _transport):
151152
{"textDocument/semanticTokens/full", bind(&LanguageServer::semanticTokensFull, this, _1, _2)},
152153
{"workspace/didChangeConfiguration", bind(&LanguageServer::handleWorkspaceDidChangeConfiguration, this, _2)},
153154
},
154-
m_fileRepository("/" /* basePath */, {} /* no search paths */),
155+
m_fileRepository("/" /* basePath */, {} /* no search paths */, {} /* no remappings */),
155156
m_compilerStack{m_fileRepository.reader()}
156157
{
157158
}
@@ -212,6 +213,40 @@ void LanguageServer::changeConfiguration(Json::Value const& _settings)
212213
if (typeFailureCount)
213214
m_client.trace("Invalid JSON configuration passed. \"include-paths\" must be an array of strings.");
214215
}
216+
217+
Json::Value jsonRemappings = _settings["remappings"];
218+
219+
if (jsonRemappings)
220+
{
221+
bool typeFailures = false;
222+
if (jsonRemappings.isArray())
223+
{
224+
vector<frontend::ImportRemapper::Remapping> remappings;
225+
for (Json::Value const& jsonPath: jsonRemappings)
226+
{
227+
if (
228+
jsonPath.isObject() &&
229+
jsonPath.get("context", "").isString() &&
230+
jsonPath.get("prefix", "").isString() &&
231+
jsonPath.get("target", "").isString()
232+
)
233+
remappings.emplace_back(ImportRemapper::Remapping{
234+
jsonPath.get("context", "").asString(),
235+
jsonPath.get("prefix", "").asString(),
236+
jsonPath.get("target", "").asString(),
237+
});
238+
else
239+
typeFailures = true;
240+
}
241+
m_fileRepository.setRemappings(std::move(remappings));
242+
}
243+
else
244+
typeFailures = true;
245+
246+
if (typeFailures)
247+
m_client.trace("Invalid JSON configuration passed. \"remappings\" should be of form [{\"context\":\"\", \"prefix\":\"foo\", \"target\":\"bar\"}].");
248+
249+
}
215250
}
216251

217252
vector<boost::filesystem::path> LanguageServer::allSolidityFilesFromProject() const
@@ -237,7 +272,7 @@ void LanguageServer::compile()
237272
// For files that are not open, we have to take changes on disk into account,
238273
// so we just remove all non-open files.
239274

240-
FileRepository oldRepository(m_fileRepository.basePath(), m_fileRepository.includePaths());
275+
FileRepository oldRepository(m_fileRepository.basePath(), m_fileRepository.includePaths(), m_fileRepository.remappings());
241276
swap(oldRepository, m_fileRepository);
242277

243278
// Load all solidity files from project.
@@ -261,6 +296,7 @@ void LanguageServer::compile()
261296
// TODO: optimize! do not recompile if nothing has changed (file(s) not flagged dirty).
262297

263298
m_compilerStack.reset(false);
299+
m_compilerStack.setRemappings(m_fileRepository.remappings());
264300
m_compilerStack.setSources(m_fileRepository.sourceUnits());
265301
m_compilerStack.compile(CompilerStack::State::AnalysisPerformed);
266302
}
@@ -404,7 +440,7 @@ void LanguageServer::handleInitialize(MessageID _id, Json::Value const& _args)
404440
if (_args["trace"])
405441
setTrace(_args["trace"]);
406442

407-
m_fileRepository = FileRepository(rootPath, {});
443+
m_fileRepository = FileRepository(rootPath, {}, {});
408444
if (_args["initializationOptions"].isObject())
409445
changeConfiguration(_args["initializationOptions"]);
410446

test/lsp.py

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1085,6 +1085,13 @@ def open_file_and_wait_for_diagnostics(
10851085
)
10861086
return self.wait_for_diagnostics(solc_process)
10871087

1088+
def expect_report(self, published_diagnostics, uri):
1089+
for report in published_diagnostics:
1090+
if report['uri'] == uri:
1091+
return report
1092+
self.expect_true(False, f"expected ${uri} in published_diagnostics")
1093+
return None
1094+
10881095
def expect_true(
10891096
self,
10901097
actual,
@@ -1534,6 +1541,70 @@ def test_custom_includes(self, solc: JsonRpcProcess) -> None:
15341541
self.expect_equal(len(diagnostics), 1, "no diagnostics")
15351542
self.expect_diagnostic(diagnostics[0], code=2018, lineNo=5, startEndColumns=(4, 62))
15361543

1544+
def test_remapping_wrong_paramateres(self, solc: JsonRpcProcess) -> None:
1545+
self.setup_lsp(solc, expose_project_root=False)
1546+
solc.send_notification(
1547+
'workspace/didChangeConfiguration', {
1548+
'settings': {
1549+
'remappings': [
1550+
{
1551+
'context': False,
1552+
'prefix': 1,
1553+
'target': f"{self.project_root_dir}/other-include-dir/otherlib/"
1554+
}
1555+
]
1556+
}
1557+
}
1558+
)
1559+
1560+
response = solc.receive_message()
1561+
self.expect_equal(response["method"], "$/logTrace")
1562+
1563+
if not response["params"]["message"].startswith("Invalid JSON configuration passed."):
1564+
raise Exception("Expected JSON error.")
1565+
1566+
def test_remapping(self, solc: JsonRpcProcess) -> None:
1567+
self.setup_lsp(solc, expose_project_root=False)
1568+
solc.send_notification(
1569+
'workspace/didChangeConfiguration', {
1570+
'settings': {
1571+
'remappings': [
1572+
{
1573+
'context': '',
1574+
'prefix': '@other/',
1575+
'target': f"{self.project_root_dir}/other-include-dir/otherlib/"
1576+
}
1577+
]
1578+
}
1579+
}
1580+
)
1581+
1582+
# test file
1583+
file_with_remapped_import_uri = 'file:///remapped-include.sol'
1584+
solc.send_message('textDocument/didOpen', {
1585+
'textDocument': {
1586+
'uri': file_with_remapped_import_uri,
1587+
'languageId': 'Solidity',
1588+
'version': 1,
1589+
'text': ''.join([
1590+
'// SPDX-License-Identifier: UNLICENSED\n',
1591+
'pragma solidity >=0.8.0;\n',
1592+
'import "@other/otherlib.sol";\n',
1593+
])
1594+
}
1595+
})
1596+
published_diagnostics = self.wait_for_diagnostics(solc)
1597+
self.expect_equal(len(published_diagnostics), 2, "expected reports for two files")
1598+
1599+
report = self.expect_report(published_diagnostics, file_with_remapped_import_uri)
1600+
diagnostics = report['diagnostics']
1601+
self.expect_equal(len(diagnostics), 0, "no diagnostics")
1602+
1603+
report = self.expect_report(published_diagnostics, f"{self.project_root_uri}/other-include-dir/otherlib/otherlib.sol")
1604+
diagnostics = report['diagnostics']
1605+
self.expect_equal(len(diagnostics), 1, "no diagnostics")
1606+
self.expect_diagnostic(diagnostics[0], code=2018, lineNo=5, startEndColumns=(4, 62))
1607+
15371608
def test_custom_includes_with_full_project(self, solc: JsonRpcProcess) -> None:
15381609
"""
15391610
Tests loading all project files while having custom include directories configured.

0 commit comments

Comments
 (0)