mirror of
https://github.com/apple/swift.git
synced 2025-12-14 20:36:38 +01:00
The previous code actually had a "semantic" error as well as a parse error, so depending on the timing we could get 2 diagnosticsl rdar://problem/34373697
227 lines
7.4 KiB
C++
227 lines
7.4 KiB
C++
//===----------------------------------------------------------------------===//
|
|
//
|
|
// This source file is part of the Swift.org open source project
|
|
//
|
|
// Copyright (c) 2014 - 2017 Apple Inc. and the Swift project authors
|
|
// Licensed under Apache License v2.0 with Runtime Library Exception
|
|
//
|
|
// See https://swift.org/LICENSE.txt for license information
|
|
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
#include "SourceKit/Core/Context.h"
|
|
#include "SourceKit/Core/LangSupport.h"
|
|
#include "SourceKit/Core/NotificationCenter.h"
|
|
#include "SourceKit/Support/Concurrency.h"
|
|
#include "SourceKit/SwiftLang/Factory.h"
|
|
#include "llvm/Support/MemoryBuffer.h"
|
|
#include "llvm/Support/Path.h"
|
|
#include "llvm/Support/TargetSelect.h"
|
|
#include "gtest/gtest.h"
|
|
#include <mutex>
|
|
#include <condition_variable>
|
|
|
|
using namespace SourceKit;
|
|
using namespace llvm;
|
|
|
|
static StringRef getRuntimeLibPath() {
|
|
return sys::path::parent_path(SWIFTLIB_DIR);
|
|
}
|
|
|
|
namespace {
|
|
|
|
class DiagConsumer : public EditorConsumer {
|
|
public:
|
|
UIdent DiagStage;
|
|
std::vector<DiagnosticEntryInfo> Diags;
|
|
|
|
private:
|
|
void handleRequestError(const char *Description) override {
|
|
llvm_unreachable("unexpected error");
|
|
}
|
|
|
|
bool handleSyntaxMap(unsigned Offset, unsigned Length, UIdent Kind) override {
|
|
return false;
|
|
}
|
|
|
|
bool handleSemanticAnnotation(unsigned Offset, unsigned Length,
|
|
UIdent Kind, bool isSystem) override {
|
|
return false;
|
|
}
|
|
|
|
bool beginDocumentSubStructure(unsigned Offset, unsigned Length,
|
|
UIdent Kind, UIdent AccessLevel,
|
|
UIdent SetterAccessLevel,
|
|
unsigned NameOffset,
|
|
unsigned NameLength,
|
|
unsigned BodyOffset,
|
|
unsigned BodyLength,
|
|
unsigned DocOffset,
|
|
unsigned DocLength,
|
|
StringRef DisplayName,
|
|
StringRef TypeName,
|
|
StringRef RuntimeName,
|
|
StringRef SelectorName,
|
|
ArrayRef<StringRef> InheritedTypes,
|
|
ArrayRef<UIdent> Attrs) override {
|
|
return false;
|
|
}
|
|
|
|
bool endDocumentSubStructure() override { return false; }
|
|
|
|
bool handleDocumentSubStructureElement(UIdent Kind,
|
|
unsigned Offset,
|
|
unsigned Length) override {
|
|
return false;
|
|
}
|
|
|
|
bool recordAffectedRange(unsigned Offset, unsigned Length) override {
|
|
return false;
|
|
}
|
|
|
|
bool recordAffectedLineRange(unsigned Line, unsigned Length) override {
|
|
return false;
|
|
}
|
|
|
|
bool recordFormattedText(StringRef Text) override { return false; }
|
|
|
|
bool setDiagnosticStage(UIdent diagStage) override {
|
|
DiagStage = diagStage;
|
|
return true;
|
|
}
|
|
bool handleDiagnostic(const DiagnosticEntryInfo &Info,
|
|
UIdent DiagStage) override {
|
|
Diags.push_back(Info);
|
|
return true;
|
|
}
|
|
|
|
bool handleSourceText(StringRef Text) override { return false; }
|
|
};
|
|
|
|
struct DocUpdateMutexState {
|
|
std::mutex Mtx;
|
|
std::condition_variable CV;
|
|
bool HasUpdate = false;
|
|
};
|
|
|
|
class EditTest : public ::testing::Test {
|
|
SourceKit::Context *Ctx;
|
|
std::shared_ptr<DocUpdateMutexState> DocUpdState;
|
|
|
|
public:
|
|
EditTest() {
|
|
// This is avoiding destroying \p SourceKit::Context because another
|
|
// thread may be active trying to use it to post notifications.
|
|
// FIXME: Use shared_ptr ownership to avoid such issues.
|
|
Ctx = new SourceKit::Context(getRuntimeLibPath(),
|
|
SourceKit::createSwiftLangSupport,
|
|
/*dispatchOnMain=*/false);
|
|
auto localDocUpdState = std::make_shared<DocUpdateMutexState>();
|
|
Ctx->getNotificationCenter().addDocumentUpdateNotificationReceiver(
|
|
[localDocUpdState](StringRef docName) {
|
|
std::unique_lock<std::mutex> lk(localDocUpdState->Mtx);
|
|
localDocUpdState->HasUpdate = true;
|
|
localDocUpdState->CV.notify_one();
|
|
});
|
|
DocUpdState = localDocUpdState;
|
|
}
|
|
|
|
LangSupport &getLang() { return Ctx->getSwiftLangSupport(); }
|
|
|
|
void SetUp() override {
|
|
llvm::InitializeAllTargets();
|
|
llvm::InitializeAllTargetMCs();
|
|
llvm::InitializeAllAsmPrinters();
|
|
llvm::InitializeAllAsmParsers();
|
|
}
|
|
|
|
void addNotificationReceiver(DocumentUpdateNotificationReceiver Receiver) {
|
|
Ctx->getNotificationCenter().addDocumentUpdateNotificationReceiver(Receiver);
|
|
}
|
|
|
|
bool waitForDocUpdate() {
|
|
std::chrono::seconds secondsToWait(10);
|
|
std::unique_lock<std::mutex> lk(DocUpdState->Mtx);
|
|
auto when = std::chrono::system_clock::now() + secondsToWait;
|
|
return !DocUpdState->CV.wait_until(
|
|
lk, when, [&]() { return DocUpdState->HasUpdate; });
|
|
}
|
|
|
|
void open(const char *DocName, StringRef Text, ArrayRef<const char *> CArgs,
|
|
EditorConsumer &Consumer) {
|
|
auto Args = makeArgs(DocName, CArgs);
|
|
auto Buf = MemoryBuffer::getMemBufferCopy(Text, DocName);
|
|
getLang().editorOpen(DocName, Buf.get(), /*EnableSyntaxMap=*/false, Consumer,
|
|
Args);
|
|
}
|
|
|
|
void replaceText(StringRef DocName, unsigned Offset, unsigned Length,
|
|
StringRef Text, EditorConsumer &Consumer) {
|
|
auto Buf = MemoryBuffer::getMemBufferCopy(Text, DocName);
|
|
getLang().editorReplaceText(DocName, Buf.get(), Offset, Length, Consumer);
|
|
}
|
|
|
|
unsigned findOffset(StringRef Val, StringRef Text) {
|
|
auto pos = Text.find(Val);
|
|
assert(pos != StringRef::npos);
|
|
return pos;
|
|
}
|
|
|
|
void reset(DiagConsumer &Consumer) {
|
|
Consumer.Diags.clear();
|
|
Consumer.DiagStage = UIdent();
|
|
std::unique_lock<std::mutex> lk(DocUpdState->Mtx);
|
|
DocUpdState->HasUpdate = false;
|
|
}
|
|
|
|
private:
|
|
std::vector<const char *> makeArgs(const char *DocName,
|
|
ArrayRef<const char *> CArgs) {
|
|
std::vector<const char *> Args = CArgs;
|
|
Args.push_back(DocName);
|
|
return Args;
|
|
}
|
|
};
|
|
|
|
} // anonymous namespace
|
|
|
|
static UIdent SemaDiagStage("source.diagnostic.stage.swift.sema");
|
|
static UIdent ParseDiagStage("source.diagnostic.stage.swift.parse");
|
|
|
|
TEST_F(EditTest, DiagsAfterEdit) {
|
|
const char *DocName = "/test.swift";
|
|
const char *Contents =
|
|
"func foo {}\n"
|
|
"let v = 0\n";
|
|
const char *Args[] = { "-parse-as-library" };
|
|
|
|
DiagConsumer Consumer;
|
|
open(DocName, Contents, Args, Consumer);
|
|
ASSERT_EQ(1u, Consumer.Diags.size());
|
|
EXPECT_STREQ("expected '(' in argument list of function declaration", Consumer.Diags[0].Description.c_str());
|
|
|
|
reset(Consumer);
|
|
replaceText(DocName, findOffset("func foo", Contents)+strlen("func foo"), 0, "()", Consumer);
|
|
EXPECT_TRUE(Consumer.Diags.empty());
|
|
|
|
bool expired = waitForDocUpdate();
|
|
ASSERT_FALSE(expired);
|
|
|
|
reset(Consumer);
|
|
replaceText(DocName, 0, 0, StringRef(), Consumer);
|
|
EXPECT_TRUE(Consumer.Diags.empty());
|
|
|
|
// If typecheck completed for the edit we'll get 'sema stage', otherwise
|
|
// we'll get 'parser stage' and there will be another doc-update.
|
|
if (Consumer.DiagStage == ParseDiagStage) {
|
|
bool expired = waitForDocUpdate();
|
|
ASSERT_FALSE(expired);
|
|
|
|
reset(Consumer);
|
|
replaceText(DocName, 0, 0, StringRef(), Consumer);
|
|
EXPECT_TRUE(Consumer.Diags.empty());
|
|
}
|
|
EXPECT_EQ(SemaDiagStage, Consumer.DiagStage);
|
|
}
|