Skip to content

Commit 73c097e

Browse files
chrisfarmsMarenz
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 f808855 commit 73c097e

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
@@ -4,6 +4,7 @@ Language Features:
44

55

66
Compiler Features:
7+
* Language Server: Add configuration option to apply custom remappings via ``remappings`` in the LSP settings object.
78

89

910
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>
@@ -149,7 +150,7 @@ LanguageServer::LanguageServer(Transport& _transport):
149150
{"textDocument/semanticTokens/full", bind(&LanguageServer::semanticTokensFull, this, _1, _2)},
150151
{"workspace/didChangeConfiguration", bind(&LanguageServer::handleWorkspaceDidChangeConfiguration, this, _2)},
151152
},
152-
m_fileRepository("/" /* basePath */, {} /* no search paths */),
153+
m_fileRepository("/" /* basePath */, {} /* no search paths */, {} /* no remappings */),
153154
m_compilerStack{m_fileRepository.reader()}
154155
{
155156
}
@@ -210,6 +211,40 @@ void LanguageServer::changeConfiguration(Json::Value const& _settings)
210211
if (typeFailureCount)
211212
m_client.trace("Invalid JSON configuration passed. \"include-paths\" must be an array of strings.");
212213
}
214+
215+
Json::Value jsonRemappings = _settings["remappings"];
216+
217+
if (jsonRemappings)
218+
{
219+
bool typeFailures = false;
220+
if (jsonRemappings.isArray())
221+
{
222+
vector<frontend::ImportRemapper::Remapping> remappings;
223+
for (Json::Value const& jsonPath: jsonRemappings)
224+
{
225+
if (
226+
jsonPath.isObject() &&
227+
jsonPath.get("context", "").isString() &&
228+
jsonPath.get("prefix", "").isString() &&
229+
jsonPath.get("target", "").isString()
230+
)
231+
remappings.emplace_back(ImportRemapper::Remapping{
232+
jsonPath.get("context", "").asString(),
233+
jsonPath.get("prefix", "").asString(),
234+
jsonPath.get("target", "").asString(),
235+
});
236+
else
237+
typeFailures = true;
238+
}
239+
m_fileRepository.setRemappings(std::move(remappings));
240+
}
241+
else
242+
typeFailures = true;
243+
244+
if (typeFailures)
245+
m_client.trace("Invalid JSON configuration passed. \"remappings\" should be of form [{\"context\":\"\", \"prefix\":\"foo\", \"target\":\"bar\"}].");
246+
247+
}
213248
}
214249

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

238-
FileRepository oldRepository(m_fileRepository.basePath(), m_fileRepository.includePaths());
273+
FileRepository oldRepository(m_fileRepository.basePath(), m_fileRepository.includePaths(), m_fileRepository.remappings());
239274
swap(oldRepository, m_fileRepository);
240275

241276
// Load all solidity files from project.
@@ -259,6 +294,7 @@ void LanguageServer::compile()
259294
// TODO: optimize! do not recompile if nothing has changed (file(s) not flagged dirty).
260295

261296
m_compilerStack.reset(false);
297+
m_compilerStack.setRemappings(m_fileRepository.remappings());
262298
m_compilerStack.setSources(m_fileRepository.sourceUnits());
263299
m_compilerStack.compile(CompilerStack::State::AnalysisPerformed);
264300
}
@@ -402,7 +438,7 @@ void LanguageServer::handleInitialize(MessageID _id, Json::Value const& _args)
402438
if (_args["trace"])
403439
setTrace(_args["trace"]);
404440

405-
m_fileRepository = FileRepository(rootPath, {});
441+
m_fileRepository = FileRepository(rootPath, {}, {});
406442
if (_args["initializationOptions"].isObject())
407443
changeConfiguration(_args["initializationOptions"]);
408444

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)