mirror of
https://github.com/Gericom/teak-llvm.git
synced 2025-06-27 07:19:03 -04:00

This commit changes the way that the refactoring operation classes are structured: - Users have to call `initiate` instead of constructing an instance of the class. The `initiate` is now supposed to have custom initiation logic, and you don't need to subclass the builtin requirements. - A new `describe` function returns a structure with the id, title and the description of the refactoring operation. The refactoring action classes are now placed into one common place in RefactoringActions.cpp instead of being separate. Differential Revision: https://reviews.llvm.org/D38985 llvm-svn: 316780
249 lines
8.4 KiB
C++
249 lines
8.4 KiB
C++
//===- unittest/Tooling/RefactoringTestActionRulesTest.cpp ----------------===//
|
|
//
|
|
// The LLVM Compiler Infrastructure
|
|
//
|
|
// This file is distributed under the University of Illinois Open Source
|
|
// License. See LICENSE.TXT for details.
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
#include "ReplacementTest.h"
|
|
#include "RewriterTestContext.h"
|
|
#include "clang/Tooling/Refactoring.h"
|
|
#include "clang/Tooling/Refactoring/Extract/Extract.h"
|
|
#include "clang/Tooling/Refactoring/RefactoringAction.h"
|
|
#include "clang/Tooling/Refactoring/RefactoringDiagnostic.h"
|
|
#include "clang/Tooling/Refactoring/Rename/SymbolName.h"
|
|
#include "clang/Tooling/Tooling.h"
|
|
#include "llvm/Support/Errc.h"
|
|
#include "gtest/gtest.h"
|
|
|
|
using namespace clang;
|
|
using namespace tooling;
|
|
|
|
namespace {
|
|
|
|
class RefactoringActionRulesTest : public ::testing::Test {
|
|
protected:
|
|
void SetUp() override {
|
|
Context.Sources.setMainFileID(
|
|
Context.createInMemoryFile("input.cpp", DefaultCode));
|
|
}
|
|
|
|
RewriterTestContext Context;
|
|
std::string DefaultCode = std::string(100, 'a');
|
|
};
|
|
|
|
Expected<AtomicChanges>
|
|
createReplacements(const std::unique_ptr<RefactoringActionRule> &Rule,
|
|
RefactoringRuleContext &Context) {
|
|
class Consumer final : public RefactoringResultConsumer {
|
|
void handleError(llvm::Error Err) override { Result = std::move(Err); }
|
|
|
|
void handle(AtomicChanges SourceReplacements) override {
|
|
Result = std::move(SourceReplacements);
|
|
}
|
|
void handle(SymbolOccurrences Occurrences) override {
|
|
RefactoringResultConsumer::handle(std::move(Occurrences));
|
|
}
|
|
|
|
public:
|
|
Optional<Expected<AtomicChanges>> Result;
|
|
};
|
|
|
|
Consumer C;
|
|
Rule->invoke(C, Context);
|
|
return std::move(*C.Result);
|
|
}
|
|
|
|
TEST_F(RefactoringActionRulesTest, MyFirstRefactoringRule) {
|
|
class ReplaceAWithB : public SourceChangeRefactoringRule {
|
|
std::pair<SourceRange, int> Selection;
|
|
|
|
public:
|
|
ReplaceAWithB(std::pair<SourceRange, int> Selection)
|
|
: Selection(Selection) {}
|
|
|
|
static Expected<ReplaceAWithB>
|
|
initiate(RefactoringRuleContext &Cotnext,
|
|
std::pair<SourceRange, int> Selection) {
|
|
return ReplaceAWithB(Selection);
|
|
}
|
|
|
|
Expected<AtomicChanges>
|
|
createSourceReplacements(RefactoringRuleContext &Context) {
|
|
const SourceManager &SM = Context.getSources();
|
|
SourceLocation Loc =
|
|
Selection.first.getBegin().getLocWithOffset(Selection.second);
|
|
AtomicChange Change(SM, Loc);
|
|
llvm::Error E = Change.replace(SM, Loc, 1, "b");
|
|
if (E)
|
|
return std::move(E);
|
|
return AtomicChanges{Change};
|
|
}
|
|
};
|
|
|
|
class SelectionRequirement : public SourceRangeSelectionRequirement {
|
|
public:
|
|
Expected<std::pair<SourceRange, int>>
|
|
evaluate(RefactoringRuleContext &Context) const {
|
|
Expected<SourceRange> R =
|
|
SourceRangeSelectionRequirement::evaluate(Context);
|
|
if (!R)
|
|
return R.takeError();
|
|
return std::make_pair(*R, 20);
|
|
}
|
|
};
|
|
auto Rule =
|
|
createRefactoringActionRule<ReplaceAWithB>(SelectionRequirement());
|
|
|
|
// When the requirements are satisifed, the rule's function must be invoked.
|
|
{
|
|
RefactoringRuleContext RefContext(Context.Sources);
|
|
SourceLocation Cursor =
|
|
Context.Sources.getLocForStartOfFile(Context.Sources.getMainFileID())
|
|
.getLocWithOffset(10);
|
|
RefContext.setSelectionRange({Cursor, Cursor});
|
|
|
|
Expected<AtomicChanges> ErrorOrResult =
|
|
createReplacements(Rule, RefContext);
|
|
ASSERT_FALSE(!ErrorOrResult);
|
|
AtomicChanges Result = std::move(*ErrorOrResult);
|
|
ASSERT_EQ(Result.size(), 1u);
|
|
std::string YAMLString =
|
|
const_cast<AtomicChange &>(Result[0]).toYAMLString();
|
|
|
|
ASSERT_STREQ("---\n"
|
|
"Key: 'input.cpp:30'\n"
|
|
"FilePath: input.cpp\n"
|
|
"Error: ''\n"
|
|
"InsertedHeaders: \n"
|
|
"RemovedHeaders: \n"
|
|
"Replacements: \n" // Extra whitespace here!
|
|
" - FilePath: input.cpp\n"
|
|
" Offset: 30\n"
|
|
" Length: 1\n"
|
|
" ReplacementText: b\n"
|
|
"...\n",
|
|
YAMLString.c_str());
|
|
}
|
|
|
|
// When one of the requirements is not satisfied, invoke should return a
|
|
// valid error.
|
|
{
|
|
RefactoringRuleContext RefContext(Context.Sources);
|
|
Expected<AtomicChanges> ErrorOrResult =
|
|
createReplacements(Rule, RefContext);
|
|
|
|
ASSERT_TRUE(!ErrorOrResult);
|
|
unsigned DiagID;
|
|
llvm::handleAllErrors(ErrorOrResult.takeError(),
|
|
[&](DiagnosticError &Error) {
|
|
DiagID = Error.getDiagnostic().second.getDiagID();
|
|
});
|
|
EXPECT_EQ(DiagID, diag::err_refactor_no_selection);
|
|
}
|
|
}
|
|
|
|
TEST_F(RefactoringActionRulesTest, ReturnError) {
|
|
class ErrorRule : public SourceChangeRefactoringRule {
|
|
public:
|
|
static Expected<ErrorRule> initiate(RefactoringRuleContext &,
|
|
SourceRange R) {
|
|
return ErrorRule(R);
|
|
}
|
|
|
|
ErrorRule(SourceRange R) {}
|
|
Expected<AtomicChanges> createSourceReplacements(RefactoringRuleContext &) {
|
|
return llvm::make_error<llvm::StringError>(
|
|
"Error", llvm::make_error_code(llvm::errc::invalid_argument));
|
|
}
|
|
};
|
|
|
|
auto Rule =
|
|
createRefactoringActionRule<ErrorRule>(SourceRangeSelectionRequirement());
|
|
RefactoringRuleContext RefContext(Context.Sources);
|
|
SourceLocation Cursor =
|
|
Context.Sources.getLocForStartOfFile(Context.Sources.getMainFileID());
|
|
RefContext.setSelectionRange({Cursor, Cursor});
|
|
Expected<AtomicChanges> Result = createReplacements(Rule, RefContext);
|
|
|
|
ASSERT_TRUE(!Result);
|
|
std::string Message;
|
|
llvm::handleAllErrors(Result.takeError(), [&](llvm::StringError &Error) {
|
|
Message = Error.getMessage();
|
|
});
|
|
EXPECT_EQ(Message, "Error");
|
|
}
|
|
|
|
Optional<SymbolOccurrences> findOccurrences(RefactoringActionRule &Rule,
|
|
RefactoringRuleContext &Context) {
|
|
class Consumer final : public RefactoringResultConsumer {
|
|
void handleError(llvm::Error) override {}
|
|
void handle(SymbolOccurrences Occurrences) override {
|
|
Result = std::move(Occurrences);
|
|
}
|
|
void handle(AtomicChanges Changes) override {
|
|
RefactoringResultConsumer::handle(std::move(Changes));
|
|
}
|
|
|
|
public:
|
|
Optional<SymbolOccurrences> Result;
|
|
};
|
|
|
|
Consumer C;
|
|
Rule.invoke(C, Context);
|
|
return std::move(C.Result);
|
|
}
|
|
|
|
TEST_F(RefactoringActionRulesTest, ReturnSymbolOccurrences) {
|
|
class FindOccurrences : public FindSymbolOccurrencesRefactoringRule {
|
|
SourceRange Selection;
|
|
|
|
public:
|
|
FindOccurrences(SourceRange Selection) : Selection(Selection) {}
|
|
|
|
static Expected<FindOccurrences> initiate(RefactoringRuleContext &,
|
|
SourceRange Selection) {
|
|
return FindOccurrences(Selection);
|
|
}
|
|
|
|
Expected<SymbolOccurrences>
|
|
findSymbolOccurrences(RefactoringRuleContext &) override {
|
|
SymbolOccurrences Occurrences;
|
|
Occurrences.push_back(SymbolOccurrence(SymbolName("test"),
|
|
SymbolOccurrence::MatchingSymbol,
|
|
Selection.getBegin()));
|
|
return std::move(Occurrences);
|
|
}
|
|
};
|
|
|
|
auto Rule = createRefactoringActionRule<FindOccurrences>(
|
|
SourceRangeSelectionRequirement());
|
|
|
|
RefactoringRuleContext RefContext(Context.Sources);
|
|
SourceLocation Cursor =
|
|
Context.Sources.getLocForStartOfFile(Context.Sources.getMainFileID());
|
|
RefContext.setSelectionRange({Cursor, Cursor});
|
|
Optional<SymbolOccurrences> Result = findOccurrences(*Rule, RefContext);
|
|
|
|
ASSERT_FALSE(!Result);
|
|
SymbolOccurrences Occurrences = std::move(*Result);
|
|
EXPECT_EQ(Occurrences.size(), 1u);
|
|
EXPECT_EQ(Occurrences[0].getKind(), SymbolOccurrence::MatchingSymbol);
|
|
EXPECT_EQ(Occurrences[0].getNameRanges().size(), 1u);
|
|
EXPECT_EQ(Occurrences[0].getNameRanges()[0],
|
|
SourceRange(Cursor, Cursor.getLocWithOffset(strlen("test"))));
|
|
}
|
|
|
|
TEST_F(RefactoringActionRulesTest, EditorCommandBinding) {
|
|
const RefactoringDescriptor &Descriptor = ExtractFunction::describe();
|
|
EXPECT_EQ(Descriptor.Name, "extract-function");
|
|
EXPECT_EQ(
|
|
Descriptor.Description,
|
|
"(WIP action; use with caution!) Extracts code into a new function");
|
|
EXPECT_EQ(Descriptor.Title, "Extract Function");
|
|
}
|
|
|
|
} // end anonymous namespace
|