2019-05-07 07:11:56 +00:00
|
|
|
//===-- 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"
|
2019-12-05 12:08:31 +01:00
|
|
|
#include "ClangdServer.h"
|
|
|
|
#include "SyncAPI.h"
|
2019-05-07 07:11:56 +00:00
|
|
|
#include "TestFS.h"
|
|
|
|
#include "TestTU.h"
|
2019-10-23 14:40:20 +02:00
|
|
|
#include "index/Ref.h"
|
2019-05-07 07:11:56 +00:00
|
|
|
#include "refactor/Rename.h"
|
|
|
|
#include "clang/Tooling/Core/Replacement.h"
|
2019-12-09 17:00:51 +01:00
|
|
|
#include "llvm/ADT/STLExtras.h"
|
2019-10-23 14:40:20 +02:00
|
|
|
#include "llvm/Support/MemoryBuffer.h"
|
2019-05-07 07:11:56 +00:00
|
|
|
#include "gmock/gmock.h"
|
|
|
|
#include "gtest/gtest.h"
|
2019-12-09 17:00:51 +01:00
|
|
|
#include <algorithm>
|
2019-05-07 07:11:56 +00:00
|
|
|
|
|
|
|
namespace clang {
|
|
|
|
namespace clangd {
|
|
|
|
namespace {
|
|
|
|
|
2019-10-23 14:40:20 +02:00
|
|
|
using testing::Eq;
|
|
|
|
using testing::Pair;
|
2019-12-09 17:00:51 +01:00
|
|
|
using testing::IsEmpty;
|
2019-10-23 14:40:20 +02:00
|
|
|
using testing::UnorderedElementsAre;
|
2019-12-09 17:00:51 +01:00
|
|
|
using testing::UnorderedElementsAreArray;
|
2019-10-23 14:40:20 +02:00
|
|
|
|
2020-01-04 10:28:41 -05:00
|
|
|
// Convert a Range to a Ref.
|
[clangd] Deduplicate refs from index for cross-file rename.
Summary:
If the index returns duplicated refs, it will trigger the assertion in
BuildRenameEdit (we expect the processing position is always larger the
the previous one, but it is not true if we have duplication), and also
breaks our heuristics.
This patch make the code robost enough to handle duplications, also
save some cost of redundnat llvm::sort.
Though clangd's index doesn't return duplications, our internal index
kythe will.
Reviewers: ilya-biryukov
Subscribers: MaskRay, jkorous, mgrang, arphaman, kadircet, usaxena95, cfe-commits
Tags: #clang
Differential Revision: https://reviews.llvm.org/D71300
2019-12-10 22:15:29 +01:00
|
|
|
Ref refWithRange(const clangd::Range &Range, const std::string &URI) {
|
|
|
|
Ref Result;
|
2020-02-06 10:28:47 +01:00
|
|
|
Result.Kind = RefKind::Reference | RefKind::Spelled;
|
[clangd] Deduplicate refs from index for cross-file rename.
Summary:
If the index returns duplicated refs, it will trigger the assertion in
BuildRenameEdit (we expect the processing position is always larger the
the previous one, but it is not true if we have duplication), and also
breaks our heuristics.
This patch make the code robost enough to handle duplications, also
save some cost of redundnat llvm::sort.
Though clangd's index doesn't return duplications, our internal index
kythe will.
Reviewers: ilya-biryukov
Subscribers: MaskRay, jkorous, mgrang, arphaman, kadircet, usaxena95, cfe-commits
Tags: #clang
Differential Revision: https://reviews.llvm.org/D71300
2019-12-10 22:15:29 +01:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2019-10-23 14:40:20 +02:00
|
|
|
// 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;
|
2020-01-28 20:23:46 +01:00
|
|
|
TU.HeaderCode = std::string(Code.code());
|
2019-10-23 14:40:20 +02:00
|
|
|
auto Symbols = TU.headerSymbols();
|
|
|
|
const auto &SymbolID = findSymbol(Symbols, SymbolName).ID;
|
[clangd] Deduplicate refs from index for cross-file rename.
Summary:
If the index returns duplicated refs, it will trigger the assertion in
BuildRenameEdit (we expect the processing position is always larger the
the previous one, but it is not true if we have duplication), and also
breaks our heuristics.
This patch make the code robost enough to handle duplications, also
save some cost of redundnat llvm::sort.
Though clangd's index doesn't return duplications, our internal index
kythe will.
Reviewers: ilya-biryukov
Subscribers: MaskRay, jkorous, mgrang, arphaman, kadircet, usaxena95, cfe-commits
Tags: #clang
Differential Revision: https://reviews.llvm.org/D71300
2019-12-10 22:15:29 +01:00
|
|
|
std::string PathURI = URI::create(Path).toString();
|
|
|
|
for (const auto &Range : Code.ranges())
|
|
|
|
Builder.insert(SymbolID, refWithRange(Range, PathURI));
|
2019-10-23 14:40:20 +02:00
|
|
|
|
|
|
|
return std::make_unique<RefSlab>(std::move(Builder).build());
|
|
|
|
}
|
|
|
|
|
|
|
|
std::vector<
|
2019-12-05 10:43:29 +01:00
|
|
|
std::pair</*FilePath*/ std::string, /*CodeAfterRename*/ std::string>>
|
2019-10-23 14:40:20 +02:00
|
|
|
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;
|
2019-06-25 08:43:17 +00:00
|
|
|
}
|
|
|
|
|
2019-11-06 15:08:59 +01:00
|
|
|
// 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;
|
|
|
|
}
|
|
|
|
|
2019-11-19 10:10:43 +01:00
|
|
|
TEST(RenameTest, WithinFileRename) {
|
|
|
|
// rename is runnning on all "^" points, and "[[]]" ranges point to the
|
2019-11-06 15:08:59 +01:00
|
|
|
// identifier that is being renamed.
|
|
|
|
llvm::StringRef Tests[] = {
|
2019-11-19 10:10:43 +01:00
|
|
|
// Function.
|
2019-11-06 15:08:59 +01:00
|
|
|
R"cpp(
|
2019-11-19 10:10:43 +01:00
|
|
|
void [[foo^]]() {
|
2019-11-06 15:08:59 +01:00
|
|
|
[[fo^o]]();
|
|
|
|
}
|
|
|
|
)cpp",
|
|
|
|
|
2019-11-19 10:10:43 +01:00
|
|
|
// Type.
|
2019-11-06 15:08:59 +01:00
|
|
|
R"cpp(
|
2019-11-19 10:10:43 +01:00
|
|
|
struct [[foo^]] {};
|
2019-11-06 15:08:59 +01:00
|
|
|
[[foo]] test() {
|
|
|
|
[[f^oo]] x;
|
|
|
|
return x;
|
|
|
|
}
|
|
|
|
)cpp",
|
|
|
|
|
2019-11-19 10:10:43 +01:00
|
|
|
// Local variable.
|
2019-11-06 15:08:59 +01:00
|
|
|
R"cpp(
|
|
|
|
void bar() {
|
|
|
|
if (auto [[^foo]] = 5) {
|
|
|
|
[[foo]] = 3;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
)cpp",
|
2019-11-19 10:10:43 +01:00
|
|
|
|
|
|
|
// Rename class, including constructor/destructor.
|
|
|
|
R"cpp(
|
|
|
|
class [[F^oo]] {
|
|
|
|
[[F^oo]]();
|
|
|
|
~[[Foo]]();
|
|
|
|
void foo(int x);
|
|
|
|
};
|
|
|
|
[[Foo]]::[[Fo^o]]() {}
|
|
|
|
void [[Foo]]::foo(int x) {}
|
|
|
|
)cpp",
|
|
|
|
|
2020-01-21 11:50:57 +01:00
|
|
|
// Rename template class, including constructor/destructor.
|
|
|
|
R"cpp(
|
|
|
|
template <typename T>
|
|
|
|
class [[F^oo]] {
|
|
|
|
[[F^oo]]();
|
|
|
|
~[[F^oo]]();
|
|
|
|
void f([[Foo]] x);
|
|
|
|
};
|
|
|
|
)cpp",
|
|
|
|
|
2019-11-19 10:10:43 +01:00
|
|
|
// 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]];
|
|
|
|
[[Foo]] *f();
|
|
|
|
)cpp",
|
|
|
|
|
|
|
|
// Class methods overrides.
|
|
|
|
R"cpp(
|
|
|
|
struct A {
|
|
|
|
virtual void [[f^oo]]() {}
|
|
|
|
};
|
|
|
|
struct B : A {
|
|
|
|
void [[f^oo]]() override {}
|
|
|
|
};
|
|
|
|
struct C : B {
|
|
|
|
void [[f^oo]]() override {}
|
|
|
|
};
|
|
|
|
|
|
|
|
void func() {
|
|
|
|
A().[[f^oo]]();
|
|
|
|
B().[[f^oo]]();
|
|
|
|
C().[[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() {
|
|
|
|
[[Foo]]<int> x;
|
|
|
|
[[Foo]]<bool> y;
|
|
|
|
[[Foo]]<int*> z;
|
|
|
|
}
|
|
|
|
)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;
|
|
|
|
[[Foo]]<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",
|
|
|
|
|
|
|
|
// Complicated class type.
|
|
|
|
R"cpp(
|
|
|
|
// Forward declaration.
|
|
|
|
class [[Fo^o]];
|
|
|
|
class Baz {
|
|
|
|
virtual int getValue() const = 0;
|
|
|
|
};
|
|
|
|
|
|
|
|
class [[F^oo]] : public Baz {
|
|
|
|
public:
|
|
|
|
[[Foo]](int value = 0) : x(value) {}
|
|
|
|
|
|
|
|
[[Foo]] &operator++(int);
|
|
|
|
|
|
|
|
bool operator<([[Foo]] const &rhs);
|
|
|
|
int getValue() const;
|
|
|
|
private:
|
|
|
|
int x;
|
|
|
|
};
|
|
|
|
|
|
|
|
void func() {
|
|
|
|
[[Foo]] *Pointer = 0;
|
|
|
|
[[Foo]] Variable = [[Foo]](10);
|
|
|
|
for ([[Foo]] it; it < Variable; it++);
|
|
|
|
const [[Foo]] *C = new [[Foo]]();
|
|
|
|
const_cast<[[Foo]] *>(C)->getValue();
|
|
|
|
[[Foo]] 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();
|
|
|
|
}
|
2020-01-21 05:33:39 +01:00
|
|
|
)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]] {};
|
[clangd] Errors in TestTU cause test failures unless suppressed with error-ok.
Summary:
The historic behavior of TestTU is to gather diagnostics and otherwise ignore
them. So if a test has a syntax error, and doesn't assert diagnostics, it
silently misbehaves.
This can be annoying when developing tests, as evidenced by various tests
gaining "assert no diagnostics" where that's not really the point of the test.
This patch aims to make that default behavior. For the first error
(not warning), TestTU will call ADD_FAILURE().
This can be suppressed with a comment containing "error-ok". For now that will
suppress any errors in the TU. We can make this stricter later -verify style.
(-verify itself is hard to reuse because of DiagnosticConsumer interfaces...)
A magic-comment was chosen over a TestTU option because of table-driven tests.
In addition to the behavior change, this patch:
- adds //error-ok where we're knowingly testing invalid code
(e.g. for diagnostics, crash-resilience, or token-level tests)
- fixes a bunch of errors in the checked-in tests, mostly trivial (missing ;)
- removes a bunch of now-redundant instances of "assert no diagnostics"
Reviewers: kadircet
Subscribers: ilya-biryukov, MaskRay, jkorous, arphaman, usaxena95, cfe-commits
Tags: #clang
Differential Revision: https://reviews.llvm.org/D73199
2020-01-22 16:38:41 +01:00
|
|
|
class Derived : public [[Bas^e]] {};
|
2020-01-21 05:33:39 +01:00
|
|
|
|
|
|
|
int main() {
|
|
|
|
[[Bas^e]] *foo = new Derived();
|
|
|
|
foo->[[^Base]]::~[[^Base]]();
|
|
|
|
}
|
2019-11-19 10:10:43 +01:00
|
|
|
)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() {
|
|
|
|
[[foo]]();
|
|
|
|
boo([[foo]]());
|
|
|
|
M1();
|
|
|
|
boo(M1());
|
|
|
|
M2([[foo]]());
|
|
|
|
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.[[Foo]] = 1;
|
|
|
|
MACRO(baz.[[Foo]]);
|
|
|
|
int y = baz.[[Foo]];
|
|
|
|
}
|
|
|
|
)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(
|
2020-01-27 10:39:55 +01:00
|
|
|
namespace ns {
|
2019-11-19 10:10:43 +01:00
|
|
|
class basic_string {};
|
|
|
|
typedef basic_string [[s^tring]];
|
2020-01-27 10:39:55 +01:00
|
|
|
} // namespace ns
|
2019-11-19 10:10:43 +01:00
|
|
|
|
2020-01-27 10:39:55 +01:00
|
|
|
ns::[[s^tring]] foo();
|
2019-11-19 10:10:43 +01:00
|
|
|
)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",
|
|
|
|
|
|
|
|
// Scope enums.
|
|
|
|
R"cpp(
|
|
|
|
enum class [[K^ind]] { ABC };
|
|
|
|
void ff() {
|
|
|
|
[[K^ind]] s;
|
|
|
|
s = [[Kind]]::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<[[Foo]]> {};
|
|
|
|
)cpp",
|
2019-05-07 07:45:41 +00:00
|
|
|
};
|
2020-01-07 13:58:17 -05:00
|
|
|
for (llvm::StringRef T : Tests) {
|
2020-01-27 14:09:27 +01:00
|
|
|
SCOPED_TRACE(T);
|
2019-11-06 15:08:59 +01:00
|
|
|
Annotations Code(T);
|
2019-05-07 07:45:41 +00:00
|
|
|
auto TU = TestTU::withCode(Code.code());
|
2019-11-19 10:10:43 +01:00
|
|
|
TU.ExtraArgs.push_back("-fno-delayed-template-parsing");
|
2019-05-07 07:45:41 +00:00
|
|
|
auto AST = TU.build();
|
2019-11-06 15:08:59 +01:00
|
|
|
llvm::StringRef NewName = "abcde";
|
2019-11-19 10:10:43 +01:00
|
|
|
for (const auto &RenamePos : Code.points()) {
|
|
|
|
auto RenameResult =
|
2019-10-23 14:40:20 +02:00
|
|
|
rename({RenamePos, NewName, AST, testPath(TU.Filename)});
|
|
|
|
ASSERT_TRUE(bool(RenameResult)) << RenameResult.takeError();
|
|
|
|
ASSERT_EQ(1u, RenameResult->size());
|
|
|
|
EXPECT_EQ(applyEdits(std::move(*RenameResult)).front().second,
|
|
|
|
expectedResult(Code, NewName));
|
2019-11-19 10:10:43 +01:00
|
|
|
}
|
2019-05-07 07:45:41 +00:00
|
|
|
}
|
2019-05-07 07:11:56 +00:00
|
|
|
}
|
|
|
|
|
2019-06-25 08:43:17 +00:00
|
|
|
TEST(RenameTest, Renameable) {
|
2019-06-26 08:10:26 +00:00
|
|
|
struct Case {
|
2019-06-27 13:24:10 +00:00
|
|
|
const char *Code;
|
2019-06-26 08:10:26 +00:00
|
|
|
const char* ErrorMessage; // null if no error
|
2019-06-27 13:24:10 +00:00
|
|
|
bool IsHeaderFile;
|
2019-10-02 10:46:37 +00:00
|
|
|
const SymbolIndex *Index;
|
2019-06-26 08:10:26 +00:00
|
|
|
};
|
2019-10-02 10:46:37 +00:00
|
|
|
TestTU OtherFile = TestTU::withCode("Outside s; auto ss = &foo;");
|
|
|
|
const char *CommonHeader = R"cpp(
|
|
|
|
class Outside {};
|
|
|
|
void foo();
|
|
|
|
)cpp";
|
|
|
|
OtherFile.HeaderCode = CommonHeader;
|
|
|
|
OtherFile.Filename = "other.cc";
|
|
|
|
// The index has a "Outside" reference and a "foo" reference.
|
|
|
|
auto OtherFileIndex = OtherFile.index();
|
|
|
|
const SymbolIndex *Index = OtherFileIndex.get();
|
|
|
|
|
2019-06-27 13:24:10 +00:00
|
|
|
const bool HeaderFile = true;
|
2019-06-26 08:10:26 +00:00
|
|
|
Case Cases[] = {
|
|
|
|
{R"cpp(// allow -- function-local
|
2019-06-25 08:43:17 +00:00
|
|
|
void f(int [[Lo^cal]]) {
|
|
|
|
[[Local]] = 2;
|
|
|
|
}
|
|
|
|
)cpp",
|
2019-10-02 10:46:37 +00:00
|
|
|
nullptr, HeaderFile, Index},
|
2019-06-25 08:43:17 +00:00
|
|
|
|
2019-06-26 08:10:26 +00:00
|
|
|
{R"cpp(// allow -- symbol is indexable and has no refs in index.
|
2019-06-25 08:43:17 +00:00
|
|
|
void [[On^lyInThisFile]]();
|
|
|
|
)cpp",
|
2019-10-02 10:46:37 +00:00
|
|
|
nullptr, HeaderFile, Index},
|
2019-06-25 08:43:17 +00:00
|
|
|
|
2019-06-26 08:10:26 +00:00
|
|
|
{R"cpp(// disallow -- symbol is indexable and has other refs in index.
|
2019-06-25 08:43:17 +00:00
|
|
|
void f() {
|
|
|
|
Out^side s;
|
|
|
|
}
|
|
|
|
)cpp",
|
2019-10-02 10:46:37 +00:00
|
|
|
"used outside main file", HeaderFile, Index},
|
2019-06-25 08:43:17 +00:00
|
|
|
|
2020-01-04 10:28:41 -05:00
|
|
|
{R"cpp(// disallow -- symbol in anonymous namespace in header is not indexable.
|
2019-06-25 08:43:17 +00:00
|
|
|
namespace {
|
|
|
|
class Unin^dexable {};
|
|
|
|
}
|
|
|
|
)cpp",
|
2019-10-02 10:46:37 +00:00
|
|
|
"not eligible for indexing", HeaderFile, Index},
|
2019-06-26 08:10:26 +00:00
|
|
|
|
2020-01-04 10:28:41 -05:00
|
|
|
{R"cpp(// allow -- symbol in anonymous namespace in non-header file is indexable.
|
2019-11-29 14:58:44 +01:00
|
|
|
namespace {
|
|
|
|
class [[F^oo]] {};
|
|
|
|
}
|
|
|
|
)cpp",
|
|
|
|
nullptr, !HeaderFile, Index},
|
|
|
|
|
2019-06-26 08:10:26 +00:00
|
|
|
{R"cpp(// disallow -- namespace symbol isn't supported
|
2019-11-18 14:56:59 +01:00
|
|
|
namespace n^s {}
|
2019-06-26 08:10:26 +00:00
|
|
|
)cpp",
|
2019-10-02 10:46:37 +00:00
|
|
|
"not a supported kind", HeaderFile, Index},
|
2019-06-27 13:24:10 +00:00
|
|
|
|
2019-07-01 09:26:48 +00:00
|
|
|
{
|
|
|
|
R"cpp(
|
|
|
|
#define MACRO 1
|
|
|
|
int s = MAC^RO;
|
|
|
|
)cpp",
|
2019-10-02 10:46:37 +00:00
|
|
|
"not a supported kind", HeaderFile, Index},
|
2019-07-01 09:26:48 +00:00
|
|
|
|
2019-09-16 10:16:56 +00:00
|
|
|
{
|
|
|
|
R"cpp(
|
2019-11-18 14:56:59 +01:00
|
|
|
struct X { X operator++(int); };
|
2019-09-16 10:16:56 +00:00
|
|
|
void f(X x) {x+^+;})cpp",
|
2019-12-12 13:10:59 +01:00
|
|
|
"no symbol", HeaderFile, Index},
|
2019-09-16 10:16:56 +00:00
|
|
|
|
2019-06-27 13:24:10 +00:00
|
|
|
{R"cpp(// foo is declared outside the file.
|
|
|
|
void fo^o() {}
|
2019-11-19 10:10:43 +01:00
|
|
|
)cpp",
|
|
|
|
"used outside main file", !HeaderFile /*cc file*/, Index},
|
2019-10-02 10:46:37 +00:00
|
|
|
|
|
|
|
{R"cpp(
|
|
|
|
// We should detect the symbol is used outside the file from the AST.
|
|
|
|
void fo^o() {})cpp",
|
|
|
|
"used outside main file", !HeaderFile, nullptr /*no index*/},
|
2019-11-19 10:10:43 +01:00
|
|
|
|
2020-01-27 10:39:55 +01:00
|
|
|
{R"cpp(// disallow rename on blacklisted symbols (e.g. std symbols)
|
|
|
|
namespace std {
|
|
|
|
class str^ing {};
|
|
|
|
}
|
|
|
|
)cpp",
|
|
|
|
"not a supported kind", !HeaderFile, Index},
|
|
|
|
|
2019-11-19 10:10:43 +01:00
|
|
|
{R"cpp(
|
|
|
|
void foo(int);
|
|
|
|
void foo(char);
|
|
|
|
template <typename T> void f(T t) {
|
|
|
|
fo^o(t);
|
|
|
|
})cpp",
|
|
|
|
"multiple symbols", !HeaderFile, nullptr /*no index*/},
|
|
|
|
|
|
|
|
{R"cpp(// disallow rename on unrelated token.
|
|
|
|
cl^ass Foo {};
|
|
|
|
)cpp",
|
|
|
|
"no symbol", !HeaderFile, nullptr},
|
|
|
|
|
|
|
|
{R"cpp(// disallow rename on unrelated token.
|
|
|
|
temp^late<typename T>
|
|
|
|
class Foo {};
|
|
|
|
)cpp",
|
|
|
|
"no symbol", !HeaderFile, nullptr},
|
2019-06-25 08:43:17 +00:00
|
|
|
};
|
|
|
|
|
2019-06-26 08:10:26 +00:00
|
|
|
for (const auto& Case : Cases) {
|
2020-01-27 14:09:27 +01:00
|
|
|
SCOPED_TRACE(Case.Code);
|
2019-06-27 13:24:10 +00:00
|
|
|
Annotations T(Case.Code);
|
2019-06-25 08:43:17 +00:00
|
|
|
TestTU TU = TestTU::withCode(T.code());
|
|
|
|
TU.HeaderCode = CommonHeader;
|
2019-11-19 10:10:43 +01:00
|
|
|
TU.ExtraArgs.push_back("-fno-delayed-template-parsing");
|
2019-06-27 13:24:10 +00:00
|
|
|
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");
|
|
|
|
}
|
2019-06-25 08:43:17 +00:00
|
|
|
auto AST = TU.build();
|
2019-11-06 15:08:59 +01:00
|
|
|
llvm::StringRef NewName = "dummyNewName";
|
2019-10-23 14:40:20 +02:00
|
|
|
auto Results =
|
|
|
|
rename({T.point(), NewName, AST, testPath(TU.Filename), Case.Index});
|
2019-06-25 08:43:17 +00:00
|
|
|
bool WantRename = true;
|
|
|
|
if (T.ranges().empty())
|
|
|
|
WantRename = false;
|
|
|
|
if (!WantRename) {
|
2019-06-26 08:10:26 +00:00
|
|
|
assert(Case.ErrorMessage && "Error message must be set!");
|
2019-11-06 15:08:59 +01:00
|
|
|
EXPECT_FALSE(Results)
|
2019-10-23 14:40:20 +02:00
|
|
|
<< "expected rename returned an error: " << T.code();
|
2019-06-26 08:10:26 +00:00
|
|
|
auto ActualMessage = llvm::toString(Results.takeError());
|
|
|
|
EXPECT_THAT(ActualMessage, testing::HasSubstr(Case.ErrorMessage));
|
2019-06-25 08:43:17 +00:00
|
|
|
} else {
|
2019-10-23 14:40:20 +02:00
|
|
|
EXPECT_TRUE(bool(Results)) << "rename returned an error: "
|
2019-06-25 08:43:17 +00:00
|
|
|
<< llvm::toString(Results.takeError());
|
2019-10-23 14:40:20 +02:00
|
|
|
ASSERT_EQ(1u, Results->size());
|
|
|
|
EXPECT_EQ(applyEdits(std::move(*Results)).front().second,
|
|
|
|
expectedResult(T, NewName));
|
2019-06-25 08:43:17 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-11-19 10:10:43 +01:00
|
|
|
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 =
|
2019-10-23 14:40:20 +02:00
|
|
|
rename({Code.point(), NewName, AST, testPath(TU.Filename)});
|
2019-11-19 10:10:43 +01:00
|
|
|
ASSERT_TRUE(bool(RenameResult)) << RenameResult.takeError() << Code.point();
|
2019-10-23 14:40:20 +02:00
|
|
|
ASSERT_EQ(1u, RenameResult->size());
|
|
|
|
EXPECT_EQ(applyEdits(std::move(*RenameResult)).front().second,
|
|
|
|
expectedResult(Code, NewName));
|
|
|
|
}
|
|
|
|
|
2019-12-05 12:08:31 +01:00
|
|
|
TEST(CrossFileRenameTests, DirtyBuffer) {
|
2019-10-23 14:40:20 +02:00
|
|
|
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;
|
|
|
|
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");
|
|
|
|
// Dirty buffer for foo.cc.
|
|
|
|
auto GetDirtyBuffer = [&](PathRef Path) -> llvm::Optional<std::string> {
|
|
|
|
if (Path == FooPath)
|
|
|
|
return FooDirtyBuffer.code().str();
|
|
|
|
return llvm::None;
|
|
|
|
};
|
|
|
|
|
|
|
|
// 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,
|
|
|
|
Index.get(), /*CrossFile=*/true, GetDirtyBuffer});
|
|
|
|
ASSERT_TRUE(bool(Results)) << Results.takeError();
|
|
|
|
EXPECT_THAT(
|
|
|
|
applyEdits(std::move(*Results)),
|
|
|
|
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.
|
2020-01-28 20:23:46 +01:00
|
|
|
TU.AdditionalFiles["bar.cc"] = std::string(BarCode.code());
|
2019-10-23 14:40:20 +02:00
|
|
|
AST = TU.build();
|
|
|
|
Results = rename({MainCode.point(), NewName, AST, MainFilePath, Index.get(),
|
|
|
|
/*CrossFile=*/true, GetDirtyBuffer});
|
|
|
|
ASSERT_TRUE(bool(Results)) << Results.takeError();
|
|
|
|
EXPECT_THAT(
|
|
|
|
applyEdits(std::move(*Results)),
|
|
|
|
UnorderedElementsAre(
|
|
|
|
Pair(Eq(BarPath), Eq(expectedResult(BarCode, NewName))),
|
|
|
|
Pair(Eq(MainFilePath), Eq(expectedResult(MainCode, NewName)))));
|
2019-11-28 12:47:32 +01:00
|
|
|
|
|
|
|
// 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 {}
|
|
|
|
size_t estimateMemoryUsage() const override { return 0; }
|
|
|
|
} PIndex;
|
|
|
|
Results = rename({MainCode.point(), NewName, AST, MainFilePath, &PIndex,
|
|
|
|
/*CrossFile=*/true, GetDirtyBuffer});
|
|
|
|
EXPECT_FALSE(Results);
|
|
|
|
EXPECT_THAT(llvm::toString(Results.takeError()),
|
|
|
|
testing::HasSubstr("too many occurrences"));
|
2019-10-23 14:40:20 +02:00
|
|
|
}
|
|
|
|
|
[clangd] Deduplicate refs from index for cross-file rename.
Summary:
If the index returns duplicated refs, it will trigger the assertion in
BuildRenameEdit (we expect the processing position is always larger the
the previous one, but it is not true if we have duplication), and also
breaks our heuristics.
This patch make the code robost enough to handle duplications, also
save some cost of redundnat llvm::sort.
Though clangd's index doesn't return duplications, our internal index
kythe will.
Reviewers: ilya-biryukov
Subscribers: MaskRay, jkorous, mgrang, arphaman, kadircet, usaxena95, cfe-commits
Tags: #clang
Differential Revision: https://reviews.llvm.org/D71300
2019-12-10 22:15:29 +01:00
|
|
|
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.
|
2020-01-28 20:23:46 +01:00
|
|
|
TU.AdditionalFiles["bar.cc"] = std::string(BarCode.code());
|
[clangd] Deduplicate refs from index for cross-file rename.
Summary:
If the index returns duplicated refs, it will trigger the assertion in
BuildRenameEdit (we expect the processing position is always larger the
the previous one, but it is not true if we have duplication), and also
breaks our heuristics.
This patch make the code robost enough to handle duplications, also
save some cost of redundnat llvm::sort.
Though clangd's index doesn't return duplications, our internal index
kythe will.
Reviewers: ilya-biryukov
Subscribers: MaskRay, jkorous, mgrang, arphaman, kadircet, usaxena95, cfe-commits
Tags: #clang
Differential Revision: https://reviews.llvm.org/D71300
2019-12-10 22:15:29 +01:00
|
|
|
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 {}
|
|
|
|
size_t estimateMemoryUsage() const override { return 0; }
|
|
|
|
Ref ReturnedRef;
|
|
|
|
} DIndex(XRefInBarCC);
|
|
|
|
llvm::StringRef NewName = "newName";
|
|
|
|
auto Results = rename({MainCode.point(), NewName, AST, MainFilePath, &DIndex,
|
|
|
|
/*CrossFile=*/true});
|
|
|
|
ASSERT_TRUE(bool(Results)) << Results.takeError();
|
|
|
|
EXPECT_THAT(
|
|
|
|
applyEdits(std::move(*Results)),
|
|
|
|
UnorderedElementsAre(
|
|
|
|
Pair(Eq(BarPath), Eq(expectedResult(BarCode, NewName))),
|
|
|
|
Pair(Eq(MainFilePath), Eq(expectedResult(MainCode, NewName)))));
|
|
|
|
}
|
|
|
|
|
2019-12-05 12:08:31 +01:00
|
|
|
TEST(CrossFileRenameTests, WithUpToDateIndex) {
|
|
|
|
MockCompilationDatabase CDB;
|
|
|
|
CDB.ExtraClangFlags = {"-xc++"};
|
2020-01-20 14:14:52 +01:00
|
|
|
// rename is runnning on all "^" points in FooH, and "[[]]" ranges are the
|
2019-12-12 13:10:59 +01:00
|
|
|
// expected rename occurrences.
|
2019-12-05 12:08:31 +01:00
|
|
|
struct Case {
|
|
|
|
llvm::StringRef FooH;
|
|
|
|
llvm::StringRef FooCC;
|
2019-12-12 13:10:59 +01:00
|
|
|
} Cases[] = {
|
|
|
|
{
|
|
|
|
// classes.
|
|
|
|
R"cpp(
|
2019-12-05 12:08:31 +01:00
|
|
|
class [[Fo^o]] {
|
|
|
|
[[Foo]]();
|
|
|
|
~[[Foo]]();
|
|
|
|
};
|
|
|
|
)cpp",
|
2019-12-12 13:10:59 +01:00
|
|
|
R"cpp(
|
2019-12-05 12:08:31 +01:00
|
|
|
#include "foo.h"
|
|
|
|
[[Foo]]::[[Foo]]() {}
|
|
|
|
[[Foo]]::~[[Foo]]() {}
|
|
|
|
|
|
|
|
void func() {
|
|
|
|
[[Foo]] foo;
|
|
|
|
}
|
|
|
|
)cpp",
|
2019-12-12 13:10:59 +01:00
|
|
|
},
|
|
|
|
{
|
|
|
|
// class methods.
|
|
|
|
R"cpp(
|
2019-12-05 12:08:31 +01:00
|
|
|
class Foo {
|
|
|
|
void [[f^oo]]();
|
|
|
|
};
|
|
|
|
)cpp",
|
2019-12-12 13:10:59 +01:00
|
|
|
R"cpp(
|
2019-12-05 12:08:31 +01:00
|
|
|
#include "foo.h"
|
|
|
|
void Foo::[[foo]]() {}
|
|
|
|
|
|
|
|
void func(Foo* p) {
|
|
|
|
p->[[foo]]();
|
|
|
|
}
|
|
|
|
)cpp",
|
2019-12-12 13:10:59 +01:00
|
|
|
},
|
|
|
|
{
|
2020-01-20 14:14:52 +01:00
|
|
|
// rename on constructor and destructor.
|
2019-12-12 13:10:59 +01:00
|
|
|
R"cpp(
|
|
|
|
class [[Foo]] {
|
|
|
|
[[^Foo]]();
|
|
|
|
~[[Foo^]]();
|
|
|
|
};
|
|
|
|
)cpp",
|
|
|
|
R"cpp(
|
|
|
|
#include "foo.h"
|
|
|
|
[[Foo]]::[[Foo]]() {}
|
|
|
|
[[Foo]]::~[[Foo]]() {}
|
|
|
|
|
|
|
|
void func() {
|
|
|
|
[[Foo]] foo;
|
|
|
|
}
|
|
|
|
)cpp",
|
|
|
|
},
|
|
|
|
{
|
|
|
|
// functions.
|
|
|
|
R"cpp(
|
2019-12-05 12:08:31 +01:00
|
|
|
void [[f^oo]]();
|
|
|
|
)cpp",
|
2019-12-12 13:10:59 +01:00
|
|
|
R"cpp(
|
2019-12-05 12:08:31 +01:00
|
|
|
#include "foo.h"
|
|
|
|
void [[foo]]() {}
|
|
|
|
|
|
|
|
void func() {
|
|
|
|
[[foo]]();
|
|
|
|
}
|
|
|
|
)cpp",
|
2019-12-12 13:10:59 +01:00
|
|
|
},
|
|
|
|
{
|
|
|
|
// typedefs.
|
|
|
|
R"cpp(
|
2019-12-05 12:08:31 +01:00
|
|
|
typedef int [[IN^T]];
|
|
|
|
[[INT]] foo();
|
|
|
|
)cpp",
|
2019-12-12 13:10:59 +01:00
|
|
|
R"cpp(
|
2019-12-05 12:08:31 +01:00
|
|
|
#include "foo.h"
|
|
|
|
[[INT]] foo() {}
|
|
|
|
)cpp",
|
2019-12-12 13:10:59 +01:00
|
|
|
},
|
|
|
|
{
|
|
|
|
// usings.
|
|
|
|
R"cpp(
|
2019-12-05 12:08:31 +01:00
|
|
|
using [[I^NT]] = int;
|
|
|
|
[[INT]] foo();
|
|
|
|
)cpp",
|
2019-12-12 13:10:59 +01:00
|
|
|
R"cpp(
|
2019-12-05 12:08:31 +01:00
|
|
|
#include "foo.h"
|
|
|
|
[[INT]] foo() {}
|
|
|
|
)cpp",
|
2019-12-12 13:10:59 +01:00
|
|
|
},
|
|
|
|
{
|
|
|
|
// variables.
|
|
|
|
R"cpp(
|
2020-02-06 08:18:14 +01:00
|
|
|
static const int [[VA^R]] = 123;
|
2019-12-05 12:08:31 +01:00
|
|
|
)cpp",
|
2019-12-12 13:10:59 +01:00
|
|
|
R"cpp(
|
2019-12-05 12:08:31 +01:00
|
|
|
#include "foo.h"
|
|
|
|
int s = [[VAR]];
|
|
|
|
)cpp",
|
2019-12-12 13:10:59 +01:00
|
|
|
},
|
|
|
|
{
|
|
|
|
// scope enums.
|
|
|
|
R"cpp(
|
2019-12-05 12:08:31 +01:00
|
|
|
enum class [[K^ind]] { ABC };
|
|
|
|
)cpp",
|
2019-12-12 13:10:59 +01:00
|
|
|
R"cpp(
|
2019-12-05 12:08:31 +01:00
|
|
|
#include "foo.h"
|
|
|
|
[[Kind]] ff() {
|
|
|
|
return [[Kind]]::ABC;
|
|
|
|
}
|
|
|
|
)cpp",
|
2019-12-12 13:10:59 +01:00
|
|
|
},
|
|
|
|
{
|
|
|
|
// enum constants.
|
|
|
|
R"cpp(
|
2019-12-05 12:08:31 +01:00
|
|
|
enum class Kind { [[A^BC]] };
|
|
|
|
)cpp",
|
2019-12-12 13:10:59 +01:00
|
|
|
R"cpp(
|
2019-12-05 12:08:31 +01:00
|
|
|
#include "foo.h"
|
|
|
|
Kind ff() {
|
|
|
|
return Kind::[[ABC]];
|
|
|
|
}
|
|
|
|
)cpp",
|
2019-12-12 13:10:59 +01:00
|
|
|
},
|
2020-02-06 11:41:17 +01:00
|
|
|
{
|
|
|
|
// 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",
|
|
|
|
},
|
2019-12-05 12:08:31 +01:00
|
|
|
};
|
|
|
|
|
|
|
|
for (const auto& T : Cases) {
|
2020-01-27 14:09:27 +01:00
|
|
|
SCOPED_TRACE(T.FooH);
|
2019-12-05 12:08:31 +01:00
|
|
|
Annotations FooH(T.FooH);
|
|
|
|
Annotations FooCC(T.FooCC);
|
|
|
|
std::string FooHPath = testPath("foo.h");
|
|
|
|
std::string FooCCPath = testPath("foo.cc");
|
|
|
|
|
|
|
|
MockFSProvider FS;
|
2020-01-28 20:23:46 +01:00
|
|
|
FS.Files[FooHPath] = std::string(FooH.code());
|
|
|
|
FS.Files[FooCCPath] = std::string(FooCC.code());
|
2019-12-05 12:08:31 +01:00
|
|
|
|
|
|
|
auto ServerOpts = ClangdServer::optsForTest();
|
|
|
|
ServerOpts.CrossFileRename = true;
|
|
|
|
ServerOpts.BuildDynamicSymbolIndex = true;
|
2020-01-24 14:08:56 +01:00
|
|
|
ClangdServer Server(CDB, FS, ServerOpts);
|
2019-12-05 12:08:31 +01:00
|
|
|
|
|
|
|
// 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";
|
2020-01-20 14:14:52 +01:00
|
|
|
for (const auto &RenamePos : FooH.points()) {
|
|
|
|
auto FileEditsList =
|
|
|
|
llvm::cantFail(runRename(Server, FooHPath, RenamePos, NewName));
|
|
|
|
EXPECT_THAT(
|
|
|
|
applyEdits(std::move(FileEditsList)),
|
|
|
|
UnorderedElementsAre(
|
|
|
|
Pair(Eq(FooHPath), Eq(expectedResult(T.FooH, NewName))),
|
|
|
|
Pair(Eq(FooCCPath), Eq(expectedResult(T.FooCC, NewName)))));
|
|
|
|
}
|
2019-12-05 12:08:31 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-10-23 14:40:20 +02:00
|
|
|
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)),
|
|
|
|
UnorderedElementsAre(Pair(Eq(Path), Eq(expectedResult(Code, NewName)))));
|
2019-11-19 10:10:43 +01:00
|
|
|
}
|
|
|
|
|
2019-11-19 15:23:36 +01:00
|
|
|
TEST(CrossFileRenameTests, BuildRenameEdits) {
|
|
|
|
Annotations Code("[[😂]]");
|
|
|
|
auto LSPRange = Code.range();
|
2019-11-28 16:48:49 +01:00
|
|
|
llvm::StringRef FilePath = "/test/TestTU.cpp";
|
2019-12-10 21:13:36 +01:00
|
|
|
llvm::StringRef NewName = "abc";
|
|
|
|
auto Edit = buildRenameEdit(FilePath, Code.code(), {LSPRange}, NewName);
|
2019-11-19 15:23:36 +01:00
|
|
|
ASSERT_TRUE(bool(Edit)) << Edit.takeError();
|
|
|
|
ASSERT_EQ(1UL, Edit->Replacements.size());
|
2019-11-28 16:48:49 +01:00
|
|
|
EXPECT_EQ(FilePath, Edit->Replacements.begin()->getFilePath());
|
2019-11-19 15:23:36 +01:00
|
|
|
EXPECT_EQ(4UL, Edit->Replacements.begin()->getLength());
|
|
|
|
|
|
|
|
// Test invalid range.
|
|
|
|
LSPRange.end = {10, 0}; // out of range
|
2019-12-10 21:13:36 +01:00
|
|
|
Edit = buildRenameEdit(FilePath, Code.code(), {LSPRange}, NewName);
|
2019-11-19 15:23:36 +01:00
|
|
|
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");
|
2019-12-10 21:13:36 +01:00
|
|
|
Edit = buildRenameEdit(FilePath, T.code(), T.ranges(), NewName);
|
2019-11-19 15:23:36 +01:00
|
|
|
ASSERT_TRUE(bool(Edit)) << Edit.takeError();
|
|
|
|
EXPECT_EQ(applyEdits(FileEdits{{T.code(), std::move(*Edit)}}).front().second,
|
2019-12-10 21:13:36 +01:00
|
|
|
expectedResult(T, NewName));
|
2019-11-19 15:23:36 +01:00
|
|
|
}
|
|
|
|
|
2019-12-09 17:00:51 +01:00
|
|
|
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) {
|
2020-01-27 14:09:27 +01:00
|
|
|
SCOPED_TRACE(T.DraftCode);
|
2019-12-09 17:00:51 +01:00
|
|
|
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(),
|
2020-01-27 14:09:27 +01:00
|
|
|
testing::UnorderedElementsAreArray(*ActualRanges));
|
2019-12-09 17:00:51 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
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) {
|
2020-01-27 14:09:27 +01:00
|
|
|
SCOPED_TRACE(T.IndexedCode);
|
2019-12-09 17:00:51 +01:00
|
|
|
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
|
2020-01-27 14:09:27 +01:00
|
|
|
EXPECT_THAT(ExpectedMatches, UnorderedElementsAreArray(*Mapped));
|
2019-12-09 17:00:51 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
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) {
|
2020-01-27 14:09:27 +01:00
|
|
|
SCOPED_TRACE(T.RangeCode);
|
2019-12-09 17:00:51 +01:00
|
|
|
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),
|
2020-01-27 14:09:27 +01:00
|
|
|
T.ExpectedCost);
|
2019-12-09 17:00:51 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-05-07 07:11:56 +00:00
|
|
|
} // namespace
|
|
|
|
} // namespace clangd
|
|
|
|
} // namespace clang
|