mirror of
https://github.com/apple/swift.git
synced 2025-12-14 20:36:38 +01:00
389 lines
14 KiB
C++
389 lines
14 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"
|
|
|
|
using namespace SourceKit;
|
|
using namespace llvm;
|
|
|
|
static StringRef getRuntimeLibPath() {
|
|
return sys::path::parent_path(SWIFTLIB_DIR);
|
|
}
|
|
|
|
namespace {
|
|
|
|
class NullEditorConsumer : public EditorConsumer {
|
|
bool needsSemanticInfo() override { return needsSema; }
|
|
|
|
void handleRequestError(const char *Description) override {
|
|
llvm_unreachable("unexpected error");
|
|
}
|
|
|
|
bool syntaxMapEnabled() override { return true; }
|
|
|
|
void handleSyntaxMap(unsigned Offset, unsigned Length, UIdent Kind) override {
|
|
}
|
|
|
|
void handleSemanticAnnotation(unsigned Offset, unsigned Length, UIdent Kind,
|
|
bool isSystem) override {}
|
|
|
|
bool documentStructureEnabled() override { return false; }
|
|
|
|
void 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<std::tuple<UIdent, unsigned, unsigned>> Attrs) override {
|
|
}
|
|
|
|
void endDocumentSubStructure() override {}
|
|
|
|
void handleDocumentSubStructureElement(UIdent Kind, unsigned Offset,
|
|
unsigned Length) override {}
|
|
|
|
void recordAffectedRange(unsigned Offset, unsigned Length) override {}
|
|
|
|
void recordAffectedLineRange(unsigned Line, unsigned Length) override {}
|
|
|
|
void setDiagnosticStage(UIdent DiagStage) override {}
|
|
void handleDiagnostic(const DiagnosticEntryInfo &Info,
|
|
UIdent DiagStage) override {}
|
|
void recordFormattedText(StringRef Text) override {}
|
|
|
|
void handleSourceText(StringRef Text) override {}
|
|
void handleSyntaxTree(const swift::syntax::SourceFileSyntax &SyntaxTree,
|
|
std::unordered_set<unsigned> &ReusedNodeIds) override {}
|
|
|
|
SyntaxTreeTransferMode syntaxTreeTransferMode() override {
|
|
return SyntaxTreeTransferMode::Off;
|
|
}
|
|
|
|
public:
|
|
bool needsSema = false;
|
|
};
|
|
|
|
struct TestCursorInfo {
|
|
std::string Name;
|
|
std::string Typename;
|
|
std::string Filename;
|
|
Optional<std::pair<unsigned, unsigned>> DeclarationLoc;
|
|
};
|
|
|
|
class CursorInfoTest : public ::testing::Test {
|
|
SourceKit::Context &Ctx;
|
|
std::atomic<int> NumTasks;
|
|
NullEditorConsumer Consumer;
|
|
|
|
public:
|
|
LangSupport &getLang() { return Ctx.getSwiftLangSupport(); }
|
|
|
|
void SetUp() override {
|
|
llvm::InitializeAllTargets();
|
|
llvm::InitializeAllTargetMCs();
|
|
llvm::InitializeAllAsmPrinters();
|
|
llvm::InitializeAllAsmParsers();
|
|
NumTasks = 0;
|
|
}
|
|
|
|
CursorInfoTest()
|
|
: Ctx(*new SourceKit::Context(getRuntimeLibPath(),
|
|
SourceKit::createSwiftLangSupport,
|
|
/*dispatchOnMain=*/false)) {
|
|
// 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.
|
|
}
|
|
|
|
void addNotificationReceiver(DocumentUpdateNotificationReceiver Receiver) {
|
|
Ctx.getNotificationCenter().addDocumentUpdateNotificationReceiver(Receiver);
|
|
}
|
|
|
|
void open(const char *DocName, StringRef Text,
|
|
Optional<ArrayRef<const char *>> CArgs = llvm::None) {
|
|
auto Args = CArgs.hasValue() ? makeArgs(DocName, *CArgs)
|
|
: std::vector<const char *>{};
|
|
auto Buf = MemoryBuffer::getMemBufferCopy(Text, DocName);
|
|
getLang().editorOpen(DocName, Buf.get(), Consumer, Args);
|
|
}
|
|
|
|
void replaceText(StringRef DocName, unsigned Offset, unsigned Length,
|
|
StringRef Text) {
|
|
auto Buf = MemoryBuffer::getMemBufferCopy(Text, DocName);
|
|
getLang().editorReplaceText(DocName, Buf.get(), Offset, Length, Consumer);
|
|
}
|
|
|
|
TestCursorInfo getCursor(const char *DocName, unsigned Offset,
|
|
ArrayRef<const char *> CArgs) {
|
|
auto Args = makeArgs(DocName, CArgs);
|
|
Semaphore sema(0);
|
|
|
|
TestCursorInfo TestInfo;
|
|
getLang().getCursorInfo(DocName, Offset, 0, false, false, Args,
|
|
[&](const CursorInfoData &Info) {
|
|
TestInfo.Name = Info.Name;
|
|
TestInfo.Typename = Info.TypeName;
|
|
TestInfo.Filename = Info.Filename;
|
|
TestInfo.DeclarationLoc = Info.DeclarationLoc;
|
|
sema.signal();
|
|
});
|
|
|
|
bool expired = sema.wait(60 * 1000);
|
|
if (expired)
|
|
llvm::report_fatal_error("check took too long");
|
|
return TestInfo;
|
|
}
|
|
|
|
unsigned findOffset(StringRef Val, StringRef Text) {
|
|
auto pos = Text.find(Val);
|
|
assert(pos != StringRef::npos);
|
|
return pos;
|
|
}
|
|
|
|
void setNeedsSema(bool needsSema) { Consumer.needsSema = needsSema; }
|
|
|
|
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
|
|
|
|
TEST_F(CursorInfoTest, FileNotExist) {
|
|
const char *DocName = "/test.swift";
|
|
const char *Contents =
|
|
"let foo = 0\n";
|
|
const char *Args[] = { "/<not-existent-file>" };
|
|
|
|
open(DocName, Contents);
|
|
auto FooOffs = findOffset("foo =", Contents);
|
|
auto Info = getCursor(DocName, FooOffs, Args);
|
|
EXPECT_STREQ("foo", Info.Name.c_str());
|
|
EXPECT_STREQ("Int", Info.Typename.c_str());
|
|
}
|
|
|
|
static const char *ExpensiveInit =
|
|
"[0:0,0:0,0:0,0:0,0:0,0:0,0:0]";
|
|
|
|
TEST_F(CursorInfoTest, EditAfter) {
|
|
const char *DocName = "/test.swift";
|
|
const char *Contents =
|
|
"let value = foo\n"
|
|
"let foo = 0\n";
|
|
const char *Args[] = { "-parse-as-library" };
|
|
|
|
open(DocName, Contents);
|
|
auto FooRefOffs = findOffset("foo", Contents);
|
|
auto FooOffs = findOffset("foo =", Contents);
|
|
auto Info = getCursor(DocName, FooRefOffs, Args);
|
|
EXPECT_STREQ("foo", Info.Name.c_str());
|
|
EXPECT_STREQ("Int", Info.Typename.c_str());
|
|
EXPECT_STREQ(DocName, Info.Filename.c_str());
|
|
ASSERT_TRUE(Info.DeclarationLoc.hasValue());
|
|
EXPECT_EQ(FooOffs, Info.DeclarationLoc->first);
|
|
EXPECT_EQ(strlen("foo"), Info.DeclarationLoc->second);
|
|
|
|
StringRef TextToReplace = "0";
|
|
replaceText(DocName, findOffset(TextToReplace, Contents), TextToReplace.size(),
|
|
ExpensiveInit);
|
|
// Insert a space in front of 'foo' decl.
|
|
replaceText(DocName, FooOffs, 0, " ");
|
|
++FooOffs;
|
|
|
|
// Should not wait for the new AST, it should give the previous answer.
|
|
Info = getCursor(DocName, FooRefOffs, Args);
|
|
EXPECT_STREQ("foo", Info.Name.c_str());
|
|
EXPECT_STREQ("Int", Info.Typename.c_str());
|
|
EXPECT_STREQ(DocName, Info.Filename.c_str());
|
|
ASSERT_TRUE(Info.DeclarationLoc.hasValue());
|
|
EXPECT_EQ(FooOffs, Info.DeclarationLoc->first);
|
|
EXPECT_EQ(strlen("foo"), Info.DeclarationLoc->second);
|
|
}
|
|
|
|
TEST_F(CursorInfoTest, EditBefore) {
|
|
const char *DocName = "/test.swift";
|
|
const char *Contents =
|
|
"let foo = 0\n"
|
|
"let value = foo;\n";
|
|
const char *Args[] = { "-parse-as-library" };
|
|
|
|
open(DocName, Contents);
|
|
auto FooRefOffs = findOffset("foo;", Contents);
|
|
auto FooOffs = findOffset("foo =", Contents);
|
|
auto Info = getCursor(DocName, FooRefOffs, Args);
|
|
EXPECT_STREQ("foo", Info.Name.c_str());
|
|
EXPECT_STREQ("Int", Info.Typename.c_str());
|
|
EXPECT_STREQ(DocName, Info.Filename.c_str());
|
|
ASSERT_TRUE(Info.DeclarationLoc.hasValue());
|
|
EXPECT_EQ(FooOffs, Info.DeclarationLoc->first);
|
|
EXPECT_EQ(strlen("foo"), Info.DeclarationLoc->second);
|
|
|
|
StringRef TextToReplace = "0";
|
|
replaceText(DocName, findOffset(TextToReplace, Contents), TextToReplace.size(),
|
|
ExpensiveInit);
|
|
FooRefOffs += StringRef(ExpensiveInit).size() - TextToReplace.size();
|
|
// Insert a space in front of 'foo' decl.
|
|
replaceText(DocName, FooOffs, 0, " ");
|
|
++FooOffs;
|
|
++FooRefOffs;
|
|
|
|
// Should not wait for the new AST, it should give the previous answer.
|
|
Info = getCursor(DocName, FooRefOffs, Args);
|
|
EXPECT_STREQ("foo", Info.Name.c_str());
|
|
EXPECT_STREQ("Int", Info.Typename.c_str());
|
|
EXPECT_STREQ(DocName, Info.Filename.c_str());
|
|
ASSERT_TRUE(Info.DeclarationLoc.hasValue());
|
|
EXPECT_EQ(FooOffs, Info.DeclarationLoc->first);
|
|
EXPECT_EQ(strlen("foo"), Info.DeclarationLoc->second);
|
|
}
|
|
|
|
TEST_F(CursorInfoTest, CursorInfoMustWaitDueDeclLoc) {
|
|
const char *DocName = "/test.swift";
|
|
const char *Contents =
|
|
"let value = foo\n"
|
|
"let foo = 0\n";
|
|
const char *Args[] = { "-parse-as-library" };
|
|
|
|
open(DocName, Contents);
|
|
auto FooRefOffs = findOffset("foo", Contents);
|
|
auto FooOffs = findOffset("foo =", Contents);
|
|
auto Info = getCursor(DocName, FooRefOffs, Args);
|
|
EXPECT_STREQ("foo", Info.Name.c_str());
|
|
EXPECT_STREQ("Int", Info.Typename.c_str());
|
|
|
|
StringRef TextToReplace = "0";
|
|
replaceText(DocName, findOffset(TextToReplace, Contents), TextToReplace.size(),
|
|
ExpensiveInit);
|
|
// Edit over the 'foo' decl.
|
|
replaceText(DocName, FooOffs, strlen("foo"), "foo");
|
|
|
|
// Should wait for the new AST, because the declaration location for the 'foo'
|
|
// reference has been edited out.
|
|
Info = getCursor(DocName, FooRefOffs, Args);
|
|
EXPECT_STREQ("foo", Info.Name.c_str());
|
|
EXPECT_STREQ("[Int : Int]", Info.Typename.c_str());
|
|
ASSERT_TRUE(Info.DeclarationLoc.hasValue());
|
|
EXPECT_EQ(FooOffs, Info.DeclarationLoc->first);
|
|
EXPECT_EQ(strlen("foo"), Info.DeclarationLoc->second);
|
|
}
|
|
|
|
TEST_F(CursorInfoTest, CursorInfoMustWaitDueOffset) {
|
|
const char *DocName = "/test.swift";
|
|
const char *Contents =
|
|
"let value = foo\n"
|
|
"let foo = 0\n";
|
|
const char *Args[] = { "-parse-as-library" };
|
|
|
|
open(DocName, Contents);
|
|
auto FooRefOffs = findOffset("foo", Contents);
|
|
auto FooOffs = findOffset("foo =", Contents);
|
|
auto Info = getCursor(DocName, FooRefOffs, Args);
|
|
EXPECT_STREQ("foo", Info.Name.c_str());
|
|
EXPECT_STREQ("Int", Info.Typename.c_str());
|
|
|
|
StringRef TextToReplace = "0";
|
|
replaceText(DocName, findOffset(TextToReplace, Contents), TextToReplace.size(),
|
|
ExpensiveInit);
|
|
// Edit over the 'foo' reference.
|
|
replaceText(DocName, FooRefOffs, strlen("foo"), "foo");
|
|
|
|
// Should wait for the new AST, because the cursor location has been edited
|
|
// out.
|
|
Info = getCursor(DocName, FooRefOffs, Args);
|
|
EXPECT_STREQ("foo", Info.Name.c_str());
|
|
EXPECT_STREQ("[Int : Int]", Info.Typename.c_str());
|
|
ASSERT_TRUE(Info.DeclarationLoc.hasValue());
|
|
EXPECT_EQ(FooOffs, Info.DeclarationLoc->first);
|
|
EXPECT_EQ(strlen("foo"), Info.DeclarationLoc->second);
|
|
}
|
|
|
|
TEST_F(CursorInfoTest, CursorInfoMustWaitDueToken) {
|
|
const char *DocName = "/test.swift";
|
|
const char *Contents =
|
|
"let value = foo\n"
|
|
"let foo = 0\n";
|
|
const char *Args[] = { "-parse-as-library" };
|
|
|
|
open(DocName, Contents);
|
|
auto FooRefOffs = findOffset("foo", Contents);
|
|
auto FooOffs = findOffset("foo =", Contents);
|
|
auto Info = getCursor(DocName, FooRefOffs, Args);
|
|
EXPECT_STREQ("foo", Info.Name.c_str());
|
|
EXPECT_STREQ("Int", Info.Typename.c_str());
|
|
|
|
StringRef TextToReplace = "0";
|
|
replaceText(DocName, findOffset(TextToReplace, Contents), TextToReplace.size(),
|
|
ExpensiveInit);
|
|
// Change 'foo' to 'fog' by replacing the last character.
|
|
replaceText(DocName, FooOffs+2, 1, "g");
|
|
replaceText(DocName, FooRefOffs+2, 1, "g");
|
|
|
|
// Should wait for the new AST, because the cursor location points to a
|
|
// different token.
|
|
Info = getCursor(DocName, FooRefOffs, Args);
|
|
EXPECT_STREQ("fog", Info.Name.c_str());
|
|
EXPECT_STREQ("[Int : Int]", Info.Typename.c_str());
|
|
ASSERT_TRUE(Info.DeclarationLoc.hasValue());
|
|
EXPECT_EQ(FooOffs, Info.DeclarationLoc->first);
|
|
EXPECT_EQ(strlen("fog"), Info.DeclarationLoc->second);
|
|
}
|
|
|
|
TEST_F(CursorInfoTest, CursorInfoMustWaitDueTokenRace) {
|
|
const char *DocName = "/test.swift";
|
|
const char *Contents = "let value = foo\n"
|
|
"let foo = 0\n";
|
|
const char *Args[] = {"-parse-as-library"};
|
|
|
|
auto FooRefOffs = findOffset("foo", Contents);
|
|
auto FooOffs = findOffset("foo =", Contents);
|
|
|
|
// Open with args, kicking off an ast build. The hope of this tests is for
|
|
// this AST to still be in the process of building when we start the cursor
|
|
// info, to ensure the ASTManager doesn't try to handle this cursor info with
|
|
// the wrong AST.
|
|
setNeedsSema(true);
|
|
open(DocName, Contents, llvm::makeArrayRef(Args));
|
|
// Change 'foo' to 'fog' by replacing the last character.
|
|
replaceText(DocName, FooOffs + 2, 1, "g");
|
|
replaceText(DocName, FooRefOffs + 2, 1, "g");
|
|
|
|
// Should wait for the new AST, because the cursor location points to a
|
|
// different token.
|
|
auto Info = getCursor(DocName, FooRefOffs, Args);
|
|
EXPECT_STREQ("fog", Info.Name.c_str());
|
|
EXPECT_STREQ("Int", Info.Typename.c_str());
|
|
ASSERT_TRUE(Info.DeclarationLoc.hasValue());
|
|
EXPECT_EQ(FooOffs, Info.DeclarationLoc->first);
|
|
EXPECT_EQ(strlen("fog"), Info.DeclarationLoc->second);
|
|
}
|