Skip to content

Commit b6ba432

Browse files
lsp: Always load all solidity files from project for analyzing.
Co-authored-by: Kamil Śliwak <[email protected]>
1 parent 0e2ab05 commit b6ba432

File tree

10 files changed

+181
-44
lines changed

10 files changed

+181
-44
lines changed

Changelog.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ Language Features:
99
Compiler Features:
1010
* Code Generator: More efficient overflow checks for multiplication.
1111
* Yul Optimizer: Simplify the starting offset of zero-length operations to zero.
12+
* Language Server: Analyze all files in a project by default (can be customized by setting ``'file-load-strategy'`` to ``'directly-opened-and-on-import'`` in LSP settings object).
1213

1314

1415
Bugfixes:

libsolidity/lsp/FileRepository.cpp

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
// SPDX-License-Identifier: GPL-3.0
1818

1919
#include <libsolidity/lsp/FileRepository.h>
20+
#include <libsolidity/lsp/Transport.h>
2021
#include <libsolidity/lsp/Utils.h>
2122

2223
#include <libsolutil/StringUtils.h>
@@ -25,11 +26,14 @@
2526
#include <range/v3/algorithm/none_of.hpp>
2627
#include <range/v3/range/conversion.hpp>
2728
#include <range/v3/view/transform.hpp>
29+
#include <boost/algorithm/string/predicate.hpp>
2830

2931
#include <regex>
3032

3133
#include <boost/algorithm/string/predicate.hpp>
3234

35+
#include <fmt/format.h>
36+
3337
using namespace std;
3438
using namespace solidity;
3539
using namespace solidity::lsp;
@@ -84,6 +88,7 @@ string FileRepository::sourceUnitNameToUri(string const& _sourceUnitName) const
8488

8589
string FileRepository::uriToSourceUnitName(string const& _path) const
8690
{
91+
lspAssert(boost::algorithm::starts_with(_path, "file://"), ErrorCode::InternalError, "URI must start with file://");
8792
return stripFileUriSchemePrefix(_path);
8893
}
8994

@@ -92,6 +97,7 @@ void FileRepository::setSourceByUri(string const& _uri, string _source)
9297
// This is needed for uris outside the base path. It can lead to collisions,
9398
// but we need to mostly rewrite this in a future version anyway.
9499
auto sourceUnitName = uriToSourceUnitName(_uri);
100+
lspDebug(fmt::format("FileRepository.setSourceByUri({}): {}", _uri, _source));
95101
m_sourceUnitNamesToUri.emplace(sourceUnitName, _uri);
96102
m_sourceCodes[sourceUnitName] = std::move(_source);
97103
}

libsolidity/lsp/LanguageServer.cpp

Lines changed: 64 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
#include <liblangutil/SourceReferenceExtractor.h>
3333
#include <liblangutil/CharStream.h>
3434

35+
#include <libsolutil/CommonIO.h>
3536
#include <libsolutil/Visitor.h>
3637
#include <libsolutil/JSON.h>
3738

@@ -42,6 +43,8 @@
4243
#include <ostream>
4344
#include <string>
4445

46+
#include <fmt/format.h>
47+
4548
using namespace std;
4649
using namespace std::string_literals;
4750
using namespace std::placeholders;
@@ -118,7 +121,7 @@ LanguageServer::LanguageServer(Transport& _transport):
118121
{"cancelRequest", [](auto, auto) {/*nothing for now as we are synchronous */}},
119122
{"exit", [this](auto, auto) { m_state = (m_state == State::ShutdownRequested ? State::ExitRequested : State::ExitWithoutShutdown); }},
120123
{"initialize", bind(&LanguageServer::handleInitialize, this, _1, _2)},
121-
{"initialized", [](auto, auto) {}},
124+
{"initialized", bind(&LanguageServer::handleInitialized, this, _1, _2)},
122125
{"$/setTrace", [this](auto, Json::Value const& args) { setTrace(args["value"]); }},
123126
{"shutdown", [this](auto, auto) { m_state = State::ShutdownRequested; }},
124127
{"textDocument/definition", GotoDefinition(*this) },
@@ -147,6 +150,26 @@ Json::Value LanguageServer::toJson(SourceLocation const& _location)
147150

148151
void LanguageServer::changeConfiguration(Json::Value const& _settings)
149152
{
153+
// The settings item: "file-load-strategy" (enum) defaults to "project-directory" if not (or not correctly) set.
154+
// It can be overridden during client's handshake or at runtime, as usual.
155+
//
156+
// If this value is set to "project-directory" (default), all .sol files located inside the project directory or reachable through symbolic links will be subject to operations.
157+
//
158+
// Operations include compiler analysis, but also finding all symbolic references or symbolic renaming.
159+
//
160+
// If this value is set to "directly-opened-and-on-import", then only currently directly opened files and
161+
// those files being imported directly or indirectly will be included in operations.
162+
if (_settings["file-load-strategy"])
163+
{
164+
auto const text = _settings["file-load-strategy"].asString();
165+
if (text == "project-directory")
166+
m_fileLoadStrategy = FileLoadStrategy::ProjectDirectory;
167+
else if (text == "directly-opened-and-on-import")
168+
m_fileLoadStrategy = FileLoadStrategy::DirectlyOpenedAndOnImported;
169+
else
170+
lspAssert(false, ErrorCode::InvalidParams, "Invalid file load strategy: " + text);
171+
}
172+
150173
m_settingsObject = _settings;
151174
Json::Value jsonIncludePaths = _settings["include-paths"];
152175

@@ -173,6 +196,23 @@ void LanguageServer::changeConfiguration(Json::Value const& _settings)
173196
}
174197
}
175198

199+
vector<boost::filesystem::path> LanguageServer::allSolidityFilesFromProject() const
200+
{
201+
namespace fs = boost::filesystem;
202+
203+
std::vector<fs::path> collectedPaths{};
204+
205+
// We explicitly decided against including all files from include paths but leave the possibility
206+
// open for a future PR to enable such a feature to be optionally enabled (default disabled).
207+
208+
auto directoryIterator = fs::recursive_directory_iterator(m_fileRepository.basePath(), fs::symlink_option::recurse);
209+
for (fs::directory_entry const& dirEntry: directoryIterator)
210+
if (dirEntry.path().extension() == ".sol")
211+
collectedPaths.push_back(dirEntry.path());
212+
213+
return collectedPaths;
214+
}
215+
176216
void LanguageServer::compile()
177217
{
178218
// For files that are not open, we have to take changes on disk into account,
@@ -181,6 +221,18 @@ void LanguageServer::compile()
181221
FileRepository oldRepository(m_fileRepository.basePath(), m_fileRepository.includePaths());
182222
swap(oldRepository, m_fileRepository);
183223

224+
// Load all solidity files from project.
225+
if (m_fileLoadStrategy == FileLoadStrategy::ProjectDirectory)
226+
for (auto const& projectFile: allSolidityFilesFromProject())
227+
{
228+
lspDebug(fmt::format("adding project file: {}", projectFile.generic_string()));
229+
m_fileRepository.setSourceByUri(
230+
m_fileRepository.sourceUnitNameToUri(projectFile.generic_string()),
231+
util::readFileAsString(projectFile)
232+
);
233+
}
234+
235+
// Overwrite all files as opened by the client, including the ones which might potentially have changes.
184236
for (string const& fileName: m_openFiles)
185237
m_fileRepository.setSourceByUri(
186238
fileName,
@@ -269,6 +321,7 @@ bool LanguageServer::run()
269321
{
270322
string const methodName = (*jsonMessage)["method"].asString();
271323
id = (*jsonMessage)["id"];
324+
lspDebug(fmt::format("received method call: {}", methodName));
272325

273326
if (auto handler = util::valueOrDefault(m_handlers, methodName))
274327
handler(id, (*jsonMessage)["params"]);
@@ -278,6 +331,10 @@ bool LanguageServer::run()
278331
else
279332
m_client.error({}, ErrorCode::ParseError, "\"method\" has to be a string.");
280333
}
334+
catch (Json::Exception const&)
335+
{
336+
m_client.error(id, ErrorCode::InvalidParams, "JSON object access error. Most likely due to a badly formatted JSON request message."s);
337+
}
281338
catch (RequestError const& error)
282339
{
283340
m_client.error(id, error.code(), error.comment() ? *error.comment() : ""s);
@@ -347,6 +404,12 @@ void LanguageServer::handleInitialize(MessageID _id, Json::Value const& _args)
347404
m_client.reply(_id, move(replyArgs));
348405
}
349406

407+
void LanguageServer::handleInitialized(MessageID, Json::Value const&)
408+
{
409+
if (m_fileLoadStrategy == FileLoadStrategy::ProjectDirectory)
410+
compileAndUpdateDiagnostics();
411+
}
412+
350413
void LanguageServer::semanticTokensFull(MessageID _id, Json::Value const& _args)
351414
{
352415
auto uri = _args["textDocument"]["uri"];

libsolidity/lsp/LanguageServer.h

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,23 @@ namespace solidity::lsp
3636
class RenameSymbol;
3737
enum class ErrorCode;
3838

39+
/**
40+
* Enum to mandate what files to take into consideration for source code analysis.
41+
*/
42+
enum class FileLoadStrategy
43+
{
44+
/// Takes only those files into consideration that are explicitly opened and those
45+
/// that have been directly or indirectly imported.
46+
DirectlyOpenedAndOnImported = 0,
47+
48+
/// Takes all Solidity (.sol) files within the project root into account.
49+
/// Symbolic links will be followed, even if they lead outside of the project directory
50+
/// (`--allowed-paths` is currently ignored by the LSP).
51+
///
52+
/// This resembles the closest what other LSPs should be doing already.
53+
ProjectDirectory = 1,
54+
};
55+
3956
/**
4057
* Solidity Language Server, managing one LSP client.
4158
* This implements a subset of LSP version 3.16 that can be found at:
@@ -68,6 +85,7 @@ class LanguageServer
6885
/// Reports an error and returns false if not.
6986
void requireServerInitialized();
7087
void handleInitialize(MessageID _id, Json::Value const& _args);
88+
void handleInitialized(MessageID _id, Json::Value const& _args);
7189
void handleWorkspaceDidChangeConfiguration(Json::Value const& _args);
7290
void setTrace(Json::Value const& _args);
7391
void handleTextDocumentDidOpen(Json::Value const& _args);
@@ -82,6 +100,9 @@ class LanguageServer
82100

83101
/// Compile everything until after analysis phase.
84102
void compile();
103+
104+
std::vector<boost::filesystem::path> allSolidityFilesFromProject() const;
105+
85106
using MessageHandler = std::function<void(MessageID, Json::Value const&)>;
86107

87108
Json::Value toRange(langutil::SourceLocation const& _location);
@@ -100,6 +121,7 @@ class LanguageServer
100121
/// Set of source unit names for which we sent diagnostics to the client in the last iteration.
101122
std::set<std::string> m_nonemptyDiagnostics;
102123
FileRepository m_fileRepository;
124+
FileLoadStrategy m_fileLoadStrategy = FileLoadStrategy::ProjectDirectory;
103125

104126
frontend::CompilerStack m_compilerStack;
105127

libsolidity/lsp/Transport.cpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
*/
1717
// SPDX-License-Identifier: GPL-3.0
1818
#include <libsolidity/lsp/Transport.h>
19+
#include <libsolidity/lsp/Utils.h>
1920

2021
#include <libsolutil/JSON.h>
2122
#include <libsolutil/Visitor.h>
@@ -205,11 +206,13 @@ std::string StdioTransport::getline()
205206
{
206207
std::string line;
207208
std::getline(std::cin, line);
209+
lspDebug(fmt::format("Received: {}", line));
208210
return line;
209211
}
210212

211213
void StdioTransport::writeBytes(std::string_view _data)
212214
{
215+
lspDebug(fmt::format("Sending: {}", _data));
213216
auto const bytesWritten = fwrite(_data.data(), 1, _data.size(), stdout);
214217
solAssert(bytesWritten == _data.size());
215218
}

libsolutil/CommonIO.h

Lines changed: 0 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -48,46 +48,6 @@ inline std::ostream& operator<<(std::ostream& os, bytes const& _bytes)
4848
namespace util
4949
{
5050

51-
namespace detail
52-
{
53-
54-
template <typename Predicate>
55-
struct RecursiveFileCollector
56-
{
57-
Predicate predicate;
58-
std::vector<boost::filesystem::path> result {};
59-
60-
RecursiveFileCollector& operator()(boost::filesystem::path const& _directory)
61-
{
62-
if (!boost::filesystem::is_directory(_directory))
63-
return *this;
64-
auto iterator = boost::filesystem::directory_iterator(_directory);
65-
auto const iteratorEnd = boost::filesystem::directory_iterator();
66-
67-
while (iterator != iteratorEnd)
68-
{
69-
if (boost::filesystem::is_directory(iterator->status()))
70-
(*this)(iterator->path());
71-
72-
if (predicate(iterator->path()))
73-
result.push_back(iterator->path());
74-
75-
++iterator;
76-
}
77-
return *this;
78-
}
79-
};
80-
81-
template <typename Predicate>
82-
RecursiveFileCollector(Predicate) -> RecursiveFileCollector<Predicate>;
83-
}
84-
85-
template <typename Predicate>
86-
std::vector<boost::filesystem::path> findFilesRecursively(boost::filesystem::path const& _rootDirectory, Predicate _predicate)
87-
{
88-
return detail::RecursiveFileCollector{_predicate}(_rootDirectory).result;
89-
}
90-
9151
/// Retrieves and returns the contents of the given file as a std::string.
9252
/// If the file doesn't exist, it will throw a FileNotFound exception.
9353
/// If the file exists but is not a regular file, it will throw NotAFile exception.
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
// SPDX-License-Identifier: UNLICENSED
2+
pragma solidity >=0.8.0;
3+
4+
contract C
5+
{
6+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
// SPDX-License-Identifier: UNLICENSED
2+
pragma solidity >=0.8.0;
3+
4+
contract D
5+
{
6+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
// SPDX-License-Identifier: UNLICENSED
2+
pragma solidity >=0.8.0;
3+
4+
contract E
5+
{
6+
}

0 commit comments

Comments
 (0)