[clangd] Track document versions, include them with diags, enhance logs
Summary: This ties to an LSP feature (diagnostic versioning) but really a lot of the value is in being able to log what's happening with file versions and queues more descriptively and clearly. As such it's fairly invasive, for a logging patch :-\ Key decisions: - at the LSP layer, we don't reqire the client to provide versions (LSP makes it mandatory but we never enforced it). If not provided, versions start at 0 and increment. DraftStore handles this. - don't propagate magically using contexts, but rather manually: addDocument -> ParseInputs -> (ParsedAST, Preamble, various callbacks) Context-propagation would hide the versions from ClangdServer, which would make producing good log messages hard - within ClangdServer, treat versions as opaque and unordered. std::string is a convenient type for this, and allows richer versions for embedders. They're "mandatory" but "null" is a reasonable default. Subscribers: ilya-biryukov, javed.absar, MaskRay, jkorous, arphaman, kadircet, usaxena95, cfe-commits Tags: #clang Differential Revision: https://reviews.llvm.org/D75582
This commit is contained in:
@@ -41,6 +41,21 @@
|
||||
namespace clang {
|
||||
namespace clangd {
|
||||
namespace {
|
||||
|
||||
// LSP defines file versions as numbers that increase.
|
||||
// ClangdServer treats them as opaque and therefore uses strings instead.
|
||||
std::string encodeVersion(int64_t LSPVersion) {
|
||||
return llvm::to_string(LSPVersion);
|
||||
}
|
||||
llvm::Optional<int64_t> decodeVersion(llvm::StringRef Encoded) {
|
||||
int64_t Result;
|
||||
if (llvm::to_integer(Encoded, Result, 10))
|
||||
return Result;
|
||||
else if (!Encoded.empty()) // Empty can be e.g. diagnostics on close.
|
||||
elog("unexpected non-numeric version {0}", Encoded);
|
||||
return llvm::None;
|
||||
}
|
||||
|
||||
/// Transforms a tweak into a code action that would apply it if executed.
|
||||
/// EXPECTS: T.prepare() was called and returned true.
|
||||
CodeAction toCodeAction(const ClangdServer::TweakRef &T, const URIForFile &File,
|
||||
@@ -630,8 +645,9 @@ void ClangdLSPServer::onDocumentDidOpen(
|
||||
|
||||
const std::string &Contents = Params.textDocument.text;
|
||||
|
||||
DraftMgr.addDraft(File, Params.textDocument.version, Contents);
|
||||
Server->addDocument(File, Contents, WantDiagnostics::Yes);
|
||||
auto Version = DraftMgr.addDraft(File, Params.textDocument.version, Contents);
|
||||
Server->addDocument(File, Contents, encodeVersion(Version),
|
||||
WantDiagnostics::Yes);
|
||||
}
|
||||
|
||||
void ClangdLSPServer::onDocumentDidChange(
|
||||
@@ -654,7 +670,8 @@ void ClangdLSPServer::onDocumentDidChange(
|
||||
return;
|
||||
}
|
||||
|
||||
Server->addDocument(File, Draft->Contents, WantDiags, Params.forceRebuild);
|
||||
Server->addDocument(File, Draft->Contents, encodeVersion(Draft->Version),
|
||||
WantDiags, Params.forceRebuild);
|
||||
}
|
||||
|
||||
void ClangdLSPServer::onFileEvent(const DidChangeWatchedFilesParams &Params) {
|
||||
@@ -1347,7 +1364,8 @@ bool ClangdLSPServer::shouldRunCompletion(
|
||||
}
|
||||
|
||||
void ClangdLSPServer::onHighlightingsReady(
|
||||
PathRef File, std::vector<HighlightingToken> Highlightings) {
|
||||
PathRef File, llvm::StringRef Version,
|
||||
std::vector<HighlightingToken> Highlightings) {
|
||||
std::vector<HighlightingToken> Old;
|
||||
std::vector<HighlightingToken> HighlightingsCopy = Highlightings;
|
||||
{
|
||||
@@ -1358,14 +1376,18 @@ void ClangdLSPServer::onHighlightingsReady(
|
||||
// LSP allows us to send incremental edits of highlightings. Also need to diff
|
||||
// to remove highlightings from tokens that should no longer have them.
|
||||
std::vector<LineHighlightings> Diffed = diffHighlightings(Highlightings, Old);
|
||||
publishSemanticHighlighting(
|
||||
{{URIForFile::canonicalize(File, /*TUPath=*/File)},
|
||||
toSemanticHighlightingInformation(Diffed)});
|
||||
SemanticHighlightingParams Notification;
|
||||
Notification.TextDocument.uri =
|
||||
URIForFile::canonicalize(File, /*TUPath=*/File);
|
||||
Notification.TextDocument.version = decodeVersion(Version);
|
||||
Notification.Lines = toSemanticHighlightingInformation(Diffed);
|
||||
publishSemanticHighlighting(Notification);
|
||||
}
|
||||
|
||||
void ClangdLSPServer::onDiagnosticsReady(PathRef File,
|
||||
void ClangdLSPServer::onDiagnosticsReady(PathRef File, llvm::StringRef Version,
|
||||
std::vector<Diag> Diagnostics) {
|
||||
PublishDiagnosticsParams Notification;
|
||||
Notification.version = decodeVersion(Version);
|
||||
Notification.uri = URIForFile::canonicalize(File, /*TUPath=*/File);
|
||||
DiagnosticToReplacementMap LocalFixIts; // Temporary storage
|
||||
for (auto &Diag : Diagnostics) {
|
||||
@@ -1475,8 +1497,10 @@ void ClangdLSPServer::reparseOpenedFiles(
|
||||
// Reparse only opened files that were modified.
|
||||
for (const Path &FilePath : DraftMgr.getActiveFiles())
|
||||
if (ModifiedFiles.find(FilePath) != ModifiedFiles.end())
|
||||
Server->addDocument(FilePath, DraftMgr.getDraft(FilePath)->Contents,
|
||||
WantDiagnostics::Auto);
|
||||
if (auto Draft = DraftMgr.getDraft(FilePath)) // else disappeared in race?
|
||||
Server->addDocument(FilePath, std::move(Draft->Contents),
|
||||
encodeVersion(Draft->Version),
|
||||
WantDiagnostics::Auto);
|
||||
}
|
||||
|
||||
} // namespace clangd
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
#include "clang/Tooling/Core/Replacement.h"
|
||||
#include "llvm/ADT/Optional.h"
|
||||
#include "llvm/ADT/StringSet.h"
|
||||
#include "llvm/Support/JSON.h"
|
||||
#include <memory>
|
||||
|
||||
namespace clang {
|
||||
@@ -57,10 +58,11 @@ public:
|
||||
|
||||
private:
|
||||
// Implement ClangdServer::Callbacks.
|
||||
void onDiagnosticsReady(PathRef File, std::vector<Diag> Diagnostics) override;
|
||||
void onDiagnosticsReady(PathRef File, llvm::StringRef Version,
|
||||
std::vector<Diag> Diagnostics) override;
|
||||
void onFileUpdated(PathRef File, const TUStatus &Status) override;
|
||||
void
|
||||
onHighlightingsReady(PathRef File,
|
||||
onHighlightingsReady(PathRef File, llvm::StringRef Version,
|
||||
std::vector<HighlightingToken> Highlightings) override;
|
||||
void onBackgroundIndexProgress(const BackgroundQueue::Stats &Stats) override;
|
||||
|
||||
|
||||
@@ -62,11 +62,11 @@ struct UpdateIndexCallbacks : public ParsingCallbacks {
|
||||
: FIndex(FIndex), ServerCallbacks(ServerCallbacks),
|
||||
SemanticHighlighting(SemanticHighlighting) {}
|
||||
|
||||
void onPreambleAST(PathRef Path, ASTContext &Ctx,
|
||||
void onPreambleAST(PathRef Path, llvm::StringRef Version, ASTContext &Ctx,
|
||||
std::shared_ptr<clang::Preprocessor> PP,
|
||||
const CanonicalIncludes &CanonIncludes) override {
|
||||
if (FIndex)
|
||||
FIndex->updatePreamble(Path, Ctx, std::move(PP), CanonIncludes);
|
||||
FIndex->updatePreamble(Path, Version, Ctx, std::move(PP), CanonIncludes);
|
||||
}
|
||||
|
||||
void onMainAST(PathRef Path, ParsedAST &AST, PublishFn Publish) override {
|
||||
@@ -80,16 +80,19 @@ struct UpdateIndexCallbacks : public ParsingCallbacks {
|
||||
|
||||
if (ServerCallbacks)
|
||||
Publish([&]() {
|
||||
ServerCallbacks->onDiagnosticsReady(Path, std::move(Diagnostics));
|
||||
ServerCallbacks->onDiagnosticsReady(Path, AST.version(),
|
||||
std::move(Diagnostics));
|
||||
if (SemanticHighlighting)
|
||||
ServerCallbacks->onHighlightingsReady(Path, std::move(Highlightings));
|
||||
ServerCallbacks->onHighlightingsReady(Path, AST.version(),
|
||||
std::move(Highlightings));
|
||||
});
|
||||
}
|
||||
|
||||
void onFailedAST(PathRef Path, std::vector<Diag> Diags,
|
||||
PublishFn Publish) override {
|
||||
void onFailedAST(PathRef Path, llvm::StringRef Version,
|
||||
std::vector<Diag> Diags, PublishFn Publish) override {
|
||||
if (ServerCallbacks)
|
||||
Publish([&]() { ServerCallbacks->onDiagnosticsReady(Path, Diags); });
|
||||
Publish(
|
||||
[&]() { ServerCallbacks->onDiagnosticsReady(Path, Version, Diags); });
|
||||
}
|
||||
|
||||
void onFileUpdated(PathRef File, const TUStatus &Status) override {
|
||||
@@ -169,6 +172,7 @@ ClangdServer::ClangdServer(const GlobalCompilationDatabase &CDB,
|
||||
}
|
||||
|
||||
void ClangdServer::addDocument(PathRef File, llvm::StringRef Contents,
|
||||
llvm::StringRef Version,
|
||||
WantDiagnostics WantDiags, bool ForceRebuild) {
|
||||
auto FS = FSProvider.getFileSystem();
|
||||
|
||||
@@ -183,6 +187,7 @@ void ClangdServer::addDocument(PathRef File, llvm::StringRef Contents,
|
||||
ParseInputs Inputs;
|
||||
Inputs.FS = FS;
|
||||
Inputs.Contents = std::string(Contents);
|
||||
Inputs.Version = Version.str();
|
||||
Inputs.ForceRebuild = ForceRebuild;
|
||||
Inputs.Opts = std::move(Opts);
|
||||
Inputs.Index = Index;
|
||||
|
||||
@@ -69,7 +69,7 @@ public:
|
||||
|
||||
/// Called by ClangdServer when \p Diagnostics for \p File are ready.
|
||||
/// May be called concurrently for separate files, not for a single file.
|
||||
virtual void onDiagnosticsReady(PathRef File,
|
||||
virtual void onDiagnosticsReady(PathRef File, llvm::StringRef Version,
|
||||
std::vector<Diag> Diagnostics) {}
|
||||
/// Called whenever the file status is updated.
|
||||
/// May be called concurrently for separate files, not for a single file.
|
||||
@@ -78,7 +78,7 @@ public:
|
||||
/// Called by ClangdServer when some \p Highlightings for \p File are ready.
|
||||
/// May be called concurrently for separate files, not for a single file.
|
||||
virtual void
|
||||
onHighlightingsReady(PathRef File,
|
||||
onHighlightingsReady(PathRef File, llvm::StringRef Version,
|
||||
std::vector<HighlightingToken> Highlightings) {}
|
||||
|
||||
/// Called when background indexing tasks are enqueued/started/completed.
|
||||
@@ -171,13 +171,17 @@ public:
|
||||
/// \p File is already tracked. Also schedules parsing of the AST for it on a
|
||||
/// separate thread. When the parsing is complete, DiagConsumer passed in
|
||||
/// constructor will receive onDiagnosticsReady callback.
|
||||
/// Version identifies this snapshot and is propagated to ASTs, preambles,
|
||||
/// diagnostics etc built from it.
|
||||
void addDocument(PathRef File, StringRef Contents,
|
||||
llvm::StringRef Version = "null",
|
||||
WantDiagnostics WD = WantDiagnostics::Auto,
|
||||
bool ForceRebuild = false);
|
||||
|
||||
/// Remove \p File from list of tracked files, schedule a request to free
|
||||
/// resources associated with it. Pending diagnostics for closed files may not
|
||||
/// be delivered, even if requested with WantDiags::Auto or WantDiags::Yes.
|
||||
/// An empty set of diagnostics will be delivered, with Version = "".
|
||||
void removeDocument(PathRef File);
|
||||
|
||||
/// Run code completion for \p File at \p Pos.
|
||||
|
||||
@@ -45,6 +45,8 @@ struct ParseInputs {
|
||||
tooling::CompileCommand CompileCommand;
|
||||
IntrusiveRefCntPtr<llvm::vfs::FileSystem> FS;
|
||||
std::string Contents;
|
||||
// Version identifier for Contents, provided by the client and opaque to us.
|
||||
std::string Version = "null";
|
||||
// Prevent reuse of the cached preamble/AST. Slow! Useful to workaround
|
||||
// clangd's assumption that missing header files will stay missing.
|
||||
bool ForceRebuild = false;
|
||||
|
||||
@@ -239,7 +239,8 @@ void dumpAST(ParsedAST &AST, llvm::raw_ostream &OS) {
|
||||
}
|
||||
|
||||
llvm::Optional<ParsedAST>
|
||||
ParsedAST::build(std::unique_ptr<clang::CompilerInvocation> CI,
|
||||
ParsedAST::build(llvm::StringRef Version,
|
||||
std::unique_ptr<clang::CompilerInvocation> CI,
|
||||
llvm::ArrayRef<Diag> CompilerInvocationDiags,
|
||||
std::shared_ptr<const PreambleData> Preamble,
|
||||
std::unique_ptr<llvm::MemoryBuffer> Buffer,
|
||||
@@ -427,10 +428,10 @@ ParsedAST::build(std::unique_ptr<clang::CompilerInvocation> CI,
|
||||
std::vector<Diag> D = ASTDiags.take(CTContext.getPointer());
|
||||
Diags.insert(Diags.end(), D.begin(), D.end());
|
||||
}
|
||||
return ParsedAST(std::move(Preamble), std::move(Clang), std::move(Action),
|
||||
std::move(Tokens), std::move(Macros), std::move(ParsedDecls),
|
||||
std::move(Diags), std::move(Includes),
|
||||
std::move(CanonIncludes));
|
||||
return ParsedAST(Version, std::move(Preamble), std::move(Clang),
|
||||
std::move(Action), std::move(Tokens), std::move(Macros),
|
||||
std::move(ParsedDecls), std::move(Diags),
|
||||
std::move(Includes), std::move(CanonIncludes));
|
||||
}
|
||||
|
||||
ParsedAST::ParsedAST(ParsedAST &&Other) = default;
|
||||
@@ -512,14 +513,15 @@ const CanonicalIncludes &ParsedAST::getCanonicalIncludes() const {
|
||||
return CanonIncludes;
|
||||
}
|
||||
|
||||
ParsedAST::ParsedAST(std::shared_ptr<const PreambleData> Preamble,
|
||||
ParsedAST::ParsedAST(llvm::StringRef Version,
|
||||
std::shared_ptr<const PreambleData> Preamble,
|
||||
std::unique_ptr<CompilerInstance> Clang,
|
||||
std::unique_ptr<FrontendAction> Action,
|
||||
syntax::TokenBuffer Tokens, MainFileMacros Macros,
|
||||
std::vector<Decl *> LocalTopLevelDecls,
|
||||
std::vector<Diag> Diags, IncludeStructure Includes,
|
||||
CanonicalIncludes CanonIncludes)
|
||||
: Preamble(std::move(Preamble)), Clang(std::move(Clang)),
|
||||
: Version(Version), Preamble(std::move(Preamble)), Clang(std::move(Clang)),
|
||||
Action(std::move(Action)), Tokens(std::move(Tokens)),
|
||||
Macros(std::move(Macros)), Diags(std::move(Diags)),
|
||||
LocalTopLevelDecls(std::move(LocalTopLevelDecls)),
|
||||
@@ -546,7 +548,7 @@ buildAST(PathRef FileName, std::unique_ptr<CompilerInvocation> Invocation,
|
||||
}
|
||||
|
||||
return ParsedAST::build(
|
||||
std::make_unique<CompilerInvocation>(*Invocation),
|
||||
Inputs.Version, std::make_unique<CompilerInvocation>(*Invocation),
|
||||
CompilerInvocationDiags, Preamble,
|
||||
llvm::MemoryBuffer::getMemBufferCopy(Inputs.Contents, FileName),
|
||||
std::move(VFS), Inputs.Index, Inputs.Opts);
|
||||
|
||||
@@ -48,7 +48,7 @@ public:
|
||||
/// Attempts to run Clang and store parsed AST. If \p Preamble is non-null
|
||||
/// it is reused during parsing.
|
||||
static llvm::Optional<ParsedAST>
|
||||
build(std::unique_ptr<clang::CompilerInvocation> CI,
|
||||
build(llvm::StringRef Version, std::unique_ptr<clang::CompilerInvocation> CI,
|
||||
llvm::ArrayRef<Diag> CompilerInvocationDiags,
|
||||
std::shared_ptr<const PreambleData> Preamble,
|
||||
std::unique_ptr<llvm::MemoryBuffer> Buffer,
|
||||
@@ -101,14 +101,19 @@ public:
|
||||
/// (!) does not have tokens from the preamble.
|
||||
const syntax::TokenBuffer &getTokens() const { return Tokens; }
|
||||
|
||||
/// Returns the version of the ParseInputs this AST was built from.
|
||||
llvm::StringRef version() const { return Version; }
|
||||
|
||||
private:
|
||||
ParsedAST(std::shared_ptr<const PreambleData> Preamble,
|
||||
ParsedAST(llvm::StringRef Version,
|
||||
std::shared_ptr<const PreambleData> Preamble,
|
||||
std::unique_ptr<CompilerInstance> Clang,
|
||||
std::unique_ptr<FrontendAction> Action, syntax::TokenBuffer Tokens,
|
||||
MainFileMacros Macros, std::vector<Decl *> LocalTopLevelDecls,
|
||||
std::vector<Diag> Diags, IncludeStructure Includes,
|
||||
CanonicalIncludes CanonIncludes);
|
||||
|
||||
std::string Version;
|
||||
// In-memory preambles must outlive the AST, it is important that this member
|
||||
// goes before Clang and Action.
|
||||
std::shared_ptr<const PreambleData> Preamble;
|
||||
|
||||
@@ -75,12 +75,13 @@ private:
|
||||
|
||||
} // namespace
|
||||
|
||||
PreambleData::PreambleData(PrecompiledPreamble Preamble,
|
||||
PreambleData::PreambleData(llvm::StringRef Version,
|
||||
PrecompiledPreamble Preamble,
|
||||
std::vector<Diag> Diags, IncludeStructure Includes,
|
||||
MainFileMacros Macros,
|
||||
std::unique_ptr<PreambleFileStatusCache> StatCache,
|
||||
CanonicalIncludes CanonIncludes)
|
||||
: Preamble(std::move(Preamble)), Diags(std::move(Diags)),
|
||||
: Version(Version), Preamble(std::move(Preamble)), Diags(std::move(Diags)),
|
||||
Includes(std::move(Includes)), Macros(std::move(Macros)),
|
||||
StatCache(std::move(StatCache)), CanonIncludes(std::move(CanonIncludes)) {
|
||||
}
|
||||
@@ -102,12 +103,17 @@ buildPreamble(PathRef FileName, CompilerInvocation &CI,
|
||||
compileCommandsAreEqual(Inputs.CompileCommand, OldCompileCommand) &&
|
||||
OldPreamble->Preamble.CanReuse(CI, ContentsBuffer.get(), Bounds,
|
||||
Inputs.FS.get())) {
|
||||
vlog("Reusing preamble for {0}", FileName);
|
||||
vlog("Reusing preamble version {0} for version {1} of {2}",
|
||||
OldPreamble->Version, Inputs.Version, FileName);
|
||||
return OldPreamble;
|
||||
}
|
||||
vlog(OldPreamble ? "Rebuilding invalidated preamble for {0}"
|
||||
: "Building first preamble for {0}",
|
||||
FileName);
|
||||
if (OldPreamble)
|
||||
vlog("Rebuilding invalidated preamble for {0} version {1} "
|
||||
"(previous was version {2})",
|
||||
FileName, Inputs.Version, OldPreamble->Version);
|
||||
else
|
||||
vlog("Building first preamble for {0} verson {1}", FileName,
|
||||
Inputs.Version);
|
||||
|
||||
trace::Span Tracer("BuildPreamble");
|
||||
SPAN_ATTACH(Tracer, "File", FileName);
|
||||
@@ -145,16 +151,17 @@ buildPreamble(PathRef FileName, CompilerInvocation &CI,
|
||||
CI.getFrontendOpts().SkipFunctionBodies = false;
|
||||
|
||||
if (BuiltPreamble) {
|
||||
vlog("Built preamble of size {0} for file {1}", BuiltPreamble->getSize(),
|
||||
FileName);
|
||||
vlog("Built preamble of size {0} for file {1} version {2}",
|
||||
BuiltPreamble->getSize(), FileName, Inputs.Version);
|
||||
std::vector<Diag> Diags = PreambleDiagnostics.take();
|
||||
return std::make_shared<PreambleData>(
|
||||
std::move(*BuiltPreamble), std::move(Diags),
|
||||
Inputs.Version, std::move(*BuiltPreamble), std::move(Diags),
|
||||
SerializedDeclsCollector.takeIncludes(),
|
||||
SerializedDeclsCollector.takeMacros(), std::move(StatCache),
|
||||
SerializedDeclsCollector.takeCanonicalIncludes());
|
||||
} else {
|
||||
elog("Could not build a preamble for file {0}", FileName);
|
||||
elog("Could not build a preamble for file {0} version {2}", FileName,
|
||||
Inputs.Version);
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -43,11 +43,14 @@ namespace clangd {
|
||||
/// As we must avoid re-parsing the preamble, any information that can only
|
||||
/// be obtained during parsing must be eagerly captured and stored here.
|
||||
struct PreambleData {
|
||||
PreambleData(PrecompiledPreamble Preamble, std::vector<Diag> Diags,
|
||||
IncludeStructure Includes, MainFileMacros Macros,
|
||||
PreambleData(llvm::StringRef Version, PrecompiledPreamble Preamble,
|
||||
std::vector<Diag> Diags, IncludeStructure Includes,
|
||||
MainFileMacros Macros,
|
||||
std::unique_ptr<PreambleFileStatusCache> StatCache,
|
||||
CanonicalIncludes CanonIncludes);
|
||||
|
||||
// Version of the ParseInputs this preamble was built from.
|
||||
std::string Version;
|
||||
tooling::CompileCommand CompileCommand;
|
||||
PrecompiledPreamble Preamble;
|
||||
std::vector<Diag> Diags;
|
||||
|
||||
@@ -92,8 +92,7 @@ bool fromJSON(const llvm::json::Value &Params, TextDocumentIdentifier &R) {
|
||||
|
||||
llvm::json::Value toJSON(const VersionedTextDocumentIdentifier &R) {
|
||||
auto Result = toJSON(static_cast<const TextDocumentIdentifier &>(R));
|
||||
if (R.version)
|
||||
Result.getAsObject()->try_emplace("version", R.version);
|
||||
Result.getAsObject()->try_emplace("version", R.version);
|
||||
return Result;
|
||||
}
|
||||
|
||||
@@ -547,8 +546,9 @@ bool fromJSON(const llvm::json::Value &Params, Diagnostic &R) {
|
||||
|
||||
llvm::json::Value toJSON(const PublishDiagnosticsParams &PDP) {
|
||||
return llvm::json::Object{
|
||||
{"uri", PDP.uri},
|
||||
{"diagnostics", PDP.diagnostics},
|
||||
{"uri", PDP.uri},
|
||||
{"diagnostics", PDP.diagnostics},
|
||||
{"version", PDP.version},
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -125,14 +125,16 @@ llvm::json::Value toJSON(const TextDocumentIdentifier &);
|
||||
bool fromJSON(const llvm::json::Value &, TextDocumentIdentifier &);
|
||||
|
||||
struct VersionedTextDocumentIdentifier : public TextDocumentIdentifier {
|
||||
// The version number of this document. If a versioned text document
|
||||
// identifier is sent from the server to the client and the file is not open
|
||||
// in the editor (the server has not received an open notification before) the
|
||||
// server can send `null` to indicate that the version is known and the
|
||||
// content on disk is the master (as speced with document content ownership).
|
||||
//
|
||||
// The version number of a document will increase after each change, including
|
||||
// undo/redo. The number doesn't need to be consecutive.
|
||||
/// The version number of this document. If a versioned text document
|
||||
/// identifier is sent from the server to the client and the file is not open
|
||||
/// in the editor (the server has not received an open notification before)
|
||||
/// the server can send `null` to indicate that the version is known and the
|
||||
/// content on disk is the master (as speced with document content ownership).
|
||||
///
|
||||
/// The version number of a document will increase after each change,
|
||||
/// including undo/redo. The number doesn't need to be consecutive.
|
||||
///
|
||||
/// clangd extension: versions are optional, and synthesized if missing.
|
||||
llvm::Optional<std::int64_t> version;
|
||||
};
|
||||
llvm::json::Value toJSON(const VersionedTextDocumentIdentifier &);
|
||||
@@ -237,7 +239,10 @@ struct TextDocumentItem {
|
||||
std::string languageId;
|
||||
|
||||
/// The version number of this document (it will strictly increase after each
|
||||
std::int64_t version = 0;
|
||||
/// change, including undo/redo.
|
||||
///
|
||||
/// clangd extension: versions are optional, and synthesized if missing.
|
||||
llvm::Optional<int64_t> version;
|
||||
|
||||
/// The content of the opened text document.
|
||||
std::string text;
|
||||
@@ -811,6 +816,8 @@ struct PublishDiagnosticsParams {
|
||||
URIForFile uri;
|
||||
/// An array of diagnostic information items.
|
||||
std::vector<Diagnostic> diagnostics;
|
||||
/// The version number of the document the diagnostics are published for.
|
||||
llvm::Optional<int64_t> version;
|
||||
};
|
||||
llvm::json::Value toJSON(const PublishDiagnosticsParams &);
|
||||
|
||||
@@ -1353,7 +1360,7 @@ llvm::json::Value toJSON(const SemanticHighlightingInformation &Highlighting);
|
||||
/// Parameters for the semantic highlighting (server-side) push notification.
|
||||
struct SemanticHighlightingParams {
|
||||
/// The textdocument these highlightings belong to.
|
||||
TextDocumentIdentifier TextDocument;
|
||||
VersionedTextDocumentIdentifier TextDocument;
|
||||
/// The lines of highlightings that should be sent.
|
||||
std::vector<SemanticHighlightingInformation> Lines;
|
||||
};
|
||||
|
||||
@@ -375,7 +375,7 @@ ASTWorker::~ASTWorker() {
|
||||
}
|
||||
|
||||
void ASTWorker::update(ParseInputs Inputs, WantDiagnostics WantDiags) {
|
||||
llvm::StringRef TaskName = "Update";
|
||||
std::string TaskName = llvm::formatv("Update ({0})", Inputs.Version);
|
||||
auto Task = [=]() mutable {
|
||||
auto RunPublish = [&](llvm::function_ref<void()> Publish) {
|
||||
// Ensure we only publish results from the worker if the file was not
|
||||
@@ -409,8 +409,8 @@ void ASTWorker::update(ParseInputs Inputs, WantDiagnostics WantDiags) {
|
||||
}
|
||||
RanASTCallback = false;
|
||||
emitTUStatus({TUAction::BuildingPreamble, TaskName});
|
||||
log("Updating file {0} with command {1}\n[{2}]\n{3}", FileName,
|
||||
Inputs.CompileCommand.Heuristic,
|
||||
log("ASTWorker building file {0} version {1} with command {2}\n[{3}]\n{4}",
|
||||
FileName, Inputs.Version, Inputs.CompileCommand.Heuristic,
|
||||
Inputs.CompileCommand.Directory,
|
||||
llvm::join(Inputs.CompileCommand.CommandLine, " "));
|
||||
// Rebuild the preamble and the AST.
|
||||
@@ -431,8 +431,8 @@ void ASTWorker::update(ParseInputs Inputs, WantDiagnostics WantDiags) {
|
||||
Details.BuildFailed = true;
|
||||
emitTUStatus({TUAction::BuildingPreamble, TaskName}, &Details);
|
||||
// Report the diagnostics we collected when parsing the command line.
|
||||
Callbacks.onFailedAST(FileName, std::move(CompilerInvocationDiags),
|
||||
RunPublish);
|
||||
Callbacks.onFailedAST(FileName, Inputs.Version,
|
||||
std::move(CompilerInvocationDiags), RunPublish);
|
||||
// Make sure anyone waiting for the preamble gets notified it could not
|
||||
// be built.
|
||||
PreambleWasBuilt.notify();
|
||||
@@ -445,9 +445,11 @@ void ASTWorker::update(ParseInputs Inputs, WantDiagnostics WantDiags) {
|
||||
std::shared_ptr<const PreambleData> NewPreamble = buildPreamble(
|
||||
FileName, *Invocation, OldPreamble, OldCommand, Inputs,
|
||||
StorePreambleInMemory,
|
||||
[this](ASTContext &Ctx, std::shared_ptr<clang::Preprocessor> PP,
|
||||
const CanonicalIncludes &CanonIncludes) {
|
||||
Callbacks.onPreambleAST(FileName, Ctx, std::move(PP), CanonIncludes);
|
||||
[this, Version(Inputs.Version)](
|
||||
ASTContext &Ctx, std::shared_ptr<clang::Preprocessor> PP,
|
||||
const CanonicalIncludes &CanonIncludes) {
|
||||
Callbacks.onPreambleAST(FileName, Version, Ctx, std::move(PP),
|
||||
CanonIncludes);
|
||||
});
|
||||
|
||||
bool CanReuseAST = InputsAreTheSame && (OldPreamble == NewPreamble);
|
||||
@@ -541,7 +543,8 @@ void ASTWorker::update(ParseInputs Inputs, WantDiagnostics WantDiags) {
|
||||
// line if there were any.
|
||||
// FIXME: we might have got more errors while trying to build the AST,
|
||||
// surface them too.
|
||||
Callbacks.onFailedAST(FileName, CompilerInvocationDiags, RunPublish);
|
||||
Callbacks.onFailedAST(FileName, Inputs.Version, CompilerInvocationDiags,
|
||||
RunPublish);
|
||||
}
|
||||
// Stash the AST in the cache for further use.
|
||||
IdleASTs.put(this, std::move(*AST));
|
||||
@@ -563,6 +566,8 @@ void ASTWorker::runWithAST(
|
||||
std::unique_ptr<CompilerInvocation> Invocation = buildCompilerInvocation(
|
||||
*CurrentInputs, CompilerInvocationDiagConsumer);
|
||||
// Try rebuilding the AST.
|
||||
vlog("ASTWorker rebuilding evicted AST to run {0}: {1} version {2}", Name,
|
||||
FileName, CurrentInputs->Version);
|
||||
llvm::Optional<ParsedAST> NewAST =
|
||||
Invocation
|
||||
? buildAST(FileName,
|
||||
@@ -579,6 +584,8 @@ void ASTWorker::runWithAST(
|
||||
if (!*AST)
|
||||
return Action(llvm::make_error<llvm::StringError>(
|
||||
"invalid AST", llvm::errc::invalid_argument));
|
||||
vlog("ASTWorker running {0} on version {2} of {1}", Name, FileName,
|
||||
CurrentInputs->Version);
|
||||
Action(InputsAndAST{*CurrentInputs, **AST});
|
||||
};
|
||||
startTask(Name, std::move(Task), /*UpdateType=*/None, Invalidation);
|
||||
@@ -788,8 +795,10 @@ Deadline ASTWorker::scheduleLocked() {
|
||||
I->UpdateType = WantDiagnostics::Auto;
|
||||
}
|
||||
|
||||
while (shouldSkipHeadLocked())
|
||||
while (shouldSkipHeadLocked()) {
|
||||
vlog("ASTWorker skipping {0} for {1}", Requests.front().Name, FileName);
|
||||
Requests.pop_front();
|
||||
}
|
||||
assert(!Requests.empty() && "skipped the whole queue");
|
||||
// Some updates aren't dead yet, but never end up being used.
|
||||
// e.g. the first keystroke is live until obsoleted by the second.
|
||||
|
||||
@@ -122,7 +122,8 @@ public:
|
||||
/// Called on the AST that was built for emitting the preamble. The built AST
|
||||
/// contains only AST nodes from the #include directives at the start of the
|
||||
/// file. AST node in the current file should be observed on onMainAST call.
|
||||
virtual void onPreambleAST(PathRef Path, ASTContext &Ctx,
|
||||
virtual void onPreambleAST(PathRef Path, llvm::StringRef Version,
|
||||
ASTContext &Ctx,
|
||||
std::shared_ptr<clang::Preprocessor> PP,
|
||||
const CanonicalIncludes &) {}
|
||||
|
||||
@@ -153,8 +154,8 @@ public:
|
||||
|
||||
/// Called whenever the AST fails to build. \p Diags will have the diagnostics
|
||||
/// that led to failure.
|
||||
virtual void onFailedAST(PathRef Path, std::vector<Diag> Diags,
|
||||
PublishFn Publish) {}
|
||||
virtual void onFailedAST(PathRef Path, llvm::StringRef Version,
|
||||
std::vector<Diag> Diags, PublishFn Publish) {}
|
||||
|
||||
/// Called whenever the TU status is updated.
|
||||
virtual void onFileUpdated(PathRef File, const TUStatus &Status) {}
|
||||
|
||||
@@ -35,7 +35,7 @@ static SlabTuple indexSymbols(ASTContext &AST, std::shared_ptr<Preprocessor> PP,
|
||||
llvm::ArrayRef<Decl *> DeclsToIndex,
|
||||
const MainFileMacros *MacroRefsToIndex,
|
||||
const CanonicalIncludes &Includes,
|
||||
bool IsIndexMainAST) {
|
||||
bool IsIndexMainAST, llvm::StringRef Version) {
|
||||
SymbolCollector::Options CollectorOpts;
|
||||
CollectorOpts.CollectIncludePath = true;
|
||||
CollectorOpts.Includes = &Includes;
|
||||
@@ -74,12 +74,13 @@ static SlabTuple indexSymbols(ASTContext &AST, std::shared_ptr<Preprocessor> PP,
|
||||
auto Refs = Collector.takeRefs();
|
||||
auto Relations = Collector.takeRelations();
|
||||
|
||||
vlog("index AST for {0} (main={1}): \n"
|
||||
" symbol slab: {2} symbols, {3} bytes\n"
|
||||
" ref slab: {4} symbols, {5} refs, {6} bytes\n"
|
||||
" relations slab: {7} relations, {8} bytes",
|
||||
FileName, IsIndexMainAST, Syms.size(), Syms.bytes(), Refs.size(),
|
||||
Refs.numRefs(), Refs.bytes(), Relations.size(), Relations.bytes());
|
||||
vlog("indexed {0} AST for {1} version {2}:\n"
|
||||
" symbol slab: {3} symbols, {4} bytes\n"
|
||||
" ref slab: {5} symbols, {6} refs, {7} bytes\n"
|
||||
" relations slab: {8} relations, {9} bytes",
|
||||
IsIndexMainAST ? "file" : "preamble", FileName, Version, Syms.size(),
|
||||
Syms.bytes(), Refs.size(), Refs.numRefs(), Refs.bytes(),
|
||||
Relations.size(), Relations.bytes());
|
||||
return std::make_tuple(std::move(Syms), std::move(Refs),
|
||||
std::move(Relations));
|
||||
}
|
||||
@@ -88,17 +89,18 @@ SlabTuple indexMainDecls(ParsedAST &AST) {
|
||||
return indexSymbols(AST.getASTContext(), AST.getPreprocessorPtr(),
|
||||
AST.getLocalTopLevelDecls(), &AST.getMacros(),
|
||||
AST.getCanonicalIncludes(),
|
||||
/*IsIndexMainAST=*/true);
|
||||
/*IsIndexMainAST=*/true, AST.version());
|
||||
}
|
||||
|
||||
SlabTuple indexHeaderSymbols(ASTContext &AST, std::shared_ptr<Preprocessor> PP,
|
||||
SlabTuple indexHeaderSymbols(llvm::StringRef Version, ASTContext &AST,
|
||||
std::shared_ptr<Preprocessor> PP,
|
||||
const CanonicalIncludes &Includes) {
|
||||
std::vector<Decl *> DeclsToIndex(
|
||||
AST.getTranslationUnitDecl()->decls().begin(),
|
||||
AST.getTranslationUnitDecl()->decls().end());
|
||||
return indexSymbols(AST, std::move(PP), DeclsToIndex,
|
||||
/*MainFileMacros=*/nullptr, Includes,
|
||||
/*IsIndexMainAST=*/false);
|
||||
/*IsIndexMainAST=*/false, Version);
|
||||
}
|
||||
|
||||
void FileSymbols::update(PathRef Path, std::unique_ptr<SymbolSlab> Symbols,
|
||||
@@ -248,10 +250,11 @@ FileIndex::FileIndex(bool UseDex)
|
||||
PreambleIndex(std::make_unique<MemIndex>()),
|
||||
MainFileIndex(std::make_unique<MemIndex>()) {}
|
||||
|
||||
void FileIndex::updatePreamble(PathRef Path, ASTContext &AST,
|
||||
void FileIndex::updatePreamble(PathRef Path, llvm::StringRef Version,
|
||||
ASTContext &AST,
|
||||
std::shared_ptr<Preprocessor> PP,
|
||||
const CanonicalIncludes &Includes) {
|
||||
auto Slabs = indexHeaderSymbols(AST, std::move(PP), Includes);
|
||||
auto Slabs = indexHeaderSymbols(Version, AST, std::move(PP), Includes);
|
||||
PreambleSymbols.update(
|
||||
Path, std::make_unique<SymbolSlab>(std::move(std::get<0>(Slabs))),
|
||||
std::make_unique<RefSlab>(),
|
||||
|
||||
@@ -98,7 +98,7 @@ public:
|
||||
|
||||
/// Update preamble symbols of file \p Path with all declarations in \p AST
|
||||
/// and macros in \p PP.
|
||||
void updatePreamble(PathRef Path, ASTContext &AST,
|
||||
void updatePreamble(PathRef Path, llvm::StringRef Version, ASTContext &AST,
|
||||
std::shared_ptr<Preprocessor> PP,
|
||||
const CanonicalIncludes &Includes);
|
||||
|
||||
@@ -142,7 +142,8 @@ SlabTuple indexMainDecls(ParsedAST &AST);
|
||||
|
||||
/// Index declarations from \p AST and macros from \p PP that are declared in
|
||||
/// included headers.
|
||||
SlabTuple indexHeaderSymbols(ASTContext &AST, std::shared_ptr<Preprocessor> PP,
|
||||
SlabTuple indexHeaderSymbols(llvm::StringRef Version, ASTContext &AST,
|
||||
std::shared_ptr<Preprocessor> PP,
|
||||
const CanonicalIncludes &Includes);
|
||||
|
||||
} // namespace clangd
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# RUN: clangd -lit-test < %s | FileCheck -strict-whitespace %s
|
||||
{"jsonrpc":"2.0","id":0,"method":"initialize","params":{"processId":123,"rootPath":"clangd","capabilities":{"textDocument":{"publishDiagnostics":{"categorySupport":true}}},"trace":"off"}}
|
||||
---
|
||||
{"jsonrpc":"2.0","method":"textDocument/didOpen","params":{"textDocument":{"uri":"test:///foo.c","languageId":"c","version":1,"text":"struct Point {}; union Point p;"}}}
|
||||
{"jsonrpc":"2.0","method":"textDocument/didOpen","params":{"textDocument":{"uri":"test:///foo.c","languageId":"c","text":"struct Point {}; union Point p;"}}}
|
||||
# CHECK: "method": "textDocument/publishDiagnostics",
|
||||
# CHECK-NEXT: "params": {
|
||||
# CHECK-NEXT: "diagnostics": [
|
||||
@@ -37,7 +37,8 @@
|
||||
# CHECK-NEXT: "severity": 3
|
||||
# CHECK-NEXT: }
|
||||
# CHECK-NEXT: ],
|
||||
# CHECK-NEXT: "uri": "file://{{.*}}/foo.c"
|
||||
# CHECK-NEXT: "uri": "file://{{.*}}/foo.c",
|
||||
# CHECK-NEXT: "version": 0
|
||||
# CHECK-NEXT: }
|
||||
---
|
||||
{"jsonrpc":"2.0","id":4,"method":"shutdown"}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# RUN: clangd -lit-test -clang-tidy=false < %s | FileCheck -strict-whitespace %s
|
||||
{"jsonrpc":"2.0","id":0,"method":"initialize","params":{"processId":123,"rootPath":"clangd","capabilities":{},"trace":"off"}}
|
||||
---
|
||||
{"jsonrpc":"2.0","method":"textDocument/didOpen","params":{"textDocument":{"uri":"test:///foo.c","languageId":"c","version":1,"text":"void main() {\n(void)sizeof(42);\n}"}}}
|
||||
{"jsonrpc":"2.0","method":"textDocument/didOpen","params":{"textDocument":{"uri":"test:///foo.c","languageId":"c","text":"void main() {\n(void)sizeof(42);\n}"}}}
|
||||
# CHECK: "method": "textDocument/publishDiagnostics",
|
||||
# CHECK-NEXT: "params": {
|
||||
# CHECK-NEXT: "diagnostics": [
|
||||
@@ -22,7 +22,8 @@
|
||||
# CHECK-NEXT: "source": "clang"
|
||||
# CHECK-NEXT: }
|
||||
# CHECK-NEXT: ],
|
||||
# CHECK-NEXT: "uri": "file://{{.*}}/foo.c"
|
||||
# CHECK-NEXT: "uri": "file://{{.*}}/foo.c",
|
||||
# CHECK-NEXT: "version": 0
|
||||
# CHECK-NEXT: }
|
||||
---
|
||||
{"jsonrpc":"2.0","id":2,"method":"sync","params":null}
|
||||
@@ -31,7 +32,8 @@
|
||||
# CHECK: "method": "textDocument/publishDiagnostics",
|
||||
# CHECK-NEXT: "params": {
|
||||
# CHECK-NEXT: "diagnostics": [],
|
||||
# CHECK-NEXT: "uri": "file://{{.*}}/foo.c"
|
||||
# CHECK-NEXT: "uri": "file://{{.*}}/foo.c",
|
||||
# CHECK-NEXT: "version": null
|
||||
# CHECK-NEXT: }
|
||||
---
|
||||
{"jsonrpc":"2.0","id":5,"method":"shutdown"}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# RUN: clangd -lit-test < %s | FileCheck -strict-whitespace %s
|
||||
{"jsonrpc":"2.0","id":0,"method":"initialize","params":{"processId":123,"rootPath":"clangd","capabilities":{"textDocument":{"publishDiagnostics":{"relatedInformation":true}}},"trace":"off"}}
|
||||
---
|
||||
{"jsonrpc":"2.0","method":"textDocument/didOpen","params":{"textDocument":{"uri":"test:///foo.cc","languageId":"cpp","version":1,"text":"int x;\nint x;"}}}
|
||||
{"jsonrpc":"2.0","method":"textDocument/didOpen","params":{"textDocument":{"uri":"test:///foo.cc","languageId":"cpp","text":"int x;\nint x;"}}}
|
||||
# CHECK: "method": "textDocument/publishDiagnostics",
|
||||
# CHECK-NEXT: "params": {
|
||||
# CHECK-NEXT: "diagnostics": [
|
||||
@@ -40,7 +40,8 @@
|
||||
# CHECK-NEXT: "source": "clang"
|
||||
# CHECK-NEXT: }
|
||||
# CHECK-NEXT: ],
|
||||
# CHECK-NEXT: "uri": "file://{{.*}}/foo.cc"
|
||||
# CHECK-NEXT: "uri": "file://{{.*}}/foo.cc",
|
||||
# CHECK-NEXT: "version": 0
|
||||
# CHECK-NEXT: }
|
||||
---
|
||||
{"jsonrpc":"2.0","id":5,"method":"shutdown"}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# RUN: clangd -lit-test -clang-tidy-checks=bugprone-sizeof-expression < %s | FileCheck -strict-whitespace %s
|
||||
{"jsonrpc":"2.0","id":0,"method":"initialize","params":{"processId":123,"rootPath":"clangd","capabilities":{},"trace":"off"}}
|
||||
---
|
||||
{"jsonrpc":"2.0","method":"textDocument/didOpen","params":{"textDocument":{"uri":"test:///foo.c","languageId":"c","version":1,"text":"void main() {\n(void)sizeof(42);\n}"}}}
|
||||
{"jsonrpc":"2.0","method":"textDocument/didOpen","params":{"textDocument":{"uri":"test:///foo.c","languageId":"c","text":"void main() {\n(void)sizeof(42);\n}"}}}
|
||||
# CHECK: "method": "textDocument/publishDiagnostics",
|
||||
# CHECK-NEXT: "params": {
|
||||
# CHECK-NEXT: "diagnostics": [
|
||||
@@ -38,7 +38,8 @@
|
||||
# CHECK-NEXT: "source": "clang-tidy"
|
||||
# CHECK-NEXT: }
|
||||
# CHECK-NEXT: ],
|
||||
# CHECK-NEXT: "uri": "file://{{.*}}/foo.c"
|
||||
# CHECK-NEXT: "uri": "file://{{.*}}/foo.c",
|
||||
# CHECK-NEXT: "version": 0
|
||||
# CHECK-NEXT: }
|
||||
---
|
||||
{"jsonrpc":"2.0","id":2,"method":"sync","params":null}
|
||||
@@ -47,7 +48,8 @@
|
||||
# CHECK: "method": "textDocument/publishDiagnostics",
|
||||
# CHECK-NEXT: "params": {
|
||||
# CHECK-NEXT: "diagnostics": [],
|
||||
# CHECK-NEXT: "uri": "file://{{.*}}/foo.c"
|
||||
# CHECK-NEXT: "uri": "file://{{.*}}/foo.c",
|
||||
# CHECK-NEXT: "version": null
|
||||
# CHECK-NEXT: }
|
||||
---
|
||||
{"jsonrpc":"2.0","id":5,"method":"shutdown"}
|
||||
|
||||
@@ -5,18 +5,20 @@
|
||||
---
|
||||
{"jsonrpc":"2.0","method":"workspace/didChangeConfiguration","params":{"settings":{"compilationDatabaseChanges":{"/clangd-test/foo.c": {"workingDirectory":"/clangd-test", "compilationCommand": ["clang", "-c", "foo.c"]}}}}}
|
||||
---
|
||||
{"jsonrpc":"2.0","method":"textDocument/didOpen","params":{"textDocument":{"uri":"test:///foo.c","languageId":"c","version":1,"text":"int main() { int i; return i; }"}}}
|
||||
{"jsonrpc":"2.0","method":"textDocument/didOpen","params":{"textDocument":{"uri":"test:///foo.c","languageId":"c","text":"int main() { int i; return i; }"}}}
|
||||
# CHECK: "method": "textDocument/publishDiagnostics",
|
||||
# CHECK-NEXT: "params": {
|
||||
# CHECK-NEXT: "diagnostics": [],
|
||||
# CHECK-NEXT: "uri": "file://{{.*}}/foo.c"
|
||||
# CHECK-NEXT: "uri": "file://{{.*}}/foo.c",
|
||||
# CHECK-NEXT: "version": 0
|
||||
# CHECK-NEXT: }
|
||||
---
|
||||
{"jsonrpc":"2.0","method":"textDocument/didOpen","params":{"textDocument":{"uri":"test:///bar.c","languageId":"c","version":1,"text":"int main() { int i; return i; }"}}}
|
||||
{"jsonrpc":"2.0","method":"textDocument/didOpen","params":{"textDocument":{"uri":"test:///bar.c","languageId":"c","text":"int main() { int i; return i; }"}}}
|
||||
# CHECK: "method": "textDocument/publishDiagnostics",
|
||||
# CHECK-NEXT: "params": {
|
||||
# CHECK-NEXT: "diagnostics": [],
|
||||
# CHECK-NEXT: "uri": "file://{{.*}}/bar.c"
|
||||
# CHECK-NEXT: "uri": "file://{{.*}}/bar.c",
|
||||
# CHECK-NEXT: "version": 0
|
||||
# CHECK-NEXT: }
|
||||
---
|
||||
{"jsonrpc":"2.0","method":"workspace/didChangeConfiguration","params":{"settings":{"compilationDatabaseChanges":{"/clangd-test/foo.c": {"workingDirectory":"/clangd-test2", "compilationCommand": ["clang", "-c", "foo.c", "-Wall", "-Werror"]}}}}}
|
||||
@@ -40,10 +42,11 @@
|
||||
# CHECK-NEXT: "source": "clang"
|
||||
# CHECK-NEXT: }
|
||||
# CHECK-NEXT: ],
|
||||
# CHECK-NEXT: "uri": "file://{{.*}}/foo.c"
|
||||
# CHECK-NEXT: "uri": "file://{{.*}}/foo.c",
|
||||
# CHECK-NEXT: "version": 0
|
||||
# CHECK-NEXT: }
|
||||
#
|
||||
# ERR: Updating file {{.*}}foo.c with command
|
||||
# ERR: ASTWorker building file {{.*}}foo.c version 0 with command
|
||||
# ERR: [{{.*}}clangd-test2]
|
||||
# ERR: clang -c foo.c -Wall -Werror
|
||||
---
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# RUN: clangd -lit-test < %s | FileCheck -strict-whitespace %s
|
||||
{"jsonrpc":"2.0","id":0,"method":"initialize","params":{"processId":123,"rootPath":"clangd","capabilities":{},"trace":"off"}}
|
||||
---
|
||||
{"jsonrpc":"2.0","method":"textDocument/didOpen","params":{"textDocument":{"uri":"test:///foo.c","languageId":"c","version":1,"text":"int main(int i, char **a) { if (i = 2) {}}"}}}
|
||||
{"jsonrpc":"2.0","method":"textDocument/didOpen","params":{"textDocument":{"uri":"test:///foo.c","languageId":"c","text":"int main(int i, char **a) { if (i = 2) {}}"}}}
|
||||
# CHECK: "method": "textDocument/publishDiagnostics",
|
||||
# CHECK-NEXT: "params": {
|
||||
# CHECK-NEXT: "diagnostics": [
|
||||
@@ -22,7 +22,8 @@
|
||||
# CHECK-NEXT: "source": "clang"
|
||||
# CHECK-NEXT: }
|
||||
# CHECK-NEXT: ],
|
||||
# CHECK-NEXT: "uri": "file://{{.*}}/foo.c"
|
||||
# CHECK-NEXT: "uri": "file://{{.*}}/foo.c",
|
||||
# CHECK-NEXT: "version": 0
|
||||
# CHECK-NEXT: }
|
||||
---
|
||||
# No command name
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# RUN: clangd -lit-test < %s | FileCheck -strict-whitespace %s
|
||||
{"jsonrpc":"2.0","id":0,"method":"initialize","params":{"processId":123,"rootPath":"clangd","capabilities":{"textDocument":{"codeAction":{"codeActionLiteralSupport":{}}}},"trace":"off"}}
|
||||
---
|
||||
{"jsonrpc":"2.0","method":"textDocument/didOpen","params":{"textDocument":{"uri":"test:///foo.c","languageId":"c","version":1,"text":"int main(int i, char **a) { if (i = 2) {}}"}}}
|
||||
{"jsonrpc":"2.0","method":"textDocument/didOpen","params":{"textDocument":{"uri":"test:///foo.c","languageId":"c","text":"int main(int i, char **a) { if (i = 2) {}}"}}}
|
||||
# CHECK: "method": "textDocument/publishDiagnostics",
|
||||
# CHECK-NEXT: "params": {
|
||||
# CHECK-NEXT: "diagnostics": [
|
||||
@@ -22,7 +22,8 @@
|
||||
# CHECK-NEXT: "source": "clang"
|
||||
# CHECK-NEXT: }
|
||||
# CHECK-NEXT: ],
|
||||
# CHECK-NEXT: "uri": "file://{{.*}}/foo.c"
|
||||
# CHECK-NEXT: "uri": "file://{{.*}}/foo.c",
|
||||
# CHECK-NEXT: "version": 0
|
||||
# CHECK-NEXT: }
|
||||
---
|
||||
{"jsonrpc":"2.0","id":2,"method":"textDocument/codeAction","params":{"textDocument":{"uri":"test:///foo.c"},"range":{"start":{"line":0,"character":13},"end":{"line":0,"character":35}},"context":{"diagnostics":[{"range":{"start": {"line": 0, "character": 32}, "end": {"line": 0, "character": 37}},"severity":2,"message":"Using the result of an assignment as a condition without parentheses (fixes available)", "code": "-Wparentheses", "source": "clang"}]}}}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# RUN: clangd -lit-test < %s | FileCheck -strict-whitespace %s
|
||||
{"jsonrpc":"2.0","id":0,"method":"initialize","params":{"processId":123,"rootPath":"clangd","capabilities":{},"trace":"off"}}
|
||||
---
|
||||
{"jsonrpc":"2.0","method":"textDocument/didOpen","params":{"textDocument":{"uri":"test:///foo.c","languageId":"c","version":1,"text":"int main(int i, char **a) { if (i = 2) {}}"}}}
|
||||
{"jsonrpc":"2.0","method":"textDocument/didOpen","params":{"textDocument":{"uri":"test:///foo.c","languageId":"c","text":"int main(int i, char **a) { if (i = 2) {}}"}}}
|
||||
# CHECK: "method": "textDocument/publishDiagnostics",
|
||||
# CHECK-NEXT: "params": {
|
||||
# CHECK-NEXT: "diagnostics": [
|
||||
@@ -22,7 +22,8 @@
|
||||
# CHECK-NEXT: "source": "clang"
|
||||
# CHECK-NEXT: }
|
||||
# CHECK-NEXT: ],
|
||||
# CHECK-NEXT: "uri": "file://{{.*}}/foo.c"
|
||||
# CHECK-NEXT: "uri": "file://{{.*}}/foo.c",
|
||||
# CHECK-NEXT: "version": 0
|
||||
# CHECK-NEXT: }
|
||||
---
|
||||
{"jsonrpc":"2.0","id":2,"method":"textDocument/codeAction","params":{"textDocument":{"uri":"test:///foo.c"},"range":{"start":{"line":0,"character":13},"end":{"line":0,"character":35}},"context":{"diagnostics":[{"range":{"start": {"line": 0, "character": 32}, "end": {"line": 0, "character": 37}},"severity":2,"message":"Using the result of an assignment as a condition without parentheses (fixes available)"}]}}}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# RUN: clangd -lit-test < %s | FileCheck -strict-whitespace %s
|
||||
{"jsonrpc":"2.0","id":0,"method":"initialize","params":{"processId":123,"rootPath":"clangd","capabilities":{"textDocument":{"publishDiagnostics":{"codeActionsInline":true}}},"trace":"off"}}
|
||||
---
|
||||
{"jsonrpc":"2.0","method":"textDocument/didOpen","params":{"textDocument":{"uri":"test:///foo.c","languageId":"c","version":1,"text":"struct Point {}; union Point p;"}}}
|
||||
{"jsonrpc":"2.0","method":"textDocument/didOpen","params":{"textDocument":{"uri":"test:///foo.c","languageId":"c","text":"struct Point {}; union Point p;"}}}
|
||||
# CHECK: "method": "textDocument/publishDiagnostics",
|
||||
# CHECK-NEXT: "params": {
|
||||
# CHECK-NEXT: "diagnostics": [
|
||||
@@ -61,7 +61,8 @@
|
||||
# CHECK-NEXT: "severity": 3
|
||||
# CHECK-NEXT: }
|
||||
# CHECK-NEXT: ],
|
||||
# CHECK-NEXT: "uri": "file://{{.*}}/foo.c"
|
||||
# CHECK-NEXT: "uri": "file://{{.*}}/foo.c",
|
||||
# CHECK-NEXT: "version": 0
|
||||
# CHECK-NEXT: }
|
||||
---
|
||||
{"jsonrpc":"2.0","id":4,"method":"shutdown"}
|
||||
|
||||
@@ -12,7 +12,6 @@
|
||||
"textDocument": {
|
||||
"uri": "file:///C:/client/bar.cpp",
|
||||
"languageId": "cpp",
|
||||
"version": 1,
|
||||
"text": "#include \"foo.h\"\nint main(){\nreturn foo();\n}"
|
||||
}
|
||||
}
|
||||
@@ -21,7 +20,8 @@
|
||||
# CHECK: "method": "textDocument/publishDiagnostics",
|
||||
# CHECK-NEXT: "params": {
|
||||
# CHECK-NEXT: "diagnostics": [],
|
||||
# CHECK-NEXT: "uri": "file:///C:/client/bar.cpp"
|
||||
# CHECK-NEXT: "uri": "file:///C:/client/bar.cpp",
|
||||
# CHECK-NEXT: "version": 0
|
||||
# CHECK-NEXT: }
|
||||
---
|
||||
# We're editing bar.cpp, which includes foo.h, where foo.h "exists" at a server location
|
||||
@@ -47,7 +47,7 @@
|
||||
# CHECK-NEXT: "range": {
|
||||
# CHECK-NEXT: "end": {
|
||||
# CHECK-NEXT: "character": {{[0-9]+}},
|
||||
# CHECK-NEXT: "line": {{[0-9]+}}
|
||||
# CHECK-NEXT: "line": {{[0-9]+}}
|
||||
# CHECK-NEXT: },
|
||||
# CHECK-NEXT: "start": {
|
||||
# CHECK-NEXT: "character": {{[0-9]+}},
|
||||
|
||||
@@ -67,7 +67,7 @@
|
||||
# CHECK-NEXT: ]
|
||||
# CHECK-NEXT: },
|
||||
---
|
||||
{"jsonrpc":"2.0","method":"textDocument/didOpen","params":{"textDocument":{"uri":"test:///foo.cpp","languageId":"cpp","version":1,"text":"int x = 2;"}}}
|
||||
{"jsonrpc":"2.0","method":"textDocument/didOpen","params":{"textDocument":{"uri":"test:///foo.cpp","languageId":"cpp","text":"int x = 2;"}}}
|
||||
# CHECK: "method": "textDocument/semanticHighlighting",
|
||||
# CHECK-NEXT: "params": {
|
||||
# CHECK-NEXT: "lines": [
|
||||
@@ -78,12 +78,13 @@
|
||||
# CHECK-NEXT: }
|
||||
# CHECK-NEXT: ],
|
||||
# CHECK-NEXT: "textDocument": {
|
||||
# CHECK-NEXT: "uri": "file://{{.*}}/clangd-test/foo.cpp"
|
||||
# CHECK-NEXT: "uri": "file://{{.*}}/clangd-test/foo.cpp",
|
||||
# CHECK-NEXT: "version": 0
|
||||
# CHECK-NEXT: }
|
||||
# CHECK-NEXT: }
|
||||
# CHECK-NEXT:}
|
||||
---
|
||||
{"jsonrpc":"2.0","method":"textDocument/didOpen","params":{"textDocument":{"uri":"test:///foo2.cpp","languageId":"cpp","version":1,"text":"int x = 2;\nint y = 2;"}}}
|
||||
{"jsonrpc":"2.0","method":"textDocument/didOpen","params":{"textDocument":{"uri":"test:///foo2.cpp","languageId":"cpp","text":"int x = 2;\nint y = 2;"}}}
|
||||
# CHECK: "method": "textDocument/semanticHighlighting",
|
||||
# CHECK-NEXT: "params": {
|
||||
# CHECK-NEXT: "lines": [
|
||||
@@ -99,12 +100,13 @@
|
||||
# CHECK-NEXT: }
|
||||
# CHECK-NEXT: ],
|
||||
# CHECK-NEXT: "textDocument": {
|
||||
# CHECK-NEXT: "uri": "file://{{.*}}/clangd-test/foo2.cpp"
|
||||
# CHECK-NEXT: "uri": "file://{{.*}}/clangd-test/foo2.cpp",
|
||||
# CHECK-NEXT: "version": 0
|
||||
# CHECK-NEXT: }
|
||||
# CHECK-NEXT: }
|
||||
# CHECK-NEXT:}
|
||||
---
|
||||
{"jsonrpc":"2.0","method":"textDocument/didChange","params":{"textDocument":{"uri":"test:///foo.cpp","version":2},"contentChanges": [{"range":{"start": {"line": 0,"character": 10},"end": {"line": 0,"character": 10}},"rangeLength": 0,"text": "\nint y = 2;"}]}}
|
||||
{"jsonrpc":"2.0","method":"textDocument/didChange","params":{"textDocument":{"uri":"test:///foo.cpp"},"contentChanges": [{"range":{"start": {"line": 0,"character": 10},"end": {"line": 0,"character": 10}},"rangeLength": 0,"text": "\nint y = 2;"}]}}
|
||||
# CHECK: "method": "textDocument/semanticHighlighting",
|
||||
# CHECK-NEXT: "params": {
|
||||
# CHECK-NEXT: "lines": [
|
||||
@@ -115,12 +117,13 @@
|
||||
# CHECK-NEXT: }
|
||||
# CHECK-NEXT: ],
|
||||
# CHECK-NEXT: "textDocument": {
|
||||
# CHECK-NEXT: "uri": "file://{{.*}}/clangd-test/foo.cpp"
|
||||
# CHECK-NEXT: "uri": "file://{{.*}}/clangd-test/foo.cpp",
|
||||
# CHECK-NEXT: "version": 1
|
||||
# CHECK-NEXT: }
|
||||
# CHECK-NEXT: }
|
||||
# CHECK-NEXT:}
|
||||
---
|
||||
{"jsonrpc":"2.0","method":"textDocument/didChange","params":{"textDocument":{"uri":"test:///foo.cpp","version":2},"contentChanges": [{"range":{"start": {"line": 0,"character": 10},"end": {"line": 1,"character": 10}},"rangeLength": 11,"text": ""}]}}
|
||||
{"jsonrpc":"2.0","method":"textDocument/didChange","params":{"textDocument":{"uri":"test:///foo.cpp"},"contentChanges": [{"range":{"start": {"line": 0,"character": 10},"end": {"line": 1,"character": 10}},"rangeLength": 11,"text": ""}]}}
|
||||
# CHECK: "method": "textDocument/semanticHighlighting",
|
||||
# CHECK-NEXT: "params": {
|
||||
# CHECK-NEXT: "lines": [
|
||||
@@ -131,7 +134,8 @@
|
||||
# CHECK-NEXT: }
|
||||
# CHECK-NEXT: ],
|
||||
# CHECK-NEXT: "textDocument": {
|
||||
# CHECK-NEXT: "uri": "file://{{.*}}/clangd-test/foo.cpp"
|
||||
# CHECK-NEXT: "uri": "file://{{.*}}/clangd-test/foo.cpp",
|
||||
# CHECK-NEXT: "version": 2
|
||||
# CHECK-NEXT: }
|
||||
# CHECK-NEXT: }
|
||||
# CHECK-NEXT:}
|
||||
|
||||
25
clang-tools-extra/clangd/test/version.test
Normal file
25
clang-tools-extra/clangd/test/version.test
Normal file
@@ -0,0 +1,25 @@
|
||||
# RUN: clangd -lit-test < %s | FileCheck -strict-whitespace %s
|
||||
# Verify versions get recorded/inferred, and are reported in publishDiagnostics.
|
||||
{"jsonrpc":"2.0","id":0,"method":"initialize","params":{}}
|
||||
---
|
||||
{"jsonrpc":"2.0","method":"textDocument/didOpen","params":{"textDocument":{"uri":"test:///foo.c","languageId":"c","text":""}}}
|
||||
# CHECK: "version": 0
|
||||
---
|
||||
{"jsonrpc":"2.0","method":"textDocument/didChange","params":{"textDocument":{"uri":"test:///foo.c","version":5},"contentChanges":[{"text":"a"}]}}
|
||||
# CHECK: "version": 5
|
||||
---
|
||||
{"jsonrpc":"2.0","method":"textDocument/didChange","params":{"textDocument":{"uri":"test:///foo.c"},"contentChanges":[{"text":"b"}]}}
|
||||
# CHECK: "version": 6
|
||||
---
|
||||
{"jsonrpc":"2.0","method":"textDocument/didOpen","params":{"textDocument":{"uri":"test:///bar.c","version": 42, "languageId":"c","text":""}}}
|
||||
# CHECK: "version": 42
|
||||
---
|
||||
{"jsonrpc":"2.0","method":"textDocument/didChange","params":{"textDocument":{"uri":"test:///bar.c"},"contentChanges":[{"text":"c"}]}}
|
||||
# CHECK: "version": 43
|
||||
---
|
||||
{"jsonrpc":"2.0","method":"textDocument/didChange","params":{"textDocument":{"uri":"test:///bar.c", "version": 123},"contentChanges":[{"text":"d"}]}}
|
||||
# CHECK: "version": 123
|
||||
---
|
||||
{"jsonrpc":"2.0","id":6,"method":"shutdown"}
|
||||
---
|
||||
{"jsonrpc":"2.0","method":"exit"}
|
||||
@@ -61,7 +61,7 @@ bool diagsContainErrors(const std::vector<Diag> &Diagnostics) {
|
||||
|
||||
class ErrorCheckingCallbacks : public ClangdServer::Callbacks {
|
||||
public:
|
||||
void onDiagnosticsReady(PathRef File,
|
||||
void onDiagnosticsReady(PathRef File, llvm::StringRef Version,
|
||||
std::vector<Diag> Diagnostics) override {
|
||||
bool HadError = diagsContainErrors(Diagnostics);
|
||||
std::lock_guard<std::mutex> Lock(Mutex);
|
||||
@@ -82,7 +82,7 @@ private:
|
||||
/// least one error.
|
||||
class MultipleErrorCheckingCallbacks : public ClangdServer::Callbacks {
|
||||
public:
|
||||
void onDiagnosticsReady(PathRef File,
|
||||
void onDiagnosticsReady(PathRef File, llvm::StringRef Version,
|
||||
std::vector<Diag> Diagnostics) override {
|
||||
bool HadError = diagsContainErrors(Diagnostics);
|
||||
|
||||
@@ -276,7 +276,7 @@ TEST_F(ClangdVFSTest, PropagatesContexts) {
|
||||
mutable int Got;
|
||||
} FS;
|
||||
struct Callbacks : public ClangdServer::Callbacks {
|
||||
void onDiagnosticsReady(PathRef File,
|
||||
void onDiagnosticsReady(PathRef File, llvm::StringRef Version,
|
||||
std::vector<Diag> Diagnostics) override {
|
||||
Got = Context::current().getExisting(Secret);
|
||||
}
|
||||
@@ -295,6 +295,23 @@ TEST_F(ClangdVFSTest, PropagatesContexts) {
|
||||
EXPECT_EQ(Callbacks.Got, 42);
|
||||
}
|
||||
|
||||
TEST_F(ClangdVFSTest, PropagatesVersion) {
|
||||
MockCompilationDatabase CDB;
|
||||
MockFSProvider FS;
|
||||
struct Callbacks : public ClangdServer::Callbacks {
|
||||
void onDiagnosticsReady(PathRef File, llvm::StringRef Version,
|
||||
std::vector<Diag> Diagnostics) override {
|
||||
Got = Version.str();
|
||||
}
|
||||
std::string Got = "";
|
||||
} Callbacks;
|
||||
|
||||
// Verify that the version is plumbed to diagnostics.
|
||||
ClangdServer Server(CDB, FS, ClangdServer::optsForTest(), &Callbacks);
|
||||
runAddDocument(Server, testPath("foo.cpp"), "void main(){}", "42");
|
||||
EXPECT_EQ(Callbacks.Got, "42");
|
||||
}
|
||||
|
||||
// Only enable this test on Unix
|
||||
#ifdef LLVM_ON_UNIX
|
||||
TEST_F(ClangdVFSTest, SearchLibDir) {
|
||||
@@ -374,7 +391,7 @@ struct bar { T x; };
|
||||
|
||||
// Now switch to C++ mode.
|
||||
CDB.ExtraClangFlags = {"-xc++"};
|
||||
runAddDocument(Server, FooCpp, SourceContents2, WantDiagnostics::Auto);
|
||||
runAddDocument(Server, FooCpp, SourceContents2);
|
||||
EXPECT_FALSE(DiagConsumer.hadErrorInLastDiags());
|
||||
// Subsequent addDocument calls should finish without errors too.
|
||||
runAddDocument(Server, FooCpp, SourceContents1);
|
||||
@@ -406,7 +423,7 @@ int main() { return 0; }
|
||||
|
||||
// Parse without the define, no errors should be produced.
|
||||
CDB.ExtraClangFlags = {};
|
||||
runAddDocument(Server, FooCpp, SourceContents, WantDiagnostics::Auto);
|
||||
runAddDocument(Server, FooCpp, SourceContents);
|
||||
ASSERT_TRUE(Server.blockUntilIdleForTest());
|
||||
EXPECT_FALSE(DiagConsumer.hadErrorInLastDiags());
|
||||
// Subsequent addDocument call should finish without errors too.
|
||||
@@ -467,8 +484,8 @@ int hello;
|
||||
CDB.ExtraClangFlags.clear();
|
||||
DiagConsumer.clear();
|
||||
Server.removeDocument(BazCpp);
|
||||
Server.addDocument(FooCpp, FooSource.code(), WantDiagnostics::Auto);
|
||||
Server.addDocument(BarCpp, BarSource.code(), WantDiagnostics::Auto);
|
||||
Server.addDocument(FooCpp, FooSource.code());
|
||||
Server.addDocument(BarCpp, BarSource.code());
|
||||
ASSERT_TRUE(Server.blockUntilIdleForTest());
|
||||
|
||||
EXPECT_THAT(DiagConsumer.filesWithDiags(),
|
||||
@@ -595,7 +612,7 @@ int d;
|
||||
public:
|
||||
TestDiagConsumer() : Stats(FilesCount, FileStat()) {}
|
||||
|
||||
void onDiagnosticsReady(PathRef File,
|
||||
void onDiagnosticsReady(PathRef File, llvm::StringRef Version,
|
||||
std::vector<Diag> Diagnostics) override {
|
||||
StringRef FileIndexStr = llvm::sys::path::stem(File);
|
||||
ASSERT_TRUE(FileIndexStr.consume_front("Foo"));
|
||||
@@ -672,8 +689,7 @@ int d;
|
||||
bool ShouldHaveErrors = ShouldHaveErrorsDist(RandGen);
|
||||
Server.addDocument(FilePaths[FileIndex],
|
||||
ShouldHaveErrors ? SourceContentsWithErrors
|
||||
: SourceContentsWithoutErrors,
|
||||
WantDiagnostics::Auto);
|
||||
: SourceContentsWithoutErrors);
|
||||
UpdateStatsOnAddDocument(FileIndex, ShouldHaveErrors);
|
||||
};
|
||||
|
||||
@@ -775,7 +791,8 @@ TEST_F(ClangdThreadingTest, NoConcurrentDiagnostics) {
|
||||
NoConcurrentAccessDiagConsumer(std::promise<void> StartSecondReparse)
|
||||
: StartSecondReparse(std::move(StartSecondReparse)) {}
|
||||
|
||||
void onDiagnosticsReady(PathRef, std::vector<Diag>) override {
|
||||
void onDiagnosticsReady(PathRef, llvm::StringRef,
|
||||
std::vector<Diag>) override {
|
||||
++Count;
|
||||
std::unique_lock<std::mutex> Lock(Mutex, std::try_to_lock_t());
|
||||
ASSERT_TRUE(Lock.owns_lock())
|
||||
|
||||
@@ -1509,7 +1509,7 @@ TEST(CompletionTest, DocumentationFromChangedFileCrash) {
|
||||
}
|
||||
int a = fun^
|
||||
)cpp");
|
||||
Server.addDocument(FooCpp, Source.code(), WantDiagnostics::Yes);
|
||||
Server.addDocument(FooCpp, Source.code(), "null", WantDiagnostics::Yes);
|
||||
// We need to wait for preamble to build.
|
||||
ASSERT_TRUE(Server.blockUntilIdleForTest());
|
||||
|
||||
@@ -1575,7 +1575,7 @@ TEST(CompletionTest, NonDocComments) {
|
||||
// FIXME: Auto-completion in a template requires disabling delayed template
|
||||
// parsing.
|
||||
CDB.ExtraClangFlags.push_back("-fno-delayed-template-parsing");
|
||||
runAddDocument(Server, FooCpp, Source.code(), WantDiagnostics::Yes);
|
||||
runAddDocument(Server, FooCpp, Source.code(), "null", WantDiagnostics::Yes);
|
||||
CodeCompleteResult Completions = cantFail(runCodeComplete(
|
||||
Server, FooCpp, Source.point(), clangd::CodeCompleteOptions()));
|
||||
|
||||
|
||||
@@ -151,8 +151,8 @@ void update(FileIndex &M, llvm::StringRef Basename, llvm::StringRef Code) {
|
||||
File.HeaderFilename = (Basename + ".h").str();
|
||||
File.HeaderCode = std::string(Code);
|
||||
auto AST = File.build();
|
||||
M.updatePreamble(File.Filename, AST.getASTContext(), AST.getPreprocessorPtr(),
|
||||
AST.getCanonicalIncludes());
|
||||
M.updatePreamble(File.Filename, /*Version=*/"null", AST.getASTContext(),
|
||||
AST.getPreprocessorPtr(), AST.getCanonicalIncludes());
|
||||
}
|
||||
|
||||
TEST(FileIndexTest, CustomizedURIScheme) {
|
||||
@@ -293,7 +293,8 @@ TEST(FileIndexTest, RebuildWithPreamble) {
|
||||
const CanonicalIncludes &CanonIncludes) {
|
||||
EXPECT_FALSE(IndexUpdated) << "Expected only a single index update";
|
||||
IndexUpdated = true;
|
||||
Index.updatePreamble(FooCpp, Ctx, std::move(PP), CanonIncludes);
|
||||
Index.updatePreamble(FooCpp, /*Version=*/"null", Ctx, std::move(PP),
|
||||
CanonIncludes);
|
||||
});
|
||||
ASSERT_TRUE(IndexUpdated);
|
||||
|
||||
@@ -392,7 +393,7 @@ TEST(FileIndexTest, Relations) {
|
||||
TU.HeaderCode = "class A {}; class B : public A {};";
|
||||
auto AST = TU.build();
|
||||
FileIndex Index;
|
||||
Index.updatePreamble(TU.Filename, AST.getASTContext(),
|
||||
Index.updatePreamble(TU.Filename, /*Version=*/"null", AST.getASTContext(),
|
||||
AST.getPreprocessorPtr(), AST.getCanonicalIncludes());
|
||||
SymbolID A = findSymbol(TU.headerSymbols(), "A").ID;
|
||||
uint32_t Results = 0;
|
||||
|
||||
@@ -702,7 +702,8 @@ TEST(SemanticHighlighting, GeneratesHighlightsWhenFileChange) {
|
||||
std::atomic<int> Count = {0};
|
||||
|
||||
void onHighlightingsReady(
|
||||
PathRef File, std::vector<HighlightingToken> Highlightings) override {
|
||||
PathRef File, llvm::StringRef Version,
|
||||
std::vector<HighlightingToken> Highlightings) override {
|
||||
++Count;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -13,8 +13,9 @@ namespace clang {
|
||||
namespace clangd {
|
||||
|
||||
void runAddDocument(ClangdServer &Server, PathRef File,
|
||||
llvm::StringRef Contents, WantDiagnostics WantDiags) {
|
||||
Server.addDocument(File, Contents, WantDiags);
|
||||
llvm::StringRef Contents, llvm::StringRef Version,
|
||||
WantDiagnostics WantDiags, bool ForceRebuild) {
|
||||
Server.addDocument(File, Contents, Version, WantDiags, ForceRebuild);
|
||||
if (!Server.blockUntilIdleForTest())
|
||||
llvm_unreachable("not idle after addDocument");
|
||||
}
|
||||
|
||||
@@ -23,7 +23,9 @@ namespace clangd {
|
||||
|
||||
// Calls addDocument and then blockUntilIdleForTest.
|
||||
void runAddDocument(ClangdServer &Server, PathRef File, StringRef Contents,
|
||||
WantDiagnostics WantDiags = WantDiagnostics::Auto);
|
||||
StringRef Version = "null",
|
||||
WantDiagnostics WantDiags = WantDiagnostics::Auto,
|
||||
bool ForceRebuild = false);
|
||||
|
||||
llvm::Expected<CodeCompleteResult>
|
||||
runCodeComplete(ClangdServer &Server, PathRef File, Position Pos,
|
||||
|
||||
@@ -41,7 +41,15 @@ using ::testing::Pointee;
|
||||
using ::testing::UnorderedElementsAre;
|
||||
|
||||
MATCHER_P2(TUState, State, ActionName, "") {
|
||||
return arg.Action.S == State && arg.Action.Name == ActionName;
|
||||
if (arg.Action.S != State) {
|
||||
*result_listener << "state is " << arg.Action.S;
|
||||
return false;
|
||||
}
|
||||
if (arg.Action.Name != ActionName) {
|
||||
*result_listener << "name is " << arg.Action.Name;
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
TUScheduler::Options optsForTest() {
|
||||
@@ -62,8 +70,15 @@ protected:
|
||||
void updateWithCallback(TUScheduler &S, PathRef File,
|
||||
llvm::StringRef Contents, WantDiagnostics WD,
|
||||
llvm::unique_function<void()> CB) {
|
||||
updateWithCallback(S, File, getInputs(File, std::string(Contents)), WD,
|
||||
std::move(CB));
|
||||
}
|
||||
|
||||
void updateWithCallback(TUScheduler &S, PathRef File, ParseInputs Inputs,
|
||||
WantDiagnostics WD,
|
||||
llvm::unique_function<void()> CB) {
|
||||
WithContextValue Ctx(llvm::make_scope_exit(std::move(CB)));
|
||||
S.update(File, getInputs(File, std::string(Contents)), WD);
|
||||
S.update(File, Inputs, WD);
|
||||
}
|
||||
|
||||
static Key<llvm::unique_function<void(PathRef File, std::vector<Diag>)>>
|
||||
@@ -78,8 +93,8 @@ protected:
|
||||
reportDiagnostics(File, AST.getDiagnostics(), Publish);
|
||||
}
|
||||
|
||||
void onFailedAST(PathRef File, std::vector<Diag> Diags,
|
||||
PublishFn Publish) override {
|
||||
void onFailedAST(PathRef File, llvm::StringRef Version,
|
||||
std::vector<Diag> Diags, PublishFn Publish) override {
|
||||
reportDiagnostics(File, Diags, Publish);
|
||||
}
|
||||
|
||||
@@ -244,7 +259,9 @@ TEST_F(TUSchedulerTests, PreambleConsistency) {
|
||||
// Schedule two updates (A, B) and two preamble reads (stale, consistent).
|
||||
// The stale read should see A, and the consistent read should see B.
|
||||
// (We recognize the preambles by their included files).
|
||||
updateWithCallback(S, Path, "#include <A>", WantDiagnostics::Yes, [&]() {
|
||||
auto Inputs = getInputs(Path, "#include <A>");
|
||||
Inputs.Version = "A";
|
||||
updateWithCallback(S, Path, Inputs, WantDiagnostics::Yes, [&]() {
|
||||
// This callback runs in between the two preamble updates.
|
||||
|
||||
// This blocks update B, preventing it from winning the race
|
||||
@@ -257,12 +274,14 @@ TEST_F(TUSchedulerTests, PreambleConsistency) {
|
||||
// If the second read was stale, it would usually see A.
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||||
});
|
||||
S.update(Path, getInputs(Path, "#include <B>"), WantDiagnostics::Yes);
|
||||
Inputs.Contents = "#include <B>";
|
||||
Inputs.Version = "B";
|
||||
S.update(Path, Inputs, WantDiagnostics::Yes);
|
||||
|
||||
S.runWithPreamble("StaleRead", Path, TUScheduler::Stale,
|
||||
[&](Expected<InputsAndPreamble> Pre) {
|
||||
ASSERT_TRUE(bool(Pre));
|
||||
assert(bool(Pre));
|
||||
EXPECT_EQ(Pre->Preamble->Version, "A");
|
||||
EXPECT_THAT(includes(Pre->Preamble),
|
||||
ElementsAre("<A>"));
|
||||
InconsistentReadDone.notify();
|
||||
@@ -271,6 +290,7 @@ TEST_F(TUSchedulerTests, PreambleConsistency) {
|
||||
S.runWithPreamble("ConsistentRead", Path, TUScheduler::Consistent,
|
||||
[&](Expected<InputsAndPreamble> Pre) {
|
||||
ASSERT_TRUE(bool(Pre));
|
||||
EXPECT_EQ(Pre->Preamble->Version, "B");
|
||||
EXPECT_THAT(includes(Pre->Preamble),
|
||||
ElementsAre("<B>"));
|
||||
++CallbackCount;
|
||||
@@ -446,6 +466,7 @@ TEST_F(TUSchedulerTests, ManyUpdates) {
|
||||
auto Inputs = getInputs(File, Contents.str());
|
||||
{
|
||||
WithContextValue WithNonce(NonceKey, ++Nonce);
|
||||
Inputs.Version = Nonce;
|
||||
updateWithDiags(
|
||||
S, File, Inputs, WantDiagnostics::Auto,
|
||||
[File, Nonce, &Mut, &TotalUpdates](std::vector<Diag>) {
|
||||
@@ -467,6 +488,8 @@ TEST_F(TUSchedulerTests, ManyUpdates) {
|
||||
ASSERT_TRUE((bool)AST);
|
||||
EXPECT_EQ(AST->Inputs.FS, Inputs.FS);
|
||||
EXPECT_EQ(AST->Inputs.Contents, Inputs.Contents);
|
||||
EXPECT_EQ(AST->Inputs.Version, Inputs.Version);
|
||||
EXPECT_EQ(AST->AST.version(), Inputs.Version);
|
||||
|
||||
std::lock_guard<std::mutex> Lock(Mut);
|
||||
++TotalASTReads;
|
||||
@@ -769,9 +792,6 @@ TEST_F(TUSchedulerTests, Run) {
|
||||
TEST_F(TUSchedulerTests, TUStatus) {
|
||||
class CaptureTUStatus : public ClangdServer::Callbacks {
|
||||
public:
|
||||
void onDiagnosticsReady(PathRef File,
|
||||
std::vector<Diag> Diagnostics) override {}
|
||||
|
||||
void onFileUpdated(PathRef File, const TUStatus &Status) override {
|
||||
std::lock_guard<std::mutex> Lock(Mutex);
|
||||
AllStatus.push_back(Status);
|
||||
@@ -793,7 +813,8 @@ TEST_F(TUSchedulerTests, TUStatus) {
|
||||
|
||||
// We schedule the following tasks in the queue:
|
||||
// [Update] [GoToDefinition]
|
||||
Server.addDocument(testPath("foo.cpp"), Code.code(), WantDiagnostics::Yes);
|
||||
Server.addDocument(testPath("foo.cpp"), Code.code(), "1",
|
||||
WantDiagnostics::Yes);
|
||||
Server.locateSymbolAt(testPath("foo.cpp"), Code.point(),
|
||||
[](Expected<std::vector<LocatedSymbol>> Result) {
|
||||
ASSERT_TRUE((bool)Result);
|
||||
@@ -804,9 +825,9 @@ TEST_F(TUSchedulerTests, TUStatus) {
|
||||
EXPECT_THAT(CaptureTUStatus.allStatus(),
|
||||
ElementsAre(
|
||||
// Statuses of "Update" action.
|
||||
TUState(TUAction::RunningAction, "Update"),
|
||||
TUState(TUAction::BuildingPreamble, "Update"),
|
||||
TUState(TUAction::BuildingFile, "Update"),
|
||||
TUState(TUAction::RunningAction, "Update (1)"),
|
||||
TUState(TUAction::BuildingPreamble, "Update (1)"),
|
||||
TUState(TUAction::BuildingFile, "Update (1)"),
|
||||
|
||||
// Statuses of "Definitions" action
|
||||
TUState(TUAction::RunningAction, "Definitions"),
|
||||
|
||||
@@ -97,7 +97,7 @@ ParsedAST TestTU::build() const {
|
||||
|
||||
SymbolSlab TestTU::headerSymbols() const {
|
||||
auto AST = build();
|
||||
return std::get<0>(indexHeaderSymbols(AST.getASTContext(),
|
||||
return std::get<0>(indexHeaderSymbols(/*Version=*/"null", AST.getASTContext(),
|
||||
AST.getPreprocessorPtr(),
|
||||
AST.getCanonicalIncludes()));
|
||||
}
|
||||
@@ -105,8 +105,8 @@ SymbolSlab TestTU::headerSymbols() const {
|
||||
std::unique_ptr<SymbolIndex> TestTU::index() const {
|
||||
auto AST = build();
|
||||
auto Idx = std::make_unique<FileIndex>(/*UseDex=*/true);
|
||||
Idx->updatePreamble(Filename, AST.getASTContext(), AST.getPreprocessorPtr(),
|
||||
AST.getCanonicalIncludes());
|
||||
Idx->updatePreamble(Filename, /*Version=*/"null", AST.getASTContext(),
|
||||
AST.getPreprocessorPtr(), AST.getCanonicalIncludes());
|
||||
Idx->updateMain(Filename, AST);
|
||||
return std::move(Idx);
|
||||
}
|
||||
|
||||
@@ -849,7 +849,8 @@ TEST(LocateSymbol, WithPreamble) {
|
||||
ElementsAre(Sym("foo.h", FooHeader.range())));
|
||||
|
||||
// Only preamble is built, and no AST is built in this request.
|
||||
Server.addDocument(FooCpp, FooWithoutHeader.code(), WantDiagnostics::No);
|
||||
Server.addDocument(FooCpp, FooWithoutHeader.code(), "null",
|
||||
WantDiagnostics::No);
|
||||
// We build AST here, and it should use the latest preamble rather than the
|
||||
// stale one.
|
||||
EXPECT_THAT(
|
||||
@@ -859,7 +860,8 @@ TEST(LocateSymbol, WithPreamble) {
|
||||
// Reset test environment.
|
||||
runAddDocument(Server, FooCpp, FooWithHeader.code());
|
||||
// Both preamble and AST are built in this request.
|
||||
Server.addDocument(FooCpp, FooWithoutHeader.code(), WantDiagnostics::Yes);
|
||||
Server.addDocument(FooCpp, FooWithoutHeader.code(), "null",
|
||||
WantDiagnostics::Yes);
|
||||
// Use the AST being built in above request.
|
||||
EXPECT_THAT(
|
||||
cantFail(runLocateSymbolAt(Server, FooCpp, FooWithoutHeader.point())),
|
||||
|
||||
Reference in New Issue
Block a user