forked from OSchip/llvm-project
1954 lines
49 KiB
C++
1954 lines
49 KiB
C++
//===-- RenameTests.cpp -----------------------------------------*- C++ -*-===//
|
|
//
|
|
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
|
|
// See https://llvm.org/LICENSE.txt for license information.
|
|
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
#include "Annotations.h"
|
|
#include "ClangdServer.h"
|
|
#include "SyncAPI.h"
|
|
#include "TestFS.h"
|
|
#include "TestTU.h"
|
|
#include "index/Ref.h"
|
|
#include "refactor/Rename.h"
|
|
#include "support/TestTracer.h"
|
|
#include "clang/Tooling/Core/Replacement.h"
|
|
#include "llvm/ADT/STLExtras.h"
|
|
#include "llvm/Support/MemoryBuffer.h"
|
|
#include <algorithm>
|
|
#include "gmock/gmock.h"
|
|
#include "gtest/gtest.h"
|
|
|
|
namespace clang {
|
|
namespace clangd {
|
|
namespace {
|
|
|
|
using testing::ElementsAre;
|
|
using testing::Eq;
|
|
using testing::IsEmpty;
|
|
using testing::Pair;
|
|
using testing::SizeIs;
|
|
using testing::UnorderedElementsAre;
|
|
using testing::UnorderedElementsAreArray;
|
|
|
|
llvm::IntrusiveRefCntPtr<llvm::vfs::OverlayFileSystem>
|
|
createOverlay(llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> Base,
|
|
llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> Overlay) {
|
|
auto OFS =
|
|
llvm::makeIntrusiveRefCnt<llvm::vfs::OverlayFileSystem>(std::move(Base));
|
|
OFS->pushOverlay(std::move(Overlay));
|
|
return OFS;
|
|
}
|
|
|
|
llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> getVFSFromAST(ParsedAST &AST) {
|
|
return &AST.getSourceManager().getFileManager().getVirtualFileSystem();
|
|
}
|
|
|
|
// Convert a Range to a Ref.
|
|
Ref refWithRange(const clangd::Range &Range, const std::string &URI) {
|
|
Ref Result;
|
|
Result.Kind = RefKind::Reference | RefKind::Spelled;
|
|
Result.Location.Start.setLine(Range.start.line);
|
|
Result.Location.Start.setColumn(Range.start.character);
|
|
Result.Location.End.setLine(Range.end.line);
|
|
Result.Location.End.setColumn(Range.end.character);
|
|
Result.Location.FileURI = URI.c_str();
|
|
return Result;
|
|
}
|
|
|
|
// Build a RefSlab from all marked ranges in the annotation. The ranges are
|
|
// assumed to associate with the given SymbolName.
|
|
std::unique_ptr<RefSlab> buildRefSlab(const Annotations &Code,
|
|
llvm::StringRef SymbolName,
|
|
llvm::StringRef Path) {
|
|
RefSlab::Builder Builder;
|
|
TestTU TU;
|
|
TU.HeaderCode = std::string(Code.code());
|
|
auto Symbols = TU.headerSymbols();
|
|
const auto &SymbolID = findSymbol(Symbols, SymbolName).ID;
|
|
std::string PathURI = URI::create(Path).toString();
|
|
for (const auto &Range : Code.ranges())
|
|
Builder.insert(SymbolID, refWithRange(Range, PathURI));
|
|
|
|
return std::make_unique<RefSlab>(std::move(Builder).build());
|
|
}
|
|
|
|
std::vector<
|
|
std::pair</*FilePath*/ std::string, /*CodeAfterRename*/ std::string>>
|
|
applyEdits(FileEdits FE) {
|
|
std::vector<std::pair<std::string, std::string>> Results;
|
|
for (auto &It : FE)
|
|
Results.emplace_back(
|
|
It.first().str(),
|
|
llvm::cantFail(tooling::applyAllReplacements(
|
|
It.getValue().InitialCode, It.getValue().Replacements)));
|
|
return Results;
|
|
}
|
|
|
|
// Generates an expected rename result by replacing all ranges in the given
|
|
// annotation with the NewName.
|
|
std::string expectedResult(Annotations Test, llvm::StringRef NewName) {
|
|
std::string Result;
|
|
unsigned NextChar = 0;
|
|
llvm::StringRef Code = Test.code();
|
|
for (const auto &R : Test.llvm::Annotations::ranges()) {
|
|
assert(R.Begin <= R.End && NextChar <= R.Begin);
|
|
Result += Code.substr(NextChar, R.Begin - NextChar);
|
|
Result += NewName;
|
|
NextChar = R.End;
|
|
}
|
|
Result += Code.substr(NextChar);
|
|
return Result;
|
|
}
|
|
|
|
TEST(RenameTest, WithinFileRename) {
|
|
// For each "^" this test moves cursor to its location and applies renaming
|
|
// while checking that all identifiers in [[]] ranges are also renamed.
|
|
llvm::StringRef Tests[] = {
|
|
// Function.
|
|
R"cpp(
|
|
void [[foo^]]() {
|
|
[[fo^o]]();
|
|
}
|
|
)cpp",
|
|
|
|
// Type.
|
|
R"cpp(
|
|
struct [[foo^]] {};
|
|
[[foo]] test() {
|
|
[[f^oo]] x;
|
|
return x;
|
|
}
|
|
)cpp",
|
|
|
|
// Local variable.
|
|
R"cpp(
|
|
void bar() {
|
|
if (auto [[^foo]] = 5) {
|
|
[[foo]] = 3;
|
|
}
|
|
}
|
|
)cpp",
|
|
|
|
// Class, its constructor and destructor.
|
|
R"cpp(
|
|
class [[F^oo]] {
|
|
[[F^oo]]();
|
|
~[[F^oo]]();
|
|
[[F^oo]] *foo(int x);
|
|
|
|
[[F^oo]] *Ptr;
|
|
};
|
|
[[F^oo]]::[[Fo^o]]() {}
|
|
[[F^oo]]::~[[Fo^o]]() {}
|
|
[[F^oo]] *[[F^oo]]::foo(int x) { return Ptr; }
|
|
)cpp",
|
|
|
|
// Template class, its constructor and destructor.
|
|
R"cpp(
|
|
template <typename T>
|
|
class [[F^oo]] {
|
|
[[F^oo]]();
|
|
~[[F^oo]]();
|
|
void f([[F^oo]] x);
|
|
};
|
|
|
|
template<typename T>
|
|
[[F^oo]]<T>::[[Fo^o]]() {}
|
|
|
|
template<typename T>
|
|
[[F^oo]]<T>::~[[Fo^o]]() {}
|
|
)cpp",
|
|
|
|
// Template class constructor.
|
|
R"cpp(
|
|
class [[F^oo]] {
|
|
template<typename T>
|
|
[[Fo^o]]();
|
|
|
|
template<typename T>
|
|
[[F^oo]](T t);
|
|
};
|
|
|
|
template<typename T>
|
|
[[F^oo]]::[[Fo^o]]() {}
|
|
)cpp",
|
|
|
|
// Class in template argument.
|
|
R"cpp(
|
|
class [[F^oo]] {};
|
|
template <typename T> void func();
|
|
template <typename T> class Baz {};
|
|
int main() {
|
|
func<[[F^oo]]>();
|
|
Baz<[[F^oo]]> obj;
|
|
return 0;
|
|
}
|
|
)cpp",
|
|
|
|
// Forward class declaration without definition.
|
|
R"cpp(
|
|
class [[F^oo]];
|
|
[[F^oo]] *f();
|
|
)cpp",
|
|
|
|
// Member function.
|
|
R"cpp(
|
|
struct X {
|
|
void [[F^oo]]() {}
|
|
void Baz() { [[F^oo]](); }
|
|
};
|
|
)cpp",
|
|
|
|
// Templated method instantiation.
|
|
R"cpp(
|
|
template<typename T>
|
|
class Foo {
|
|
public:
|
|
static T [[f^oo]]() {}
|
|
};
|
|
|
|
void bar() {
|
|
Foo<int>::[[f^oo]]();
|
|
}
|
|
)cpp",
|
|
R"cpp(
|
|
template<typename T>
|
|
class Foo {
|
|
public:
|
|
T [[f^oo]]() {}
|
|
};
|
|
|
|
void bar() {
|
|
Foo<int>().[[f^oo]]();
|
|
}
|
|
)cpp",
|
|
|
|
// Template class (partial) specializations.
|
|
R"cpp(
|
|
template <typename T>
|
|
class [[F^oo]] {};
|
|
|
|
template<>
|
|
class [[F^oo]]<bool> {};
|
|
template <typename T>
|
|
class [[F^oo]]<T*> {};
|
|
|
|
void test() {
|
|
[[F^oo]]<int> x;
|
|
[[F^oo]]<bool> y;
|
|
[[F^oo]]<int*> z;
|
|
}
|
|
)cpp",
|
|
|
|
// Incomplete class specializations
|
|
R"cpp(
|
|
template <typename T>
|
|
class [[Fo^o]] {};
|
|
void func([[F^oo]]<int>);
|
|
)cpp",
|
|
|
|
// Template class instantiations.
|
|
R"cpp(
|
|
template <typename T>
|
|
class [[F^oo]] {
|
|
public:
|
|
T foo(T arg, T& ref, T* ptr) {
|
|
T value;
|
|
int number = 42;
|
|
value = (T)number;
|
|
value = static_cast<T>(number);
|
|
return value;
|
|
}
|
|
static void foo(T value) {}
|
|
T member;
|
|
};
|
|
|
|
template <typename T>
|
|
void func() {
|
|
[[F^oo]]<T> obj;
|
|
obj.member = T();
|
|
[[Foo]]<T>::foo();
|
|
}
|
|
|
|
void test() {
|
|
[[F^oo]]<int> i;
|
|
i.member = 0;
|
|
[[F^oo]]<int>::foo(0);
|
|
|
|
[[F^oo]]<bool> b;
|
|
b.member = false;
|
|
[[F^oo]]<bool>::foo(false);
|
|
}
|
|
)cpp",
|
|
|
|
// Template class methods.
|
|
R"cpp(
|
|
template <typename T>
|
|
class A {
|
|
public:
|
|
void [[f^oo]]() {}
|
|
};
|
|
|
|
void func() {
|
|
A<int>().[[f^oo]]();
|
|
A<double>().[[f^oo]]();
|
|
A<float>().[[f^oo]]();
|
|
}
|
|
)cpp",
|
|
|
|
// Templated class specialization.
|
|
R"cpp(
|
|
template<typename T, typename U=bool>
|
|
class [[Foo^]];
|
|
|
|
template<typename T, typename U>
|
|
class [[Foo^]] {};
|
|
|
|
template<typename T=int, typename U>
|
|
class [[Foo^]];
|
|
)cpp",
|
|
R"cpp(
|
|
template<typename T=float, typename U=int>
|
|
class [[Foo^]];
|
|
|
|
template<typename T, typename U>
|
|
class [[Foo^]] {};
|
|
)cpp",
|
|
|
|
// Function template specialization.
|
|
R"cpp(
|
|
template<typename T=int, typename U=bool>
|
|
U [[foo^]]();
|
|
|
|
template<typename T, typename U>
|
|
U [[foo^]]() {};
|
|
)cpp",
|
|
R"cpp(
|
|
template<typename T, typename U>
|
|
U [[foo^]]() {};
|
|
|
|
template<typename T=int, typename U=bool>
|
|
U [[foo^]]();
|
|
)cpp",
|
|
R"cpp(
|
|
template<typename T=int, typename U=bool>
|
|
U [[foo^]]();
|
|
|
|
template<typename T, typename U>
|
|
U [[foo^]]();
|
|
)cpp",
|
|
R"cpp(
|
|
template <typename T>
|
|
void [[f^oo]](T t);
|
|
|
|
template <>
|
|
void [[f^oo]](int a);
|
|
|
|
void test() {
|
|
[[f^oo]]<double>(1);
|
|
}
|
|
)cpp",
|
|
|
|
// Variable template.
|
|
R"cpp(
|
|
template <typename T, int U>
|
|
bool [[F^oo]] = true;
|
|
|
|
// Explicit template specialization
|
|
template <>
|
|
bool [[F^oo]]<int, 0> = false;
|
|
|
|
// Partial template specialization
|
|
template <typename T>
|
|
bool [[F^oo]]<T, 1> = false;
|
|
|
|
void foo() {
|
|
// Ref to the explicit template specialization
|
|
[[F^oo]]<int, 0>;
|
|
// Ref to the primary template.
|
|
[[F^oo]]<double, 2>;
|
|
}
|
|
)cpp",
|
|
|
|
// Complicated class type.
|
|
R"cpp(
|
|
// Forward declaration.
|
|
class [[Fo^o]];
|
|
class Baz {
|
|
virtual int getValue() const = 0;
|
|
};
|
|
|
|
class [[F^oo]] : public Baz {
|
|
public:
|
|
[[F^oo]](int value = 0) : x(value) {}
|
|
|
|
[[F^oo]] &operator++(int);
|
|
|
|
bool operator<([[Foo]] const &rhs);
|
|
int getValue() const;
|
|
private:
|
|
int x;
|
|
};
|
|
|
|
void func() {
|
|
[[F^oo]] *Pointer = 0;
|
|
[[F^oo]] Variable = [[Foo]](10);
|
|
for ([[F^oo]] it; it < Variable; it++);
|
|
const [[F^oo]] *C = new [[Foo]]();
|
|
const_cast<[[F^oo]] *>(C)->getValue();
|
|
[[F^oo]] foo;
|
|
const Baz &BazReference = foo;
|
|
const Baz *BazPointer = &foo;
|
|
reinterpret_cast<const [[^Foo]] *>(BazPointer)->getValue();
|
|
static_cast<const [[^Foo]] &>(BazReference).getValue();
|
|
static_cast<const [[^Foo]] *>(BazPointer)->getValue();
|
|
}
|
|
)cpp",
|
|
|
|
// Static class member.
|
|
R"cpp(
|
|
struct Foo {
|
|
static Foo *[[Static^Member]];
|
|
};
|
|
|
|
Foo* Foo::[[Static^Member]] = nullptr;
|
|
|
|
void foo() {
|
|
Foo* Pointer = Foo::[[Static^Member]];
|
|
}
|
|
)cpp",
|
|
|
|
// Reference in lambda parameters.
|
|
R"cpp(
|
|
template <class T>
|
|
class function;
|
|
template <class R, class... ArgTypes>
|
|
class function<R(ArgTypes...)> {
|
|
public:
|
|
template <typename Functor>
|
|
function(Functor f) {}
|
|
|
|
function() {}
|
|
|
|
R operator()(ArgTypes...) const {}
|
|
};
|
|
|
|
namespace ns {
|
|
class [[Old]] {};
|
|
void f() {
|
|
function<void([[Old]])> func;
|
|
}
|
|
} // namespace ns
|
|
)cpp",
|
|
|
|
// Destructor explicit call.
|
|
R"cpp(
|
|
class [[F^oo]] {
|
|
public:
|
|
~[[^Foo]]();
|
|
};
|
|
|
|
[[Foo^]]::~[[^Foo]]() {}
|
|
|
|
int main() {
|
|
[[Fo^o]] f;
|
|
f.~/*something*/[[^Foo]]();
|
|
f.~[[^Foo]]();
|
|
}
|
|
)cpp",
|
|
|
|
// Derived destructor explicit call.
|
|
R"cpp(
|
|
class [[Bas^e]] {};
|
|
class Derived : public [[Bas^e]] {};
|
|
|
|
int main() {
|
|
[[Bas^e]] *foo = new Derived();
|
|
foo->[[^Base]]::~[[^Base]]();
|
|
}
|
|
)cpp",
|
|
|
|
// CXXConstructor initializer list.
|
|
R"cpp(
|
|
class Baz {};
|
|
class Qux {
|
|
Baz [[F^oo]];
|
|
public:
|
|
Qux();
|
|
};
|
|
Qux::Qux() : [[F^oo]]() {}
|
|
)cpp",
|
|
|
|
// DeclRefExpr.
|
|
R"cpp(
|
|
class C {
|
|
public:
|
|
static int [[F^oo]];
|
|
};
|
|
|
|
int foo(int x);
|
|
#define MACRO(a) foo(a)
|
|
|
|
void func() {
|
|
C::[[F^oo]] = 1;
|
|
MACRO(C::[[Foo]]);
|
|
int y = C::[[F^oo]];
|
|
}
|
|
)cpp",
|
|
|
|
// Macros.
|
|
R"cpp(
|
|
// no rename inside macro body.
|
|
#define M1 foo
|
|
#define M2(x) x
|
|
int [[fo^o]]();
|
|
void boo(int);
|
|
|
|
void qoo() {
|
|
[[f^oo]]();
|
|
boo([[f^oo]]());
|
|
M1();
|
|
boo(M1());
|
|
M2([[f^oo]]());
|
|
M2(M1()); // foo is inside the nested macro body.
|
|
}
|
|
)cpp",
|
|
|
|
// MemberExpr in macros
|
|
R"cpp(
|
|
class Baz {
|
|
public:
|
|
int [[F^oo]];
|
|
};
|
|
int qux(int x);
|
|
#define MACRO(a) qux(a)
|
|
|
|
int main() {
|
|
Baz baz;
|
|
baz.[[F^oo]] = 1;
|
|
MACRO(baz.[[F^oo]]);
|
|
int y = baz.[[F^oo]];
|
|
}
|
|
)cpp",
|
|
|
|
// Fields in classes & partial and full specialiations.
|
|
R"cpp(
|
|
template<typename T>
|
|
struct Foo {
|
|
T [[Vari^able]] = 42;
|
|
};
|
|
|
|
void foo() {
|
|
Foo<int> f;
|
|
f.[[Varia^ble]] = 9000;
|
|
}
|
|
)cpp",
|
|
R"cpp(
|
|
template<typename T, typename U>
|
|
struct Foo {
|
|
T Variable[42];
|
|
U Another;
|
|
|
|
void bar() {}
|
|
};
|
|
|
|
template<typename T>
|
|
struct Foo<T, bool> {
|
|
T [[Var^iable]];
|
|
void bar() { ++[[Var^iable]]; }
|
|
};
|
|
|
|
void foo() {
|
|
Foo<unsigned, bool> f;
|
|
f.[[Var^iable]] = 9000;
|
|
}
|
|
)cpp",
|
|
R"cpp(
|
|
template<typename T, typename U>
|
|
struct Foo {
|
|
T Variable[42];
|
|
U Another;
|
|
|
|
void bar() {}
|
|
};
|
|
|
|
template<typename T>
|
|
struct Foo<T, bool> {
|
|
T Variable;
|
|
void bar() { ++Variable; }
|
|
};
|
|
|
|
template<>
|
|
struct Foo<unsigned, bool> {
|
|
unsigned [[Var^iable]];
|
|
void bar() { ++[[Var^iable]]; }
|
|
};
|
|
|
|
void foo() {
|
|
Foo<unsigned, bool> f;
|
|
f.[[Var^iable]] = 9000;
|
|
}
|
|
)cpp",
|
|
// Static fields.
|
|
R"cpp(
|
|
struct Foo {
|
|
static int [[Var^iable]];
|
|
};
|
|
|
|
int Foo::[[Var^iable]] = 42;
|
|
|
|
void foo() {
|
|
int LocalInt = Foo::[[Var^iable]];
|
|
}
|
|
)cpp",
|
|
R"cpp(
|
|
template<typename T>
|
|
struct Foo {
|
|
static T [[Var^iable]];
|
|
};
|
|
|
|
template <>
|
|
int Foo<int>::[[Var^iable]] = 42;
|
|
|
|
template <>
|
|
bool Foo<bool>::[[Var^iable]] = true;
|
|
|
|
void foo() {
|
|
int LocalInt = Foo<int>::[[Var^iable]];
|
|
bool LocalBool = Foo<bool>::[[Var^iable]];
|
|
}
|
|
)cpp",
|
|
|
|
// Template parameters.
|
|
R"cpp(
|
|
template <typename [[^T]]>
|
|
class Foo {
|
|
[[T^]] foo([[T^]] arg, [[T^]]& ref, [[^T]]* ptr) {
|
|
[[T]] value;
|
|
int number = 42;
|
|
value = ([[T^]])number;
|
|
value = static_cast<[[^T]]>(number);
|
|
return value;
|
|
}
|
|
static void foo([[T^]] value) {}
|
|
[[T^]] member;
|
|
};
|
|
)cpp",
|
|
|
|
// Typedef.
|
|
R"cpp(
|
|
namespace ns {
|
|
class basic_string {};
|
|
typedef basic_string [[s^tring]];
|
|
} // namespace ns
|
|
|
|
ns::[[s^tring]] foo();
|
|
)cpp",
|
|
|
|
// Variable.
|
|
R"cpp(
|
|
namespace A {
|
|
int [[F^oo]];
|
|
}
|
|
int Foo;
|
|
int Qux = Foo;
|
|
int Baz = A::[[^Foo]];
|
|
void fun() {
|
|
struct {
|
|
int Foo;
|
|
} b = {100};
|
|
int Foo = 100;
|
|
Baz = Foo;
|
|
{
|
|
extern int Foo;
|
|
Baz = Foo;
|
|
Foo = A::[[F^oo]] + Baz;
|
|
A::[[Fo^o]] = b.Foo;
|
|
}
|
|
Foo = b.Foo;
|
|
}
|
|
)cpp",
|
|
|
|
// Namespace alias.
|
|
R"cpp(
|
|
namespace a { namespace b { void foo(); } }
|
|
namespace [[^x]] = a::b;
|
|
void bar() {
|
|
[[x^]]::foo();
|
|
}
|
|
)cpp",
|
|
|
|
// Enum.
|
|
R"cpp(
|
|
enum [[C^olor]] { Red, Green, Blue };
|
|
void foo() {
|
|
[[C^olor]] c;
|
|
c = [[C^olor]]::Blue;
|
|
}
|
|
)cpp",
|
|
|
|
// Scoped enum.
|
|
R"cpp(
|
|
enum class [[K^ind]] { ABC };
|
|
void ff() {
|
|
[[K^ind]] s;
|
|
s = [[K^ind]]::ABC;
|
|
}
|
|
)cpp",
|
|
|
|
// Template class in template argument list.
|
|
R"cpp(
|
|
template<typename T>
|
|
class [[Fo^o]] {};
|
|
template <template<typename> class Z> struct Bar { };
|
|
template <> struct Bar<[[F^oo]]> {};
|
|
)cpp",
|
|
|
|
// Designated initializer.
|
|
R"cpp(
|
|
struct Bar {
|
|
int [[Fo^o]];
|
|
};
|
|
Bar bar { .[[^Foo]] = 42 };
|
|
)cpp",
|
|
|
|
// Nested designated initializer.
|
|
R"cpp(
|
|
struct Baz {
|
|
int Field;
|
|
};
|
|
struct Bar {
|
|
Baz [[Fo^o]];
|
|
};
|
|
// FIXME: v selecting here results in renaming Field.
|
|
Bar bar { .[[Foo]].Field = 42 };
|
|
)cpp",
|
|
R"cpp(
|
|
struct Baz {
|
|
int [[Fiel^d]];
|
|
};
|
|
struct Bar {
|
|
Baz Foo;
|
|
};
|
|
Bar bar { .Foo.[[^Field]] = 42 };
|
|
)cpp",
|
|
|
|
// Templated alias.
|
|
R"cpp(
|
|
template <typename T>
|
|
class X { T t; };
|
|
|
|
template <typename T>
|
|
using [[Fo^o]] = X<T>;
|
|
|
|
void bar() {
|
|
[[Fo^o]]<int> Bar;
|
|
}
|
|
)cpp",
|
|
|
|
// Alias.
|
|
R"cpp(
|
|
class X {};
|
|
using [[F^oo]] = X;
|
|
|
|
void bar() {
|
|
[[Fo^o]] Bar;
|
|
}
|
|
)cpp",
|
|
|
|
// Alias within a namespace.
|
|
R"cpp(
|
|
namespace x { class X {}; }
|
|
namespace ns {
|
|
using [[Fo^o]] = x::X;
|
|
}
|
|
|
|
void bar() {
|
|
ns::[[Fo^o]] Bar;
|
|
}
|
|
)cpp",
|
|
|
|
// Alias within macros.
|
|
R"cpp(
|
|
namespace x { class Old {}; }
|
|
namespace ns {
|
|
#define REF(alias) alias alias_var;
|
|
|
|
#define ALIAS(old) \
|
|
using old##Alias = x::old; \
|
|
REF(old##Alias);
|
|
|
|
ALIAS(Old);
|
|
|
|
[[Old^Alias]] old_alias;
|
|
}
|
|
|
|
void bar() {
|
|
ns::[[Old^Alias]] Bar;
|
|
}
|
|
)cpp",
|
|
|
|
// User defined conversion.
|
|
R"cpp(
|
|
class [[F^oo]] {
|
|
public:
|
|
[[F^oo]]() {}
|
|
};
|
|
|
|
class Baz {
|
|
public:
|
|
operator [[F^oo]]() {
|
|
return [[F^oo]]();
|
|
}
|
|
};
|
|
|
|
int main() {
|
|
Baz boo;
|
|
[[F^oo]] foo = static_cast<[[F^oo]]>(boo);
|
|
}
|
|
)cpp",
|
|
|
|
// ObjC, should not crash.
|
|
R"cpp(
|
|
@interface ObjC {
|
|
char [[da^ta]];
|
|
} @end
|
|
)cpp",
|
|
};
|
|
llvm::StringRef NewName = "NewName";
|
|
for (llvm::StringRef T : Tests) {
|
|
SCOPED_TRACE(T);
|
|
Annotations Code(T);
|
|
auto TU = TestTU::withCode(Code.code());
|
|
TU.ExtraArgs.push_back("-xobjective-c++");
|
|
auto AST = TU.build();
|
|
auto Index = TU.index();
|
|
for (const auto &RenamePos : Code.points()) {
|
|
auto RenameResult =
|
|
rename({RenamePos, NewName, AST, testPath(TU.Filename),
|
|
getVFSFromAST(AST), Index.get()});
|
|
ASSERT_TRUE(bool(RenameResult)) << RenameResult.takeError();
|
|
ASSERT_EQ(1u, RenameResult->GlobalChanges.size());
|
|
EXPECT_EQ(
|
|
applyEdits(std::move(RenameResult->GlobalChanges)).front().second,
|
|
expectedResult(Code, NewName));
|
|
}
|
|
}
|
|
}
|
|
|
|
TEST(RenameTest, Renameable) {
|
|
struct Case {
|
|
const char *Code;
|
|
const char* ErrorMessage; // null if no error
|
|
bool IsHeaderFile;
|
|
llvm::StringRef NewName = "MockName";
|
|
};
|
|
const bool HeaderFile = true;
|
|
Case Cases[] = {
|
|
{R"cpp(// allow -- function-local
|
|
void f(int [[Lo^cal]]) {
|
|
[[Local]] = 2;
|
|
}
|
|
)cpp",
|
|
nullptr, HeaderFile},
|
|
|
|
{R"cpp(// disallow -- symbol in anonymous namespace in header is not indexable.
|
|
namespace {
|
|
class Unin^dexable {};
|
|
}
|
|
)cpp",
|
|
"not eligible for indexing", HeaderFile},
|
|
|
|
{R"cpp(// disallow -- namespace symbol isn't supported
|
|
namespace n^s {}
|
|
)cpp",
|
|
"not a supported kind", HeaderFile},
|
|
|
|
{
|
|
R"cpp(
|
|
#define MACRO 1
|
|
int s = MAC^RO;
|
|
)cpp",
|
|
"not a supported kind", HeaderFile},
|
|
|
|
{
|
|
R"cpp(
|
|
struct X { X operator++(int); };
|
|
void f(X x) {x+^+;})cpp",
|
|
"no symbol", HeaderFile},
|
|
|
|
{R"cpp(// disallow rename on excluded symbols (e.g. std symbols)
|
|
namespace std {
|
|
class str^ing {};
|
|
}
|
|
)cpp",
|
|
"not a supported kind", !HeaderFile},
|
|
{R"cpp(// disallow rename on excluded symbols (e.g. std symbols)
|
|
namespace std {
|
|
inline namespace __u {
|
|
class str^ing {};
|
|
}
|
|
}
|
|
)cpp",
|
|
"not a supported kind", !HeaderFile},
|
|
|
|
{R"cpp(// disallow rename on non-normal identifiers.
|
|
@interface Foo {}
|
|
-(int) fo^o:(int)x; // Token is an identifier, but declaration name isn't a simple identifier.
|
|
@end
|
|
)cpp",
|
|
"not a supported kind", HeaderFile},
|
|
{R"cpp(// FIXME: rename virtual/override methods is not supported yet.
|
|
struct A {
|
|
virtual void f^oo() {}
|
|
};
|
|
)cpp",
|
|
"not a supported kind", !HeaderFile},
|
|
{R"cpp(
|
|
void foo(int);
|
|
void foo(char);
|
|
template <typename T> void f(T t) {
|
|
fo^o(t);
|
|
})cpp",
|
|
"multiple symbols", !HeaderFile},
|
|
|
|
{R"cpp(// disallow rename on unrelated token.
|
|
cl^ass Foo {};
|
|
)cpp",
|
|
"no symbol", !HeaderFile},
|
|
|
|
{R"cpp(// disallow rename on unrelated token.
|
|
temp^late<typename T>
|
|
class Foo {};
|
|
)cpp",
|
|
"no symbol", !HeaderFile},
|
|
|
|
{R"cpp(
|
|
namespace {
|
|
int Conflict;
|
|
int Va^r;
|
|
}
|
|
)cpp",
|
|
"conflict", !HeaderFile, "Conflict"},
|
|
|
|
{R"cpp(
|
|
int Conflict;
|
|
int Va^r;
|
|
)cpp",
|
|
"conflict", !HeaderFile, "Conflict"},
|
|
|
|
{R"cpp(
|
|
class Foo {
|
|
int Conflict;
|
|
int Va^r;
|
|
};
|
|
)cpp",
|
|
"conflict", !HeaderFile, "Conflict"},
|
|
|
|
{R"cpp(
|
|
enum E {
|
|
Conflict,
|
|
Fo^o,
|
|
};
|
|
)cpp",
|
|
"conflict", !HeaderFile, "Conflict"},
|
|
|
|
{R"cpp(
|
|
int Conflict;
|
|
enum E { // transparent context.
|
|
F^oo,
|
|
};
|
|
)cpp",
|
|
"conflict", !HeaderFile, "Conflict"},
|
|
|
|
{R"cpp(
|
|
void func() {
|
|
bool Whatever;
|
|
int V^ar;
|
|
char Conflict;
|
|
}
|
|
)cpp",
|
|
"conflict", !HeaderFile, "Conflict"},
|
|
|
|
{R"cpp(
|
|
void func() {
|
|
if (int Conflict = 42) {
|
|
int V^ar;
|
|
}
|
|
}
|
|
)cpp",
|
|
"conflict", !HeaderFile, "Conflict"},
|
|
|
|
{R"cpp(
|
|
void func() {
|
|
if (int Conflict = 42) {
|
|
} else {
|
|
bool V^ar;
|
|
}
|
|
}
|
|
)cpp",
|
|
"conflict", !HeaderFile, "Conflict"},
|
|
|
|
{R"cpp(
|
|
void func() {
|
|
if (int V^ar = 42) {
|
|
} else {
|
|
bool Conflict;
|
|
}
|
|
}
|
|
)cpp",
|
|
"conflict", !HeaderFile, "Conflict"},
|
|
|
|
{R"cpp(
|
|
void func() {
|
|
while (int V^ar = 10) {
|
|
bool Conflict = true;
|
|
}
|
|
}
|
|
)cpp",
|
|
"conflict", !HeaderFile, "Conflict"},
|
|
|
|
{R"cpp(
|
|
void func() {
|
|
for (int Something = 9000, Anything = 14, Conflict = 42; Anything > 9;
|
|
++Something) {
|
|
int V^ar;
|
|
}
|
|
}
|
|
)cpp",
|
|
"conflict", !HeaderFile, "Conflict"},
|
|
|
|
{R"cpp(
|
|
void func() {
|
|
for (int V^ar = 14, Conflict = 42;;) {
|
|
}
|
|
}
|
|
)cpp",
|
|
"conflict", !HeaderFile, "Conflict"},
|
|
|
|
{R"cpp(
|
|
void func(int Conflict) {
|
|
bool V^ar;
|
|
}
|
|
)cpp",
|
|
"conflict", !HeaderFile, "Conflict"},
|
|
|
|
{R"cpp(
|
|
void func(int Var);
|
|
|
|
void func(int V^ar) {
|
|
bool Conflict;
|
|
}
|
|
)cpp",
|
|
"conflict", !HeaderFile, "Conflict"},
|
|
|
|
{R"cpp(// No conflict: only forward declaration's argument is renamed.
|
|
void func(int [[V^ar]]);
|
|
|
|
void func(int Var) {
|
|
bool Conflict;
|
|
}
|
|
)cpp",
|
|
nullptr, !HeaderFile, "Conflict"},
|
|
|
|
{R"cpp(
|
|
void func(int V^ar, int Conflict) {
|
|
}
|
|
)cpp",
|
|
"conflict", !HeaderFile, "Conflict"},
|
|
|
|
{R"cpp(// Trying to rename into the same name, SameName == SameName.
|
|
void func() {
|
|
int S^ameName;
|
|
}
|
|
)cpp",
|
|
"new name is the same", !HeaderFile, "SameName"},
|
|
{R"cpp(// Ensure it doesn't associate base specifier with base name.
|
|
struct A {};
|
|
struct B : priv^ate A {};
|
|
)cpp",
|
|
"Cannot rename symbol: there is no symbol at the given location", false},
|
|
{R"cpp(// Ensure it doesn't associate base specifier with base name.
|
|
/*error-ok*/
|
|
struct A {
|
|
A() : inva^lid(0) {}
|
|
};
|
|
)cpp",
|
|
"no symbol", false},
|
|
};
|
|
|
|
for (const auto& Case : Cases) {
|
|
SCOPED_TRACE(Case.Code);
|
|
Annotations T(Case.Code);
|
|
TestTU TU = TestTU::withCode(T.code());
|
|
TU.ExtraArgs.push_back("-fno-delayed-template-parsing");
|
|
if (Case.IsHeaderFile) {
|
|
// We open the .h file as the main file.
|
|
TU.Filename = "test.h";
|
|
// Parsing the .h file as C++ include.
|
|
TU.ExtraArgs.push_back("-xobjective-c++-header");
|
|
}
|
|
auto AST = TU.build();
|
|
llvm::StringRef NewName = Case.NewName;
|
|
auto Results = rename({T.point(), NewName, AST, testPath(TU.Filename)});
|
|
bool WantRename = true;
|
|
if (T.ranges().empty())
|
|
WantRename = false;
|
|
if (!WantRename) {
|
|
assert(Case.ErrorMessage && "Error message must be set!");
|
|
EXPECT_FALSE(Results)
|
|
<< "expected rename returned an error: " << T.code();
|
|
auto ActualMessage = llvm::toString(Results.takeError());
|
|
EXPECT_THAT(ActualMessage, testing::HasSubstr(Case.ErrorMessage));
|
|
} else {
|
|
EXPECT_TRUE(bool(Results)) << "rename returned an error: "
|
|
<< llvm::toString(Results.takeError());
|
|
ASSERT_EQ(1u, Results->GlobalChanges.size());
|
|
EXPECT_EQ(applyEdits(std::move(Results->GlobalChanges)).front().second,
|
|
expectedResult(T, NewName));
|
|
}
|
|
}
|
|
}
|
|
|
|
MATCHER_P(newText, T, "") { return arg.newText == T; }
|
|
|
|
TEST(RenameTest, IndexMergeMainFile) {
|
|
Annotations Code("int ^x();");
|
|
TestTU TU = TestTU::withCode(Code.code());
|
|
TU.Filename = "main.cc";
|
|
auto AST = TU.build();
|
|
|
|
auto Main = testPath("main.cc");
|
|
auto InMemFS = llvm::makeIntrusiveRefCnt<llvm::vfs::InMemoryFileSystem>();
|
|
InMemFS->addFile(testPath("main.cc"), 0,
|
|
llvm::MemoryBuffer::getMemBuffer(Code.code()));
|
|
InMemFS->addFile(testPath("other.cc"), 0,
|
|
llvm::MemoryBuffer::getMemBuffer(Code.code()));
|
|
|
|
auto Rename = [&](const SymbolIndex *Idx) {
|
|
RenameInputs Inputs{Code.point(),
|
|
"xPrime",
|
|
AST,
|
|
Main,
|
|
Idx ? createOverlay(getVFSFromAST(AST), InMemFS)
|
|
: nullptr,
|
|
Idx,
|
|
RenameOptions()};
|
|
auto Results = rename(Inputs);
|
|
EXPECT_TRUE(bool(Results)) << llvm::toString(Results.takeError());
|
|
return std::move(*Results);
|
|
};
|
|
|
|
// We do not expect to see duplicated edits from AST vs index.
|
|
auto Results = Rename(TU.index().get());
|
|
EXPECT_THAT(Results.GlobalChanges.keys(), ElementsAre(Main));
|
|
EXPECT_THAT(Results.GlobalChanges[Main].asTextEdits(),
|
|
ElementsAre(newText("xPrime")));
|
|
|
|
// Sanity check: we do expect to see index results!
|
|
TU.Filename = "other.cc";
|
|
Results = Rename(TU.index().get());
|
|
EXPECT_THAT(Results.GlobalChanges.keys(),
|
|
UnorderedElementsAre(Main, testPath("other.cc")));
|
|
|
|
#ifdef CLANGD_PATH_CASE_INSENSITIVE
|
|
// On case-insensitive systems, no duplicates if AST vs index case differs.
|
|
// https://github.com/clangd/clangd/issues/665
|
|
TU.Filename = "MAIN.CC";
|
|
Results = Rename(TU.index().get());
|
|
EXPECT_THAT(Results.GlobalChanges.keys(), ElementsAre(Main));
|
|
EXPECT_THAT(Results.GlobalChanges[Main].asTextEdits(),
|
|
ElementsAre(newText("xPrime")));
|
|
#endif
|
|
}
|
|
|
|
TEST(RenameTest, MainFileReferencesOnly) {
|
|
// filter out references not from main file.
|
|
llvm::StringRef Test =
|
|
R"cpp(
|
|
void test() {
|
|
int [[fo^o]] = 1;
|
|
// rename references not from main file are not included.
|
|
#include "foo.inc"
|
|
})cpp";
|
|
|
|
Annotations Code(Test);
|
|
auto TU = TestTU::withCode(Code.code());
|
|
TU.AdditionalFiles["foo.inc"] = R"cpp(
|
|
#define Macro(X) X
|
|
&Macro(foo);
|
|
&foo;
|
|
)cpp";
|
|
auto AST = TU.build();
|
|
llvm::StringRef NewName = "abcde";
|
|
|
|
auto RenameResult =
|
|
rename({Code.point(), NewName, AST, testPath(TU.Filename)});
|
|
ASSERT_TRUE(bool(RenameResult)) << RenameResult.takeError() << Code.point();
|
|
ASSERT_EQ(1u, RenameResult->GlobalChanges.size());
|
|
EXPECT_EQ(applyEdits(std::move(RenameResult->GlobalChanges)).front().second,
|
|
expectedResult(Code, NewName));
|
|
}
|
|
|
|
TEST(RenameTest, ProtobufSymbolIsExcluded) {
|
|
Annotations Code("Prot^obuf buf;");
|
|
auto TU = TestTU::withCode(Code.code());
|
|
TU.HeaderCode =
|
|
R"cpp(// Generated by the protocol buffer compiler. DO NOT EDIT!
|
|
class Protobuf {};
|
|
)cpp";
|
|
TU.HeaderFilename = "protobuf.pb.h";
|
|
auto AST = TU.build();
|
|
auto Results = rename({Code.point(), "newName", AST, testPath(TU.Filename)});
|
|
EXPECT_FALSE(Results);
|
|
EXPECT_THAT(llvm::toString(Results.takeError()),
|
|
testing::HasSubstr("not a supported kind"));
|
|
}
|
|
|
|
TEST(RenameTest, PrepareRename) {
|
|
Annotations FooH("void func();");
|
|
Annotations FooCC(R"cpp(
|
|
#include "foo.h"
|
|
void [[fu^nc]]() {}
|
|
)cpp");
|
|
std::string FooHPath = testPath("foo.h");
|
|
std::string FooCCPath = testPath("foo.cc");
|
|
MockFS FS;
|
|
FS.Files[FooHPath] = std::string(FooH.code());
|
|
FS.Files[FooCCPath] = std::string(FooCC.code());
|
|
|
|
auto ServerOpts = ClangdServer::optsForTest();
|
|
ServerOpts.BuildDynamicSymbolIndex = true;
|
|
|
|
trace::TestTracer Tracer;
|
|
MockCompilationDatabase CDB;
|
|
ClangdServer Server(CDB, FS, ServerOpts);
|
|
runAddDocument(Server, FooHPath, FooH.code());
|
|
runAddDocument(Server, FooCCPath, FooCC.code());
|
|
|
|
auto Results = runPrepareRename(Server, FooCCPath, FooCC.point(),
|
|
/*NewName=*/llvm::None, {});
|
|
// Verify that for multi-file rename, we only return main-file occurrences.
|
|
ASSERT_TRUE(bool(Results)) << Results.takeError();
|
|
// We don't know the result is complete in prepareRename (passing a nullptr
|
|
// index internally), so GlobalChanges should be empty.
|
|
EXPECT_TRUE(Results->GlobalChanges.empty());
|
|
EXPECT_THAT(FooCC.ranges(),
|
|
testing::UnorderedElementsAreArray(Results->LocalChanges));
|
|
|
|
// Name validation.
|
|
Results = runPrepareRename(Server, FooCCPath, FooCC.point(),
|
|
/*NewName=*/std::string("int"), {});
|
|
EXPECT_FALSE(Results);
|
|
EXPECT_THAT(llvm::toString(Results.takeError()),
|
|
testing::HasSubstr("keyword"));
|
|
EXPECT_THAT(Tracer.takeMetric("rename_name_invalid", "Keywords"),
|
|
ElementsAre(1));
|
|
|
|
for (std::string BadIdent : {"foo!bar", "123foo", "😀@"}) {
|
|
Results = runPrepareRename(Server, FooCCPath, FooCC.point(),
|
|
/*NewName=*/BadIdent, {});
|
|
EXPECT_FALSE(Results);
|
|
EXPECT_THAT(llvm::toString(Results.takeError()),
|
|
testing::HasSubstr("identifier"));
|
|
EXPECT_THAT(Tracer.takeMetric("rename_name_invalid", "BadIdentifier"),
|
|
ElementsAre(1));
|
|
}
|
|
for (std::string GoodIdent : {"fooBar", "__foo$", "😀"}) {
|
|
Results = runPrepareRename(Server, FooCCPath, FooCC.point(),
|
|
/*NewName=*/GoodIdent, {});
|
|
EXPECT_TRUE(bool(Results));
|
|
}
|
|
}
|
|
|
|
TEST(CrossFileRenameTests, DirtyBuffer) {
|
|
Annotations FooCode("class [[Foo]] {};");
|
|
std::string FooPath = testPath("foo.cc");
|
|
Annotations FooDirtyBuffer("class [[Foo]] {};\n// this is dirty buffer");
|
|
Annotations BarCode("void [[Bar]]() {}");
|
|
std::string BarPath = testPath("bar.cc");
|
|
// Build the index, the index has "Foo" references from foo.cc and "Bar"
|
|
// references from bar.cc.
|
|
FileSymbols FSymbols(IndexContents::All);
|
|
FSymbols.update(FooPath, nullptr, buildRefSlab(FooCode, "Foo", FooPath),
|
|
nullptr, false);
|
|
FSymbols.update(BarPath, nullptr, buildRefSlab(BarCode, "Bar", BarPath),
|
|
nullptr, false);
|
|
auto Index = FSymbols.buildIndex(IndexType::Light);
|
|
|
|
Annotations MainCode("class [[Fo^o]] {};");
|
|
auto MainFilePath = testPath("main.cc");
|
|
llvm::IntrusiveRefCntPtr<llvm::vfs::InMemoryFileSystem> InMemFS =
|
|
new llvm::vfs::InMemoryFileSystem;
|
|
InMemFS->addFile(FooPath, 0,
|
|
llvm::MemoryBuffer::getMemBuffer(FooDirtyBuffer.code()));
|
|
|
|
// Run rename on Foo, there is a dirty buffer for foo.cc, rename should
|
|
// respect the dirty buffer.
|
|
TestTU TU = TestTU::withCode(MainCode.code());
|
|
auto AST = TU.build();
|
|
llvm::StringRef NewName = "newName";
|
|
auto Results =
|
|
rename({MainCode.point(), NewName, AST, MainFilePath,
|
|
createOverlay(getVFSFromAST(AST), InMemFS), Index.get()});
|
|
ASSERT_TRUE(bool(Results)) << Results.takeError();
|
|
EXPECT_THAT(
|
|
applyEdits(std::move(Results->GlobalChanges)),
|
|
UnorderedElementsAre(
|
|
Pair(Eq(FooPath), Eq(expectedResult(FooDirtyBuffer, NewName))),
|
|
Pair(Eq(MainFilePath), Eq(expectedResult(MainCode, NewName)))));
|
|
|
|
// Run rename on Bar, there is no dirty buffer for the affected file bar.cc,
|
|
// so we should read file content from VFS.
|
|
MainCode = Annotations("void [[Bar]]() { [[B^ar]](); }");
|
|
TU = TestTU::withCode(MainCode.code());
|
|
// Set a file "bar.cc" on disk.
|
|
TU.AdditionalFiles["bar.cc"] = std::string(BarCode.code());
|
|
AST = TU.build();
|
|
Results = rename({MainCode.point(), NewName, AST, MainFilePath,
|
|
createOverlay(getVFSFromAST(AST), InMemFS), Index.get()});
|
|
ASSERT_TRUE(bool(Results)) << Results.takeError();
|
|
EXPECT_THAT(
|
|
applyEdits(std::move(Results->GlobalChanges)),
|
|
UnorderedElementsAre(
|
|
Pair(Eq(BarPath), Eq(expectedResult(BarCode, NewName))),
|
|
Pair(Eq(MainFilePath), Eq(expectedResult(MainCode, NewName)))));
|
|
|
|
// Run rename on a pagination index which couldn't return all refs in one
|
|
// request, we reject rename on this case.
|
|
class PaginationIndex : public SymbolIndex {
|
|
bool refs(const RefsRequest &Req,
|
|
llvm::function_ref<void(const Ref &)> Callback) const override {
|
|
return true; // has more references
|
|
}
|
|
|
|
bool fuzzyFind(
|
|
const FuzzyFindRequest &Req,
|
|
llvm::function_ref<void(const Symbol &)> Callback) const override {
|
|
return false;
|
|
}
|
|
void
|
|
lookup(const LookupRequest &Req,
|
|
llvm::function_ref<void(const Symbol &)> Callback) const override {}
|
|
|
|
void relations(const RelationsRequest &Req,
|
|
llvm::function_ref<void(const SymbolID &, const Symbol &)>
|
|
Callback) const override {}
|
|
|
|
llvm::unique_function<IndexContents(llvm::StringRef) const>
|
|
indexedFiles() const override {
|
|
return [](llvm::StringRef) { return IndexContents::None; };
|
|
}
|
|
|
|
size_t estimateMemoryUsage() const override { return 0; }
|
|
} PIndex;
|
|
Results = rename({MainCode.point(), NewName, AST, MainFilePath,
|
|
createOverlay(getVFSFromAST(AST), InMemFS), &PIndex});
|
|
EXPECT_FALSE(Results);
|
|
EXPECT_THAT(llvm::toString(Results.takeError()),
|
|
testing::HasSubstr("too many occurrences"));
|
|
}
|
|
|
|
TEST(CrossFileRenameTests, DeduplicateRefsFromIndex) {
|
|
auto MainCode = Annotations("int [[^x]] = 2;");
|
|
auto MainFilePath = testPath("main.cc");
|
|
auto BarCode = Annotations("int [[x]];");
|
|
auto BarPath = testPath("bar.cc");
|
|
auto TU = TestTU::withCode(MainCode.code());
|
|
// Set a file "bar.cc" on disk.
|
|
TU.AdditionalFiles["bar.cc"] = std::string(BarCode.code());
|
|
auto AST = TU.build();
|
|
std::string BarPathURI = URI::create(BarPath).toString();
|
|
Ref XRefInBarCC = refWithRange(BarCode.range(), BarPathURI);
|
|
// The index will return duplicated refs, our code should be robost to handle
|
|
// it.
|
|
class DuplicatedXRefIndex : public SymbolIndex {
|
|
public:
|
|
DuplicatedXRefIndex(const Ref &ReturnedRef) : ReturnedRef(ReturnedRef) {}
|
|
bool refs(const RefsRequest &Req,
|
|
llvm::function_ref<void(const Ref &)> Callback) const override {
|
|
// Return two duplicated refs.
|
|
Callback(ReturnedRef);
|
|
Callback(ReturnedRef);
|
|
return false;
|
|
}
|
|
|
|
bool fuzzyFind(const FuzzyFindRequest &,
|
|
llvm::function_ref<void(const Symbol &)>) const override {
|
|
return false;
|
|
}
|
|
void lookup(const LookupRequest &,
|
|
llvm::function_ref<void(const Symbol &)>) const override {}
|
|
|
|
void relations(const RelationsRequest &,
|
|
llvm::function_ref<void(const SymbolID &, const Symbol &)>)
|
|
const override {}
|
|
|
|
llvm::unique_function<IndexContents(llvm::StringRef) const>
|
|
indexedFiles() const override {
|
|
return [](llvm::StringRef) { return IndexContents::None; };
|
|
}
|
|
|
|
size_t estimateMemoryUsage() const override { return 0; }
|
|
Ref ReturnedRef;
|
|
} DIndex(XRefInBarCC);
|
|
llvm::StringRef NewName = "newName";
|
|
auto Results = rename({MainCode.point(), NewName, AST, MainFilePath,
|
|
getVFSFromAST(AST), &DIndex});
|
|
ASSERT_TRUE(bool(Results)) << Results.takeError();
|
|
EXPECT_THAT(
|
|
applyEdits(std::move(Results->GlobalChanges)),
|
|
UnorderedElementsAre(
|
|
Pair(Eq(BarPath), Eq(expectedResult(BarCode, NewName))),
|
|
Pair(Eq(MainFilePath), Eq(expectedResult(MainCode, NewName)))));
|
|
}
|
|
|
|
TEST(CrossFileRenameTests, WithUpToDateIndex) {
|
|
MockCompilationDatabase CDB;
|
|
CDB.ExtraClangFlags = {"-xc++"};
|
|
// rename is runnning on all "^" points in FooH, and "[[]]" ranges are the
|
|
// expected rename occurrences.
|
|
struct Case {
|
|
llvm::StringRef FooH;
|
|
llvm::StringRef FooCC;
|
|
} Cases[] = {
|
|
{
|
|
// classes.
|
|
R"cpp(
|
|
class [[Fo^o]] {
|
|
[[Foo]]();
|
|
~[[Foo]]();
|
|
};
|
|
)cpp",
|
|
R"cpp(
|
|
#include "foo.h"
|
|
[[Foo]]::[[Foo]]() {}
|
|
[[Foo]]::~[[Foo]]() {}
|
|
|
|
void func() {
|
|
[[Foo]] foo;
|
|
}
|
|
)cpp",
|
|
},
|
|
{
|
|
// class templates.
|
|
R"cpp(
|
|
template <typename T>
|
|
class [[Foo]] {};
|
|
// FIXME: explicit template specializations are not supported due the
|
|
// clangd index limitations.
|
|
template <>
|
|
class Foo<double> {};
|
|
)cpp",
|
|
R"cpp(
|
|
#include "foo.h"
|
|
void func() {
|
|
[[F^oo]]<int> foo;
|
|
}
|
|
)cpp",
|
|
},
|
|
{
|
|
// class methods.
|
|
R"cpp(
|
|
class Foo {
|
|
void [[f^oo]]();
|
|
};
|
|
)cpp",
|
|
R"cpp(
|
|
#include "foo.h"
|
|
void Foo::[[foo]]() {}
|
|
|
|
void func(Foo* p) {
|
|
p->[[foo]]();
|
|
}
|
|
)cpp",
|
|
},
|
|
{
|
|
// rename on constructor and destructor.
|
|
R"cpp(
|
|
class [[Foo]] {
|
|
[[^Foo]]();
|
|
~[[Foo^]]();
|
|
};
|
|
)cpp",
|
|
R"cpp(
|
|
#include "foo.h"
|
|
[[Foo]]::[[Foo]]() {}
|
|
[[Foo]]::~[[Foo]]() {}
|
|
|
|
void func() {
|
|
[[Foo]] foo;
|
|
}
|
|
)cpp",
|
|
},
|
|
{
|
|
// functions.
|
|
R"cpp(
|
|
void [[f^oo]]();
|
|
)cpp",
|
|
R"cpp(
|
|
#include "foo.h"
|
|
void [[foo]]() {}
|
|
|
|
void func() {
|
|
[[foo]]();
|
|
}
|
|
)cpp",
|
|
},
|
|
{
|
|
// typedefs.
|
|
R"cpp(
|
|
typedef int [[IN^T]];
|
|
[[INT]] foo();
|
|
)cpp",
|
|
R"cpp(
|
|
#include "foo.h"
|
|
[[INT]] foo() {}
|
|
)cpp",
|
|
},
|
|
{
|
|
// usings.
|
|
R"cpp(
|
|
using [[I^NT]] = int;
|
|
[[INT]] foo();
|
|
)cpp",
|
|
R"cpp(
|
|
#include "foo.h"
|
|
[[INT]] foo() {}
|
|
)cpp",
|
|
},
|
|
{
|
|
// variables.
|
|
R"cpp(
|
|
static const int [[VA^R]] = 123;
|
|
)cpp",
|
|
R"cpp(
|
|
#include "foo.h"
|
|
int s = [[VAR]];
|
|
)cpp",
|
|
},
|
|
{
|
|
// scope enums.
|
|
R"cpp(
|
|
enum class [[K^ind]] { ABC };
|
|
)cpp",
|
|
R"cpp(
|
|
#include "foo.h"
|
|
[[Kind]] ff() {
|
|
return [[Kind]]::ABC;
|
|
}
|
|
)cpp",
|
|
},
|
|
{
|
|
// enum constants.
|
|
R"cpp(
|
|
enum class Kind { [[A^BC]] };
|
|
)cpp",
|
|
R"cpp(
|
|
#include "foo.h"
|
|
Kind ff() {
|
|
return Kind::[[ABC]];
|
|
}
|
|
)cpp",
|
|
},
|
|
{
|
|
// Implicit references in macro expansions.
|
|
R"cpp(
|
|
class [[Fo^o]] {};
|
|
#define FooFoo Foo
|
|
#define FOO Foo
|
|
)cpp",
|
|
R"cpp(
|
|
#include "foo.h"
|
|
void bar() {
|
|
[[Foo]] x;
|
|
FOO y;
|
|
FooFoo z;
|
|
}
|
|
)cpp",
|
|
},
|
|
};
|
|
|
|
trace::TestTracer Tracer;
|
|
for (const auto &T : Cases) {
|
|
SCOPED_TRACE(T.FooH);
|
|
Annotations FooH(T.FooH);
|
|
Annotations FooCC(T.FooCC);
|
|
std::string FooHPath = testPath("foo.h");
|
|
std::string FooCCPath = testPath("foo.cc");
|
|
|
|
MockFS FS;
|
|
FS.Files[FooHPath] = std::string(FooH.code());
|
|
FS.Files[FooCCPath] = std::string(FooCC.code());
|
|
|
|
auto ServerOpts = ClangdServer::optsForTest();
|
|
ServerOpts.BuildDynamicSymbolIndex = true;
|
|
ClangdServer Server(CDB, FS, ServerOpts);
|
|
|
|
// Add all files to clangd server to make sure the dynamic index has been
|
|
// built.
|
|
runAddDocument(Server, FooHPath, FooH.code());
|
|
runAddDocument(Server, FooCCPath, FooCC.code());
|
|
|
|
llvm::StringRef NewName = "NewName";
|
|
for (const auto &RenamePos : FooH.points()) {
|
|
EXPECT_THAT(Tracer.takeMetric("rename_files"), SizeIs(0));
|
|
auto FileEditsList =
|
|
llvm::cantFail(runRename(Server, FooHPath, RenamePos, NewName, {}));
|
|
EXPECT_THAT(Tracer.takeMetric("rename_files"), ElementsAre(2));
|
|
EXPECT_THAT(
|
|
applyEdits(std::move(FileEditsList.GlobalChanges)),
|
|
UnorderedElementsAre(
|
|
Pair(Eq(FooHPath), Eq(expectedResult(T.FooH, NewName))),
|
|
Pair(Eq(FooCCPath), Eq(expectedResult(T.FooCC, NewName)))));
|
|
}
|
|
}
|
|
}
|
|
|
|
TEST(CrossFileRenameTests, CrossFileOnLocalSymbol) {
|
|
// cross-file rename should work for function-local symbols, even there is no
|
|
// index provided.
|
|
Annotations Code("void f(int [[abc]]) { [[a^bc]] = 3; }");
|
|
auto TU = TestTU::withCode(Code.code());
|
|
auto Path = testPath(TU.Filename);
|
|
auto AST = TU.build();
|
|
llvm::StringRef NewName = "newName";
|
|
auto Results = rename({Code.point(), NewName, AST, Path});
|
|
ASSERT_TRUE(bool(Results)) << Results.takeError();
|
|
EXPECT_THAT(
|
|
applyEdits(std::move(Results->GlobalChanges)),
|
|
UnorderedElementsAre(Pair(Eq(Path), Eq(expectedResult(Code, NewName)))));
|
|
}
|
|
|
|
TEST(CrossFileRenameTests, BuildRenameEdits) {
|
|
Annotations Code("[[😂]]");
|
|
auto LSPRange = Code.range();
|
|
llvm::StringRef FilePath = "/test/TestTU.cpp";
|
|
llvm::StringRef NewName = "abc";
|
|
auto Edit = buildRenameEdit(FilePath, Code.code(), {LSPRange}, NewName);
|
|
ASSERT_TRUE(bool(Edit)) << Edit.takeError();
|
|
ASSERT_EQ(1UL, Edit->Replacements.size());
|
|
EXPECT_EQ(FilePath, Edit->Replacements.begin()->getFilePath());
|
|
EXPECT_EQ(4UL, Edit->Replacements.begin()->getLength());
|
|
|
|
// Test invalid range.
|
|
LSPRange.end = {10, 0}; // out of range
|
|
Edit = buildRenameEdit(FilePath, Code.code(), {LSPRange}, NewName);
|
|
EXPECT_FALSE(Edit);
|
|
EXPECT_THAT(llvm::toString(Edit.takeError()),
|
|
testing::HasSubstr("fail to convert"));
|
|
|
|
// Normal ascii characters.
|
|
Annotations T(R"cpp(
|
|
[[range]]
|
|
[[range]]
|
|
[[range]]
|
|
)cpp");
|
|
Edit = buildRenameEdit(FilePath, T.code(), T.ranges(), NewName);
|
|
ASSERT_TRUE(bool(Edit)) << Edit.takeError();
|
|
EXPECT_EQ(applyEdits(FileEdits{{T.code(), std::move(*Edit)}}).front().second,
|
|
expectedResult(T, NewName));
|
|
}
|
|
|
|
TEST(CrossFileRenameTests, adjustRenameRanges) {
|
|
// Ranges in IndexedCode indicate the indexed occurrences;
|
|
// ranges in DraftCode indicate the expected mapped result, empty indicates
|
|
// we expect no matched result found.
|
|
struct {
|
|
llvm::StringRef IndexedCode;
|
|
llvm::StringRef DraftCode;
|
|
} Tests[] = {
|
|
{
|
|
// both line and column are changed, not a near miss.
|
|
R"cpp(
|
|
int [[x]] = 0;
|
|
)cpp",
|
|
R"cpp(
|
|
// insert a line.
|
|
double x = 0;
|
|
)cpp",
|
|
},
|
|
{
|
|
// subset.
|
|
R"cpp(
|
|
int [[x]] = 0;
|
|
)cpp",
|
|
R"cpp(
|
|
int [[x]] = 0;
|
|
{int x = 0; }
|
|
)cpp",
|
|
},
|
|
{
|
|
// shift columns.
|
|
R"cpp(int [[x]] = 0; void foo(int x);)cpp",
|
|
R"cpp(double [[x]] = 0; void foo(double x);)cpp",
|
|
},
|
|
{
|
|
// shift lines.
|
|
R"cpp(
|
|
int [[x]] = 0;
|
|
void foo(int x);
|
|
)cpp",
|
|
R"cpp(
|
|
// insert a line.
|
|
int [[x]] = 0;
|
|
void foo(int x);
|
|
)cpp",
|
|
},
|
|
};
|
|
LangOptions LangOpts;
|
|
LangOpts.CPlusPlus = true;
|
|
for (const auto &T : Tests) {
|
|
SCOPED_TRACE(T.DraftCode);
|
|
Annotations Draft(T.DraftCode);
|
|
auto ActualRanges = adjustRenameRanges(
|
|
Draft.code(), "x", Annotations(T.IndexedCode).ranges(), LangOpts);
|
|
if (!ActualRanges)
|
|
EXPECT_THAT(Draft.ranges(), testing::IsEmpty());
|
|
else
|
|
EXPECT_THAT(Draft.ranges(),
|
|
testing::UnorderedElementsAreArray(*ActualRanges));
|
|
}
|
|
}
|
|
|
|
TEST(RangePatchingHeuristic, GetMappedRanges) {
|
|
// ^ in LexedCode marks the ranges we expect to be mapped; no ^ indicates
|
|
// there are no mapped ranges.
|
|
struct {
|
|
llvm::StringRef IndexedCode;
|
|
llvm::StringRef LexedCode;
|
|
} Tests[] = {
|
|
{
|
|
// no lexed ranges.
|
|
"[[]]",
|
|
"",
|
|
},
|
|
{
|
|
// both line and column are changed, not a near miss.
|
|
R"([[]])",
|
|
R"(
|
|
[[]]
|
|
)",
|
|
},
|
|
{
|
|
// subset.
|
|
"[[]]",
|
|
"^[[]] [[]]"
|
|
},
|
|
{
|
|
// shift columns.
|
|
"[[]] [[]]",
|
|
" ^[[]] ^[[]] [[]]"
|
|
},
|
|
{
|
|
R"(
|
|
[[]]
|
|
|
|
[[]] [[]]
|
|
)",
|
|
R"(
|
|
// insert a line
|
|
^[[]]
|
|
|
|
^[[]] ^[[]]
|
|
)",
|
|
},
|
|
{
|
|
R"(
|
|
[[]]
|
|
|
|
[[]] [[]]
|
|
)",
|
|
R"(
|
|
// insert a line
|
|
^[[]]
|
|
^[[]] ^[[]] // column is shifted.
|
|
)",
|
|
},
|
|
{
|
|
R"(
|
|
[[]]
|
|
|
|
[[]] [[]]
|
|
)",
|
|
R"(
|
|
// insert a line
|
|
[[]]
|
|
|
|
[[]] [[]] // not mapped (both line and column are changed).
|
|
)",
|
|
},
|
|
{
|
|
R"(
|
|
[[]]
|
|
[[]]
|
|
|
|
[[]]
|
|
[[]]
|
|
|
|
}
|
|
)",
|
|
R"(
|
|
// insert a new line
|
|
^[[]]
|
|
^[[]]
|
|
[[]] // additional range
|
|
^[[]]
|
|
^[[]]
|
|
[[]] // additional range
|
|
)",
|
|
},
|
|
{
|
|
// non-distinct result (two best results), not a near miss
|
|
R"(
|
|
[[]]
|
|
[[]]
|
|
[[]]
|
|
)",
|
|
R"(
|
|
[[]]
|
|
[[]]
|
|
[[]]
|
|
[[]]
|
|
)",
|
|
}
|
|
};
|
|
for (const auto &T : Tests) {
|
|
SCOPED_TRACE(T.IndexedCode);
|
|
auto Lexed = Annotations(T.LexedCode);
|
|
auto LexedRanges = Lexed.ranges();
|
|
std::vector<Range> ExpectedMatches;
|
|
for (auto P : Lexed.points()) {
|
|
auto Match = llvm::find_if(LexedRanges, [&P](const Range& R) {
|
|
return R.start == P;
|
|
});
|
|
ASSERT_NE(Match, LexedRanges.end());
|
|
ExpectedMatches.push_back(*Match);
|
|
}
|
|
|
|
auto Mapped =
|
|
getMappedRanges(Annotations(T.IndexedCode).ranges(), LexedRanges);
|
|
if (!Mapped)
|
|
EXPECT_THAT(ExpectedMatches, IsEmpty());
|
|
else
|
|
EXPECT_THAT(ExpectedMatches, UnorderedElementsAreArray(*Mapped));
|
|
}
|
|
}
|
|
|
|
TEST(CrossFileRenameTests, adjustmentCost) {
|
|
struct {
|
|
llvm::StringRef RangeCode;
|
|
size_t ExpectedCost;
|
|
} Tests[] = {
|
|
{
|
|
R"(
|
|
$idx[[]]$lex[[]] // diff: 0
|
|
)",
|
|
0,
|
|
},
|
|
{
|
|
R"(
|
|
$idx[[]]
|
|
$lex[[]] // line diff: +1
|
|
$idx[[]]
|
|
$lex[[]] // line diff: +1
|
|
$idx[[]]
|
|
$lex[[]] // line diff: +1
|
|
|
|
$idx[[]]
|
|
|
|
$lex[[]] // line diff: +2
|
|
)",
|
|
1 + 1
|
|
},
|
|
{
|
|
R"(
|
|
$idx[[]]
|
|
$lex[[]] // line diff: +1
|
|
$idx[[]]
|
|
|
|
$lex[[]] // line diff: +2
|
|
$idx[[]]
|
|
|
|
|
|
$lex[[]] // line diff: +3
|
|
)",
|
|
1 + 1 + 1
|
|
},
|
|
{
|
|
R"(
|
|
$idx[[]]
|
|
|
|
|
|
$lex[[]] // line diff: +3
|
|
$idx[[]]
|
|
|
|
$lex[[]] // line diff: +2
|
|
$idx[[]]
|
|
$lex[[]] // line diff: +1
|
|
)",
|
|
3 + 1 + 1
|
|
},
|
|
{
|
|
R"(
|
|
$idx[[]]
|
|
$lex[[]] // line diff: +1
|
|
$lex[[]] // line diff: -2
|
|
|
|
$idx[[]]
|
|
$idx[[]]
|
|
|
|
|
|
$lex[[]] // line diff: +3
|
|
)",
|
|
1 + 3 + 5
|
|
},
|
|
{
|
|
R"(
|
|
$idx[[]] $lex[[]] // column diff: +1
|
|
$idx[[]]$lex[[]] // diff: 0
|
|
)",
|
|
1
|
|
},
|
|
{
|
|
R"(
|
|
$idx[[]]
|
|
$lex[[]] // diff: +1
|
|
$idx[[]] $lex[[]] // column diff: +1
|
|
$idx[[]]$lex[[]] // diff: 0
|
|
)",
|
|
1 + 1 + 1
|
|
},
|
|
{
|
|
R"(
|
|
$idx[[]] $lex[[]] // column diff: +1
|
|
)",
|
|
1
|
|
},
|
|
{
|
|
R"(
|
|
// column diffs: +1, +2, +3
|
|
$idx[[]] $lex[[]] $idx[[]] $lex[[]] $idx[[]] $lex[[]]
|
|
)",
|
|
1 + 1 + 1,
|
|
},
|
|
};
|
|
for (const auto &T : Tests) {
|
|
SCOPED_TRACE(T.RangeCode);
|
|
Annotations C(T.RangeCode);
|
|
std::vector<size_t> MappedIndex;
|
|
for (size_t I = 0; I < C.ranges("lex").size(); ++I)
|
|
MappedIndex.push_back(I);
|
|
EXPECT_EQ(renameRangeAdjustmentCost(C.ranges("idx"), C.ranges("lex"),
|
|
MappedIndex),
|
|
T.ExpectedCost);
|
|
}
|
|
}
|
|
|
|
} // namespace
|
|
} // namespace clangd
|
|
} // namespace clang
|