forked from OSchip/llvm-project
[refactor] add clang-refactor tool with initial testing support and
local-rename action This commit introduces the clang-refactor tool alongside the local-rename action which uses the existing renaming engine used by clang-rename. The tool doesn't actually perform the source transformations yet, it just provides testing support. This commit also moves only one test from clang-rename over to test/Refactor. I will continue to move the other tests throughout development of clang-refactor. The following options are supported by clang-refactor: -v: use verbose output -selection: The source range that corresponds to the portion of the source that's selected (currently only special command test:<file> is supported). Please note that a follow-up commit will migrate clang-refactor to libTooling's common option parser, so clang-refactor will be able to use the common interface with compilation database and options like -p, -extra-arg, etc. The testing support provided by clang-refactor is described below: When -selection=test:<file> is given, clang-refactor will parse the selection commands from that file. The selection commands are grouped and the specified refactoring action invoked by the tool. Each command in a group is expected to produce an identical result. The precise syntax for the selection commands is described in a comment in TestSupport.h. Differential Revision: https://reviews.llvm.org/D36574 llvm-svn: 313244
This commit is contained in:
parent
d7b3add7c4
commit
b54ef6a2a4
|
@ -52,6 +52,8 @@ public:
|
|||
AtomicChange &operator=(AtomicChange &&) = default;
|
||||
AtomicChange &operator=(const AtomicChange &) = default;
|
||||
|
||||
bool operator==(const AtomicChange &Other) const;
|
||||
|
||||
/// \brief Returns the atomic change as a YAML string.
|
||||
std::string toYAMLString();
|
||||
|
||||
|
|
|
@ -0,0 +1,64 @@
|
|||
//===--- RefactoringAction.h - Clang refactoring library ------------------===//
|
||||
//
|
||||
// The LLVM Compiler Infrastructure
|
||||
//
|
||||
// This file is distributed under the University of Illinois Open Source
|
||||
// License. See LICENSE.TXT for details.
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
#ifndef LLVM_CLANG_TOOLING_REFACTOR_REFACTORING_ACTION_H
|
||||
#define LLVM_CLANG_TOOLING_REFACTOR_REFACTORING_ACTION_H
|
||||
|
||||
#include "clang/Basic/LLVM.h"
|
||||
#include "clang/Tooling/Refactoring/RefactoringActionRules.h"
|
||||
#include <vector>
|
||||
|
||||
namespace clang {
|
||||
namespace tooling {
|
||||
|
||||
/// A refactoring action is a class that defines a set of related refactoring
|
||||
/// action rules. These rules get grouped under a common umbrella - a single
|
||||
/// clang-refactor subcommand.
|
||||
///
|
||||
/// A subclass of \c RefactoringAction is responsible for creating the set of
|
||||
/// grouped refactoring action rules that represent one refactoring operation.
|
||||
/// Although the rules in one action may have a number of different
|
||||
/// implementations, they should strive to produce a similar result. It should
|
||||
/// be easy for users to identify which refactoring action produced the result
|
||||
/// regardless of which refactoring action rule was used.
|
||||
///
|
||||
/// The distinction between actions and rules enables the creation of action
|
||||
/// that uses very different rules, for example:
|
||||
/// - local vs global: a refactoring operation like
|
||||
/// "add missing switch cases" can be applied to one switch when it's
|
||||
/// selected in an editor, or to all switches in a project when an enum
|
||||
/// constant is added to an enum.
|
||||
/// - tool vs editor: some refactoring operation can be initiated in the
|
||||
/// editor when a declaration is selected, or in a tool when the name of
|
||||
/// the declaration is passed using a command-line argument.
|
||||
class RefactoringAction {
|
||||
public:
|
||||
virtual ~RefactoringAction() {}
|
||||
|
||||
/// Returns the name of the subcommand that's used by clang-refactor for this
|
||||
/// action.
|
||||
virtual StringRef getCommand() const = 0;
|
||||
|
||||
virtual StringRef getDescription() const = 0;
|
||||
|
||||
RefactoringActionRules createActiveActionRules();
|
||||
|
||||
protected:
|
||||
/// Returns a set of refactoring actions rules that are defined by this
|
||||
/// action.
|
||||
virtual RefactoringActionRules createActionRules() const = 0;
|
||||
};
|
||||
|
||||
/// Returns the list of all the available refactoring actions.
|
||||
std::vector<std::unique_ptr<RefactoringAction>> createRefactoringActions();
|
||||
|
||||
} // end namespace tooling
|
||||
} // end namespace clang
|
||||
|
||||
#endif // LLVM_CLANG_TOOLING_REFACTOR_REFACTORING_ACTION_H
|
|
@ -0,0 +1,7 @@
|
|||
#ifndef REFACTORING_ACTION
|
||||
#define REFACTORING_ACTION(Name)
|
||||
#endif
|
||||
|
||||
REFACTORING_ACTION(LocalRename)
|
||||
|
||||
#undef REFACTORING_ACTION
|
|
@ -30,6 +30,10 @@ public:
|
|||
/// consumer to propagate the result of the refactoring action.
|
||||
virtual void invoke(RefactoringResultConsumer &Consumer,
|
||||
RefactoringRuleContext &Context) = 0;
|
||||
|
||||
/// Returns true when the rule has a source selection requirement that has
|
||||
/// to be fullfilled before refactoring can be performed.
|
||||
virtual bool hasSelectionRequirement() = 0;
|
||||
};
|
||||
|
||||
/// A set of refactoring action rules that should have unique initiation
|
||||
|
|
|
@ -58,7 +58,7 @@ struct SourceSelectionRequirement
|
|||
Optional<InputT> Value = InputT::evaluate(Context);
|
||||
if (!Value)
|
||||
return None;
|
||||
return std::move(Requirement.evaluateSelection(*Value));
|
||||
return std::move(Requirement.evaluateSelection(Context, *Value));
|
||||
}
|
||||
|
||||
private:
|
||||
|
@ -82,6 +82,20 @@ struct IsRequirement<T>
|
|||
: std::conditional<std::is_base_of<internal::RequirementBase, T>::value,
|
||||
std::true_type, std::false_type>::type {};
|
||||
|
||||
/// A type trait that returns true when the given type has at least one source
|
||||
/// selection requirement.
|
||||
template <typename First, typename... Rest>
|
||||
struct HasSelectionRequirement
|
||||
: std::conditional<HasSelectionRequirement<First>::value ||
|
||||
HasSelectionRequirement<Rest...>::value,
|
||||
std::true_type, std::false_type>::type {};
|
||||
|
||||
template <typename I, typename O, typename R>
|
||||
struct HasSelectionRequirement<internal::SourceSelectionRequirement<I, O, R>>
|
||||
: std::true_type {};
|
||||
|
||||
template <typename T> struct HasSelectionRequirement<T> : std::false_type {};
|
||||
|
||||
} // end namespace traits
|
||||
} // end namespace refactoring_action_rules
|
||||
} // end namespace tooling
|
||||
|
|
|
@ -15,6 +15,9 @@
|
|||
|
||||
namespace clang {
|
||||
namespace tooling {
|
||||
|
||||
class RefactoringRuleContext;
|
||||
|
||||
namespace refactoring_action_rules {
|
||||
|
||||
/// Creates a new refactoring action rule that invokes the given function once
|
||||
|
@ -40,6 +43,7 @@ namespace refactoring_action_rules {
|
|||
template <typename ResultType, typename... RequirementTypes>
|
||||
std::unique_ptr<RefactoringActionRule>
|
||||
createRefactoringRule(Expected<ResultType> (*RefactoringFunction)(
|
||||
const RefactoringRuleContext &,
|
||||
typename RequirementTypes::OutputType...),
|
||||
const RequirementTypes &... Requirements) {
|
||||
static_assert(tooling::traits::IsValidRefactoringResult<ResultType>::value,
|
||||
|
@ -56,13 +60,12 @@ template <
|
|||
typename Fn = decltype(&Callable::operator()),
|
||||
typename ResultType = typename internal::LambdaDeducer<Fn>::ReturnType,
|
||||
bool IsNonCapturingLambda = std::is_convertible<
|
||||
Callable,
|
||||
ResultType (*)(typename RequirementTypes::OutputType...)>::value,
|
||||
Callable, typename internal::LambdaDeducer<Fn>::FunctionType>::value,
|
||||
typename = typename std::enable_if<IsNonCapturingLambda>::type>
|
||||
std::unique_ptr<RefactoringActionRule>
|
||||
createRefactoringRule(const Callable &C,
|
||||
const RequirementTypes &... Requirements) {
|
||||
ResultType (*Func)(typename RequirementTypes::OutputType...) = C;
|
||||
typename internal::LambdaDeducer<Fn>::FunctionType Func = C;
|
||||
return createRefactoringRule(Func, Requirements...);
|
||||
}
|
||||
|
||||
|
|
|
@ -39,6 +39,10 @@ public:
|
|||
llvm::index_sequence_for<RequirementTypes...>());
|
||||
}
|
||||
|
||||
bool hasSelectionRequirement() override {
|
||||
return traits::HasSelectionRequirement<RequirementTypes...>::value;
|
||||
}
|
||||
|
||||
private:
|
||||
/// Returns \c T when given \c Expected<Optional<T>>, or \c T otherwise.
|
||||
template <typename T>
|
||||
|
@ -79,7 +83,7 @@ private:
|
|||
template <size_t... Is>
|
||||
void invokeImpl(RefactoringResultConsumer &Consumer,
|
||||
RefactoringRuleContext &Context,
|
||||
llvm::index_sequence<Is...>) {
|
||||
llvm::index_sequence<Is...> Seq) {
|
||||
// Initiate the operation.
|
||||
auto Values =
|
||||
std::make_tuple(std::get<Is>(Requirements).evaluate(Context)...);
|
||||
|
@ -96,8 +100,8 @@ private:
|
|||
return Consumer.handleError(std::move(Error));
|
||||
}
|
||||
// Perform the operation.
|
||||
auto Result =
|
||||
Function(unwrapRequirementResult(std::move(std::get<Is>(Values)))...);
|
||||
auto Result = Function(
|
||||
Context, unwrapRequirementResult(std::move(std::get<Is>(Values)))...);
|
||||
if (!Result)
|
||||
return Consumer.handleError(Result.takeError());
|
||||
Consumer.handle(std::move(*Result));
|
||||
|
@ -111,8 +115,9 @@ private:
|
|||
/// createRefactoringRule.
|
||||
template <typename T> struct LambdaDeducer;
|
||||
template <typename T, typename R, typename... Args>
|
||||
struct LambdaDeducer<R (T::*)(Args...) const> {
|
||||
struct LambdaDeducer<R (T::*)(const RefactoringRuleContext &, Args...) const> {
|
||||
using ReturnType = R;
|
||||
using FunctionType = R (*)(const RefactoringRuleContext &, Args...);
|
||||
};
|
||||
|
||||
} // end namespace internal
|
||||
|
|
|
@ -13,6 +13,9 @@
|
|||
#include "clang/Basic/SourceManager.h"
|
||||
|
||||
namespace clang {
|
||||
|
||||
class ASTContext;
|
||||
|
||||
namespace tooling {
|
||||
|
||||
/// The refactoring rule context stores all of the inputs that might be needed
|
||||
|
@ -38,6 +41,15 @@ public:
|
|||
|
||||
void setSelectionRange(SourceRange R) { SelectionRange = R; }
|
||||
|
||||
bool hasASTContext() const { return AST; }
|
||||
|
||||
ASTContext &getASTContext() const {
|
||||
assert(AST && "no AST!");
|
||||
return *AST;
|
||||
}
|
||||
|
||||
void setASTContext(ASTContext &Context) { AST = &Context; }
|
||||
|
||||
private:
|
||||
/// The source manager for the translation unit / file on which a refactoring
|
||||
/// action might operate on.
|
||||
|
@ -45,6 +57,9 @@ private:
|
|||
/// An optional source selection range that's commonly used to represent
|
||||
/// a selection in an editor.
|
||||
SourceRange SelectionRange;
|
||||
/// An optional AST for the translation unit on which a refactoring action
|
||||
/// might operate on.
|
||||
ASTContext *AST = nullptr;
|
||||
};
|
||||
|
||||
} // end namespace tooling
|
||||
|
|
|
@ -23,6 +23,7 @@
|
|||
|
||||
namespace clang {
|
||||
class ASTConsumer;
|
||||
class ASTContext;
|
||||
class CompilerInstance;
|
||||
class NamedDecl;
|
||||
|
||||
|
@ -37,6 +38,10 @@ namespace tooling {
|
|||
/// - A destructor is canonicalized to its class.
|
||||
const NamedDecl *getCanonicalSymbolDeclaration(const NamedDecl *FoundDecl);
|
||||
|
||||
/// Returns the set of USRs that correspond to the given declaration.
|
||||
std::vector<std::string> getUSRsForDeclaration(const NamedDecl *ND,
|
||||
ASTContext &Context);
|
||||
|
||||
struct USRFindingAction {
|
||||
USRFindingAction(ArrayRef<unsigned> SymbolOffsets,
|
||||
ArrayRef<std::string> QualifiedNames, bool Force)
|
||||
|
|
|
@ -12,6 +12,7 @@
|
|||
|
||||
#include "clang/Basic/LLVM.h"
|
||||
#include "clang/Basic/SourceLocation.h"
|
||||
#include "clang/Tooling/Refactoring/RefactoringRuleContext.h"
|
||||
#include <type_traits>
|
||||
|
||||
namespace clang {
|
||||
|
@ -40,9 +41,10 @@ private:
|
|||
|
||||
/// A custom selection requirement.
|
||||
class Requirement {
|
||||
/// Subclasses must implement 'T evaluateSelection(SelectionConstraint) const'
|
||||
/// member function. \c T is used to determine the return type that is
|
||||
/// passed to the refactoring rule's function.
|
||||
/// Subclasses must implement
|
||||
/// 'T evaluateSelection(const RefactoringRuleContext &,
|
||||
/// SelectionConstraint) const' member function. \c T is used to determine
|
||||
/// the return type that is passed to the refactoring rule's function.
|
||||
/// If T is \c DiagnosticOr<S> , then \c S is passed to the rule's function
|
||||
/// using move semantics.
|
||||
/// Otherwise, T is passed to the function directly using move semantics.
|
||||
|
@ -64,14 +66,17 @@ namespace internal {
|
|||
template <typename T> struct EvaluateSelectionChecker : std::false_type {};
|
||||
|
||||
template <typename T, typename R, typename A>
|
||||
struct EvaluateSelectionChecker<R (T::*)(A) const> : std::true_type {
|
||||
struct EvaluateSelectionChecker<R (T::*)(const RefactoringRuleContext &, A)
|
||||
const> : std::true_type {
|
||||
using ReturnType = R;
|
||||
using ArgType = A;
|
||||
};
|
||||
|
||||
template <typename T> class Identity : public Requirement {
|
||||
public:
|
||||
T evaluateSelection(T Value) const { return std::move(Value); }
|
||||
T evaluateSelection(const RefactoringRuleContext &, T Value) const {
|
||||
return std::move(Value);
|
||||
}
|
||||
};
|
||||
|
||||
} // end namespace internal
|
||||
|
|
|
@ -138,6 +138,8 @@ module Clang_Tooling {
|
|||
// importing the AST matchers library gives a link dependency on the AST
|
||||
// matchers (and thus the AST), which clang-format should not have.
|
||||
exclude header "Tooling/RefactoringCallbacks.h"
|
||||
|
||||
textual header "Tooling/Refactoring/RefactoringActionRegistry.def"
|
||||
}
|
||||
|
||||
module Clang_ToolingCore {
|
||||
|
|
|
@ -215,6 +215,15 @@ AtomicChange::AtomicChange(std::string Key, std::string FilePath,
|
|||
RemovedHeaders(std::move(RemovedHeaders)), Replaces(std::move(Replaces)) {
|
||||
}
|
||||
|
||||
bool AtomicChange::operator==(const AtomicChange &Other) const {
|
||||
if (Key != Other.Key || FilePath != Other.FilePath || Error != Other.Error)
|
||||
return false;
|
||||
if (!(Replaces == Other.Replaces))
|
||||
return false;
|
||||
// FXIME: Compare header insertions/removals.
|
||||
return true;
|
||||
}
|
||||
|
||||
std::string AtomicChange::toYAMLString() {
|
||||
std::string YamlContent;
|
||||
llvm::raw_string_ostream YamlContentStream(YamlContent);
|
||||
|
|
|
@ -3,6 +3,7 @@ set(LLVM_LINK_COMPONENTS Support)
|
|||
add_clang_library(clangToolingRefactor
|
||||
ASTSelection.cpp
|
||||
AtomicChange.cpp
|
||||
RefactoringActions.cpp
|
||||
Rename/RenamingAction.cpp
|
||||
Rename/SymbolOccurrences.cpp
|
||||
Rename/USRFinder.cpp
|
||||
|
|
|
@ -0,0 +1,35 @@
|
|||
//===--- RefactoringActions.cpp - Constructs refactoring actions ----------===//
|
||||
//
|
||||
// The LLVM Compiler Infrastructure
|
||||
//
|
||||
// This file is distributed under the University of Illinois Open Source
|
||||
// License. See LICENSE.TXT for details.
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
#include "clang/Tooling/Refactoring/RefactoringAction.h"
|
||||
|
||||
namespace clang {
|
||||
namespace tooling {
|
||||
|
||||
// Forward declare the individual create*Action functions.
|
||||
#define REFACTORING_ACTION(Name) \
|
||||
std::unique_ptr<RefactoringAction> create##Name##Action();
|
||||
#include "clang/Tooling/Refactoring/RefactoringActionRegistry.def"
|
||||
|
||||
std::vector<std::unique_ptr<RefactoringAction>> createRefactoringActions() {
|
||||
std::vector<std::unique_ptr<RefactoringAction>> Actions;
|
||||
|
||||
#define REFACTORING_ACTION(Name) Actions.push_back(create##Name##Action());
|
||||
#include "clang/Tooling/Refactoring/RefactoringActionRegistry.def"
|
||||
|
||||
return Actions;
|
||||
}
|
||||
|
||||
RefactoringActionRules RefactoringAction::createActiveActionRules() {
|
||||
// FIXME: Filter out rules that are not supported by a particular client.
|
||||
return createActionRules();
|
||||
}
|
||||
|
||||
} // end namespace tooling
|
||||
} // end namespace clang
|
|
@ -22,6 +22,10 @@
|
|||
#include "clang/Lex/Preprocessor.h"
|
||||
#include "clang/Tooling/CommonOptionsParser.h"
|
||||
#include "clang/Tooling/Refactoring.h"
|
||||
#include "clang/Tooling/Refactoring/RefactoringAction.h"
|
||||
#include "clang/Tooling/Refactoring/RefactoringActionRules.h"
|
||||
#include "clang/Tooling/Refactoring/Rename/USRFinder.h"
|
||||
#include "clang/Tooling/Refactoring/Rename/USRFindingAction.h"
|
||||
#include "clang/Tooling/Refactoring/Rename/USRLocFinder.h"
|
||||
#include "clang/Tooling/Tooling.h"
|
||||
#include "llvm/ADT/STLExtras.h"
|
||||
|
@ -33,6 +37,63 @@ using namespace llvm;
|
|||
namespace clang {
|
||||
namespace tooling {
|
||||
|
||||
namespace {
|
||||
|
||||
class LocalRename : public RefactoringAction {
|
||||
public:
|
||||
StringRef getCommand() const override { return "local-rename"; }
|
||||
|
||||
StringRef getDescription() const override {
|
||||
return "Finds and renames symbols in code with no indexer support";
|
||||
}
|
||||
|
||||
/// Returns a set of refactoring actions rules that are defined by this
|
||||
/// action.
|
||||
RefactoringActionRules createActionRules() const override {
|
||||
using namespace refactoring_action_rules;
|
||||
RefactoringActionRules Rules;
|
||||
Rules.push_back(createRefactoringRule(
|
||||
renameOccurrences, requiredSelection(SymbolSelectionRequirement())));
|
||||
return Rules;
|
||||
}
|
||||
|
||||
private:
|
||||
static Expected<AtomicChanges>
|
||||
renameOccurrences(const RefactoringRuleContext &Context,
|
||||
const NamedDecl *ND) {
|
||||
std::vector<std::string> USRs =
|
||||
getUSRsForDeclaration(ND, Context.getASTContext());
|
||||
std::string PrevName = ND->getNameAsString();
|
||||
auto Occurrences = getOccurrencesOfUSRs(
|
||||
USRs, PrevName, Context.getASTContext().getTranslationUnitDecl());
|
||||
|
||||
// FIXME: This is a temporary workaround that's needed until the refactoring
|
||||
// options are implemented.
|
||||
StringRef NewName = "Bar";
|
||||
return createRenameReplacements(
|
||||
Occurrences, Context.getASTContext().getSourceManager(), NewName);
|
||||
}
|
||||
|
||||
class SymbolSelectionRequirement : public selection::Requirement {
|
||||
public:
|
||||
Expected<Optional<const NamedDecl *>>
|
||||
evaluateSelection(const RefactoringRuleContext &Context,
|
||||
selection::SourceSelectionRange Selection) const {
|
||||
const NamedDecl *ND = getNamedDeclAt(Context.getASTContext(),
|
||||
Selection.getRange().getBegin());
|
||||
if (!ND)
|
||||
return None;
|
||||
return getCanonicalSymbolDeclaration(ND);
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
} // end anonymous namespace
|
||||
|
||||
std::unique_ptr<RefactoringAction> createLocalRenameAction() {
|
||||
return llvm::make_unique<LocalRename>();
|
||||
}
|
||||
|
||||
Expected<std::vector<AtomicChange>>
|
||||
createRenameReplacements(const SymbolOccurrences &Occurrences,
|
||||
const SourceManager &SM,
|
||||
|
|
|
@ -154,6 +154,12 @@ private:
|
|||
};
|
||||
} // namespace
|
||||
|
||||
std::vector<std::string> getUSRsForDeclaration(const NamedDecl *ND,
|
||||
ASTContext &Context) {
|
||||
AdditionalUSRFinder Finder(ND, Context);
|
||||
return Finder.Find();
|
||||
}
|
||||
|
||||
class NamedDeclFindingConsumer : public ASTConsumer {
|
||||
public:
|
||||
NamedDeclFindingConsumer(ArrayRef<unsigned> SymbolOffsets,
|
||||
|
|
|
@ -48,6 +48,7 @@ list(APPEND CLANG_TEST_DEPS
|
|||
clang-offload-bundler
|
||||
clang-import-test
|
||||
clang-rename
|
||||
clang-refactor
|
||||
clang-diff
|
||||
)
|
||||
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
// RUN: clang-refactor local-rename -selection=test:%s -no-dbs %s | FileCheck %s
|
||||
|
||||
class Baz {
|
||||
int /*range=*/Foo; // CHECK: int /*range=*/Bar;
|
||||
public:
|
||||
Baz();
|
||||
};
|
||||
|
||||
Baz::Baz() : /*range=*/Foo(0) {} // CHECK: Baz::Baz() : /*range=*/Bar(0) {};
|
|
@ -0,0 +1,6 @@
|
|||
// RUN: not clang-refactor 2>&1 | FileCheck --check-prefix=MISSING_ACTION %s
|
||||
// MISSING_ACTION: error: no refactoring action given
|
||||
// MISSING_ACTION-NEXT: note: the following actions are supported:
|
||||
|
||||
// RUN: not clang-refactor local-rename -no-dbs 2>&1 | FileCheck --check-prefix=MISSING_SOURCES %s
|
||||
// MISSING_SOURCES: error: must provide paths to the source files when '-no-dbs' is used
|
|
@ -0,0 +1,41 @@
|
|||
// RUN: clang-refactor local-rename -selection=test:%s -no-dbs -v %s 2>&1 | FileCheck %s
|
||||
|
||||
/*range=*/int test;
|
||||
|
||||
/*range named=*/int test2;
|
||||
|
||||
/*range= +1*/int test3;
|
||||
|
||||
/* range = +100 */int test4;
|
||||
|
||||
/*range named =+0*/int test5;
|
||||
|
||||
// CHECK: Test selection group '':
|
||||
// CHECK-NEXT: 100-100
|
||||
// CHECK-NEXT: 153-153
|
||||
// CHECK-NEXT: 192-192
|
||||
// CHECK-NEXT: Test selection group 'named':
|
||||
// CHECK-NEXT: 127-127
|
||||
// CHECK-NEXT: 213-213
|
||||
|
||||
// The following invocations are in the default group:
|
||||
|
||||
// CHECK: invoking action 'local-rename':
|
||||
// CHECK-NEXT: -selection={{.*}}tool-test-support.c:3:11
|
||||
|
||||
// CHECK: invoking action 'local-rename':
|
||||
// CHECK-NEXT: -selection={{.*}}tool-test-support.c:7:15
|
||||
|
||||
// CHECK: invoking action 'local-rename':
|
||||
// CHECK-NEXT: -selection={{.*}}tool-test-support.c:9:29
|
||||
|
||||
|
||||
// The following invocations are in the 'named' group, and they follow
|
||||
// the default invocation even if some of their ranges occur prior to the
|
||||
// ranges from the default group because the groups are tested one-by-one:
|
||||
|
||||
// CHECK: invoking action 'local-rename':
|
||||
// CHECK-NEXT: -selection={{.*}}tool-test-support.c:5:17
|
||||
|
||||
// CHECK: invoking action 'local-rename':
|
||||
// CHECK-NEXT: -selection={{.*}}tool-test-support.c:11:20
|
|
@ -1,15 +0,0 @@
|
|||
class Baz {
|
||||
int Foo; /* Test 1 */ // CHECK: int Bar;
|
||||
public:
|
||||
Baz();
|
||||
};
|
||||
|
||||
Baz::Baz() : Foo(0) /* Test 2 */ {} // CHECK: Baz::Baz() : Bar(0)
|
||||
|
||||
// Test 1.
|
||||
// RUN: clang-rename -offset=18 -new-name=Bar %s -- | sed 's,//.*,,' | FileCheck %s
|
||||
// Test 2.
|
||||
// RUN: clang-rename -offset=89 -new-name=Bar %s -- | sed 's,//.*,,' | FileCheck %s
|
||||
|
||||
// To find offsets after modifying the file, use:
|
||||
// grep -Ubo 'Foo.*' <file>
|
|
@ -12,6 +12,7 @@ add_clang_subdirectory(clang-offload-bundler)
|
|||
add_clang_subdirectory(c-index-test)
|
||||
|
||||
add_clang_subdirectory(clang-rename)
|
||||
add_clang_subdirectory(clang-refactor)
|
||||
|
||||
if(CLANG_ENABLE_ARCMT)
|
||||
add_clang_subdirectory(arcmt-test)
|
||||
|
|
|
@ -0,0 +1,20 @@
|
|||
set(LLVM_LINK_COMPONENTS
|
||||
Option
|
||||
Support
|
||||
)
|
||||
|
||||
add_clang_executable(clang-refactor
|
||||
ClangRefactor.cpp
|
||||
TestSupport.cpp
|
||||
)
|
||||
|
||||
target_link_libraries(clang-refactor
|
||||
clangBasic
|
||||
clangFrontend
|
||||
clangRewrite
|
||||
clangTooling
|
||||
clangToolingCore
|
||||
clangToolingRefactor
|
||||
)
|
||||
|
||||
install(TARGETS clang-refactor RUNTIME DESTINATION bin)
|
|
@ -0,0 +1,391 @@
|
|||
//===--- ClangRefactor.cpp - Clang-based refactoring tool -----------------===//
|
||||
//
|
||||
// The LLVM Compiler Infrastructure
|
||||
//
|
||||
// This file is distributed under the University of Illinois Open Source
|
||||
// License. See LICENSE.TXT for details.
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
///
|
||||
/// \file
|
||||
/// \brief This file implements a clang-refactor tool that performs various
|
||||
/// source transformations.
|
||||
///
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
#include "TestSupport.h"
|
||||
#include "clang/Rewrite/Core/Rewriter.h"
|
||||
#include "clang/Tooling/Refactoring.h"
|
||||
#include "clang/Tooling/Refactoring/RefactoringAction.h"
|
||||
#include "clang/Tooling/Refactoring/Rename/RenamingAction.h"
|
||||
#include "clang/Tooling/Tooling.h"
|
||||
#include "llvm/Support/CommandLine.h"
|
||||
#include "llvm/Support/FileSystem.h"
|
||||
#include "llvm/Support/raw_ostream.h"
|
||||
#include <string>
|
||||
|
||||
using namespace clang;
|
||||
using namespace tooling;
|
||||
using namespace refactor;
|
||||
namespace cl = llvm::cl;
|
||||
|
||||
namespace opts {
|
||||
|
||||
static cl::OptionCategory CommonRefactorOptions("Common refactoring options");
|
||||
|
||||
static cl::opt<bool>
|
||||
NoDatabases("no-dbs",
|
||||
cl::desc("Ignore external databases including Clang's "
|
||||
"compilation database and indexer stores"),
|
||||
cl::cat(CommonRefactorOptions), cl::sub(*cl::AllSubCommands));
|
||||
|
||||
static cl::opt<bool> Verbose("v", cl::desc("Use verbose output"),
|
||||
cl::cat(CommonRefactorOptions),
|
||||
cl::sub(*cl::AllSubCommands));
|
||||
} // end namespace opts
|
||||
|
||||
namespace {
|
||||
|
||||
/// Stores the parsed `-selection` argument.
|
||||
class SourceSelectionArgument {
|
||||
public:
|
||||
virtual ~SourceSelectionArgument() {}
|
||||
|
||||
/// Parse the `-selection` argument.
|
||||
///
|
||||
/// \returns A valid argument when the parse succedeed, null otherwise.
|
||||
static std::unique_ptr<SourceSelectionArgument> fromString(StringRef Value);
|
||||
|
||||
/// Prints any additional state associated with the selection argument to
|
||||
/// the given output stream.
|
||||
virtual void print(raw_ostream &OS) = 0;
|
||||
|
||||
/// Returns a replacement refactoring result consumer (if any) that should
|
||||
/// consume the results of a refactoring operation.
|
||||
///
|
||||
/// The replacement refactoring result consumer is used by \c
|
||||
/// TestSourceSelectionArgument to inject a test-specific result handling
|
||||
/// logic into the refactoring operation. The test-specific consumer
|
||||
/// ensures that the individual results in a particular test group are
|
||||
/// identical.
|
||||
virtual std::unique_ptr<RefactoringResultConsumer> createCustomConsumer() {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
/// Runs the give refactoring function for each specified selection.
|
||||
///
|
||||
/// \returns true if an error occurred, false otherwise.
|
||||
virtual bool
|
||||
forAllRanges(const SourceManager &SM,
|
||||
llvm::function_ref<void(SourceRange R)> Callback) = 0;
|
||||
};
|
||||
|
||||
/// Stores the parsed -selection=test:<filename> option.
|
||||
class TestSourceSelectionArgument final : public SourceSelectionArgument {
|
||||
public:
|
||||
TestSourceSelectionArgument(TestSelectionRangesInFile TestSelections)
|
||||
: TestSelections(std::move(TestSelections)) {}
|
||||
|
||||
void print(raw_ostream &OS) override { TestSelections.dump(OS); }
|
||||
|
||||
std::unique_ptr<RefactoringResultConsumer> createCustomConsumer() override {
|
||||
return TestSelections.createConsumer();
|
||||
}
|
||||
|
||||
/// Testing support: invokes the selection action for each selection range in
|
||||
/// the test file.
|
||||
bool forAllRanges(const SourceManager &SM,
|
||||
llvm::function_ref<void(SourceRange R)> Callback) override {
|
||||
return TestSelections.foreachRange(SM, Callback);
|
||||
}
|
||||
|
||||
private:
|
||||
TestSelectionRangesInFile TestSelections;
|
||||
};
|
||||
|
||||
std::unique_ptr<SourceSelectionArgument>
|
||||
SourceSelectionArgument::fromString(StringRef Value) {
|
||||
if (Value.startswith("test:")) {
|
||||
StringRef Filename = Value.drop_front(strlen("test:"));
|
||||
Optional<TestSelectionRangesInFile> ParsedTestSelection =
|
||||
findTestSelectionRanges(Filename);
|
||||
if (!ParsedTestSelection)
|
||||
return nullptr; // A parsing error was already reported.
|
||||
return llvm::make_unique<TestSourceSelectionArgument>(
|
||||
std::move(*ParsedTestSelection));
|
||||
}
|
||||
// FIXME: Support true selection ranges.
|
||||
llvm::errs() << "error: '-selection' option must be specified using "
|
||||
"<file>:<line>:<column> or "
|
||||
"<file>:<line>:<column>-<line>:<column> format";
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
/// A subcommand that corresponds to individual refactoring action.
|
||||
class RefactoringActionSubcommand : public cl::SubCommand {
|
||||
public:
|
||||
RefactoringActionSubcommand(std::unique_ptr<RefactoringAction> Action,
|
||||
RefactoringActionRules ActionRules,
|
||||
cl::OptionCategory &Category)
|
||||
: SubCommand(Action->getCommand(), Action->getDescription()),
|
||||
Action(std::move(Action)), ActionRules(std::move(ActionRules)) {
|
||||
Sources = llvm::make_unique<cl::list<std::string>>(
|
||||
cl::Positional, cl::ZeroOrMore, cl::desc("<source0> [... <sourceN>]"),
|
||||
cl::cat(Category), cl::sub(*this));
|
||||
|
||||
// Check if the selection option is supported.
|
||||
bool HasSelection = false;
|
||||
for (const auto &Rule : this->ActionRules) {
|
||||
if ((HasSelection = Rule->hasSelectionRequirement()))
|
||||
break;
|
||||
}
|
||||
if (HasSelection) {
|
||||
Selection = llvm::make_unique<cl::opt<std::string>>(
|
||||
"selection",
|
||||
cl::desc("The selected source range in which the refactoring should "
|
||||
"be initiated (<file>:<line>:<column>-<line>:<column> or "
|
||||
"<file>:<line>:<column>)"),
|
||||
cl::cat(Category), cl::sub(*this));
|
||||
}
|
||||
}
|
||||
|
||||
~RefactoringActionSubcommand() { unregisterSubCommand(); }
|
||||
|
||||
const RefactoringActionRules &getActionRules() const { return ActionRules; }
|
||||
|
||||
/// Parses the command-line arguments that are specific to this rule.
|
||||
///
|
||||
/// \returns true on error, false otherwise.
|
||||
bool parseArguments() {
|
||||
if (Selection) {
|
||||
ParsedSelection = SourceSelectionArgument::fromString(*Selection);
|
||||
if (!ParsedSelection)
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
SourceSelectionArgument *getSelection() const {
|
||||
assert(Selection && "selection not supported!");
|
||||
return ParsedSelection.get();
|
||||
}
|
||||
|
||||
ArrayRef<std::string> getSources() const { return *Sources; }
|
||||
|
||||
private:
|
||||
std::unique_ptr<RefactoringAction> Action;
|
||||
RefactoringActionRules ActionRules;
|
||||
std::unique_ptr<cl::list<std::string>> Sources;
|
||||
std::unique_ptr<cl::opt<std::string>> Selection;
|
||||
std::unique_ptr<SourceSelectionArgument> ParsedSelection;
|
||||
};
|
||||
|
||||
class ClangRefactorConsumer : public RefactoringResultConsumer {
|
||||
public:
|
||||
void handleError(llvm::Error Err) {
|
||||
llvm::errs() << llvm::toString(std::move(Err)) << "\n";
|
||||
}
|
||||
|
||||
// FIXME: Consume atomic changes and apply them to files.
|
||||
};
|
||||
|
||||
class ClangRefactorTool {
|
||||
public:
|
||||
std::vector<std::unique_ptr<RefactoringActionSubcommand>> SubCommands;
|
||||
|
||||
ClangRefactorTool() {
|
||||
std::vector<std::unique_ptr<RefactoringAction>> Actions =
|
||||
createRefactoringActions();
|
||||
|
||||
// Actions must have unique command names so that we can map them to one
|
||||
// subcommand.
|
||||
llvm::StringSet<> CommandNames;
|
||||
for (const auto &Action : Actions) {
|
||||
if (!CommandNames.insert(Action->getCommand()).second) {
|
||||
llvm::errs() << "duplicate refactoring action command '"
|
||||
<< Action->getCommand() << "'!";
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
// Create subcommands and command-line options.
|
||||
for (auto &Action : Actions) {
|
||||
SubCommands.push_back(llvm::make_unique<RefactoringActionSubcommand>(
|
||||
std::move(Action), Action->createActiveActionRules(),
|
||||
opts::CommonRefactorOptions));
|
||||
}
|
||||
}
|
||||
|
||||
using TUCallbackType = llvm::function_ref<void(ASTContext &)>;
|
||||
|
||||
/// Parses the translation units that were given to the subcommand using
|
||||
/// the 'sources' option and invokes the callback for each parsed
|
||||
/// translation unit.
|
||||
bool foreachTranslationUnit(RefactoringActionSubcommand &Subcommand,
|
||||
TUCallbackType Callback) {
|
||||
std::unique_ptr<CompilationDatabase> Compilations;
|
||||
if (opts::NoDatabases) {
|
||||
// FIXME (Alex L): Support compilation options.
|
||||
Compilations =
|
||||
llvm::make_unique<clang::tooling::FixedCompilationDatabase>(
|
||||
".", std::vector<std::string>());
|
||||
} else {
|
||||
// FIXME (Alex L): Support compilation database.
|
||||
llvm::errs() << "compilation databases are not supported yet!\n";
|
||||
return true;
|
||||
}
|
||||
|
||||
class ToolASTConsumer : public ASTConsumer {
|
||||
public:
|
||||
TUCallbackType Callback;
|
||||
ToolASTConsumer(TUCallbackType Callback) : Callback(Callback) {}
|
||||
|
||||
void HandleTranslationUnit(ASTContext &Context) override {
|
||||
Callback(Context);
|
||||
}
|
||||
};
|
||||
class ActionWrapper {
|
||||
public:
|
||||
TUCallbackType Callback;
|
||||
ActionWrapper(TUCallbackType Callback) : Callback(Callback) {}
|
||||
|
||||
std::unique_ptr<ASTConsumer> newASTConsumer() {
|
||||
return llvm::make_unique<ToolASTConsumer>(std::move(Callback));
|
||||
}
|
||||
};
|
||||
|
||||
ClangTool Tool(*Compilations, Subcommand.getSources());
|
||||
ActionWrapper ToolAction(std::move(Callback));
|
||||
std::unique_ptr<tooling::FrontendActionFactory> Factory =
|
||||
tooling::newFrontendActionFactory(&ToolAction);
|
||||
return Tool.run(Factory.get());
|
||||
}
|
||||
|
||||
/// Logs an individual refactoring action invocation to STDOUT.
|
||||
void logInvocation(RefactoringActionSubcommand &Subcommand,
|
||||
const RefactoringRuleContext &Context) {
|
||||
if (!opts::Verbose)
|
||||
return;
|
||||
llvm::outs() << "invoking action '" << Subcommand.getName() << "':\n";
|
||||
if (Context.getSelectionRange().isValid()) {
|
||||
SourceRange R = Context.getSelectionRange();
|
||||
llvm::outs() << " -selection=";
|
||||
R.getBegin().print(llvm::outs(), Context.getSources());
|
||||
llvm::outs() << " -> ";
|
||||
R.getEnd().print(llvm::outs(), Context.getSources());
|
||||
llvm::outs() << "\n";
|
||||
}
|
||||
}
|
||||
|
||||
bool invokeAction(RefactoringActionSubcommand &Subcommand) {
|
||||
// Find a set of matching rules.
|
||||
SmallVector<RefactoringActionRule *, 4> MatchingRules;
|
||||
llvm::StringSet<> MissingOptions;
|
||||
|
||||
bool HasSelection = false;
|
||||
for (const auto &Rule : Subcommand.getActionRules()) {
|
||||
if (Rule->hasSelectionRequirement()) {
|
||||
HasSelection = true;
|
||||
if (Subcommand.getSelection())
|
||||
MatchingRules.push_back(Rule.get());
|
||||
else
|
||||
MissingOptions.insert("selection");
|
||||
}
|
||||
// FIXME (Alex L): Support custom options.
|
||||
}
|
||||
if (MatchingRules.empty()) {
|
||||
llvm::errs() << "error: '" << Subcommand.getName()
|
||||
<< "' can't be invoked with the given arguments:\n";
|
||||
for (const auto &Opt : MissingOptions)
|
||||
llvm::errs() << " missing '-" << Opt.getKey() << "' option\n";
|
||||
return true;
|
||||
}
|
||||
|
||||
bool HasFailed = false;
|
||||
ClangRefactorConsumer Consumer;
|
||||
if (foreachTranslationUnit(Subcommand, [&](ASTContext &AST) {
|
||||
RefactoringRuleContext Context(AST.getSourceManager());
|
||||
Context.setASTContext(AST);
|
||||
|
||||
auto InvokeRule = [&](RefactoringResultConsumer &Consumer) {
|
||||
logInvocation(Subcommand, Context);
|
||||
for (RefactoringActionRule *Rule : MatchingRules) {
|
||||
if (!Rule->hasSelectionRequirement())
|
||||
continue;
|
||||
Rule->invoke(Consumer, Context);
|
||||
return;
|
||||
}
|
||||
// FIXME (Alex L): If more than one initiation succeeded, then the
|
||||
// rules are ambiguous.
|
||||
llvm_unreachable(
|
||||
"The action must have at least one selection rule");
|
||||
};
|
||||
|
||||
if (HasSelection) {
|
||||
assert(Subcommand.getSelection() && "Missing selection argument?");
|
||||
if (opts::Verbose)
|
||||
Subcommand.getSelection()->print(llvm::outs());
|
||||
auto CustomConsumer =
|
||||
Subcommand.getSelection()->createCustomConsumer();
|
||||
if (Subcommand.getSelection()->forAllRanges(
|
||||
Context.getSources(), [&](SourceRange R) {
|
||||
Context.setSelectionRange(R);
|
||||
InvokeRule(CustomConsumer ? *CustomConsumer : Consumer);
|
||||
}))
|
||||
HasFailed = true;
|
||||
return;
|
||||
}
|
||||
// FIXME (Alex L): Implement non-selection based invocation path.
|
||||
}))
|
||||
return true;
|
||||
return HasFailed;
|
||||
}
|
||||
};
|
||||
|
||||
} // end anonymous namespace
|
||||
|
||||
int main(int argc, const char **argv) {
|
||||
ClangRefactorTool Tool;
|
||||
|
||||
// FIXME: Use LibTooling's CommonOptions parser when subcommands are supported
|
||||
// by it.
|
||||
cl::HideUnrelatedOptions(opts::CommonRefactorOptions);
|
||||
cl::ParseCommandLineOptions(
|
||||
argc, argv, "Clang-based refactoring tool for C, C++ and Objective-C");
|
||||
cl::PrintOptionValues();
|
||||
|
||||
// Figure out which action is specified by the user. The user must specify
|
||||
// the action using a command-line subcommand, e.g. the invocation
|
||||
// `clang-refactor local-rename` corresponds to the `LocalRename` refactoring
|
||||
// action. All subcommands must have a unique names. This allows us to figure
|
||||
// out which refactoring action should be invoked by looking at the first
|
||||
// subcommand that's enabled by LLVM's command-line parser.
|
||||
auto It = llvm::find_if(
|
||||
Tool.SubCommands,
|
||||
[](const std::unique_ptr<RefactoringActionSubcommand> &SubCommand) {
|
||||
return !!(*SubCommand);
|
||||
});
|
||||
if (It == Tool.SubCommands.end()) {
|
||||
llvm::errs() << "error: no refactoring action given\n";
|
||||
llvm::errs() << "note: the following actions are supported:\n";
|
||||
for (const auto &Subcommand : Tool.SubCommands)
|
||||
llvm::errs().indent(2) << Subcommand->getName() << "\n";
|
||||
return 1;
|
||||
}
|
||||
RefactoringActionSubcommand &ActionCommand = **It;
|
||||
|
||||
ArrayRef<std::string> Sources = ActionCommand.getSources();
|
||||
// When -no-dbs is used, at least one file (TU) must be given to any
|
||||
// subcommand.
|
||||
if (opts::NoDatabases && Sources.empty()) {
|
||||
llvm::errs() << "error: must provide paths to the source files when "
|
||||
"'-no-dbs' is used\n";
|
||||
return 1;
|
||||
}
|
||||
if (ActionCommand.parseArguments())
|
||||
return 1;
|
||||
if (Tool.invokeAction(ActionCommand))
|
||||
return 1;
|
||||
|
||||
return 0;
|
||||
}
|
|
@ -0,0 +1,343 @@
|
|||
//===--- TestSupport.cpp - Clang-based refactoring tool -------------------===//
|
||||
//
|
||||
// The LLVM Compiler Infrastructure
|
||||
//
|
||||
// This file is distributed under the University of Illinois Open Source
|
||||
// License. See LICENSE.TXT for details.
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
///
|
||||
/// \file
|
||||
/// \brief This file implements routines that provide refactoring testing
|
||||
/// utilities.
|
||||
///
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
#include "TestSupport.h"
|
||||
#include "clang/Basic/SourceManager.h"
|
||||
#include "clang/Lex/Lexer.h"
|
||||
#include "llvm/ADT/STLExtras.h"
|
||||
#include "llvm/Support/Error.h"
|
||||
#include "llvm/Support/ErrorOr.h"
|
||||
#include "llvm/Support/MemoryBuffer.h"
|
||||
#include "llvm/Support/Regex.h"
|
||||
#include "llvm/Support/raw_ostream.h"
|
||||
|
||||
using namespace llvm;
|
||||
|
||||
namespace clang {
|
||||
namespace refactor {
|
||||
|
||||
void TestSelectionRangesInFile::dump(raw_ostream &OS) const {
|
||||
for (const auto &Group : GroupedRanges) {
|
||||
OS << "Test selection group '" << Group.Name << "':\n";
|
||||
for (const auto &Range : Group.Ranges) {
|
||||
OS << " " << Range.Begin << "-" << Range.End << "\n";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool TestSelectionRangesInFile::foreachRange(
|
||||
const SourceManager &SM,
|
||||
llvm::function_ref<void(SourceRange)> Callback) const {
|
||||
const FileEntry *FE = SM.getFileManager().getFile(Filename);
|
||||
FileID FID = FE ? SM.translateFile(FE) : FileID();
|
||||
if (!FE || FID.isInvalid()) {
|
||||
llvm::errs() << "error: -selection=test:" << Filename
|
||||
<< " : given file is not in the target TU";
|
||||
return true;
|
||||
}
|
||||
SourceLocation FileLoc = SM.getLocForStartOfFile(FID);
|
||||
for (const auto &Group : GroupedRanges) {
|
||||
for (const TestSelectionRange &Range : Group.Ranges) {
|
||||
// Translate the offset pair to a true source range.
|
||||
SourceLocation Start =
|
||||
SM.getMacroArgExpandedLocation(FileLoc.getLocWithOffset(Range.Begin));
|
||||
SourceLocation End =
|
||||
SM.getMacroArgExpandedLocation(FileLoc.getLocWithOffset(Range.End));
|
||||
assert(Start.isValid() && End.isValid() && "unexpected invalid range");
|
||||
Callback(SourceRange(Start, End));
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
void dumpChanges(const tooling::AtomicChanges &Changes, raw_ostream &OS) {
|
||||
for (const auto &Change : Changes)
|
||||
OS << const_cast<tooling::AtomicChange &>(Change).toYAMLString() << "\n";
|
||||
}
|
||||
|
||||
bool areChangesSame(const tooling::AtomicChanges &LHS,
|
||||
const tooling::AtomicChanges &RHS) {
|
||||
if (LHS.size() != RHS.size())
|
||||
return false;
|
||||
for (const auto &I : llvm::zip(LHS, RHS)) {
|
||||
if (!(std::get<0>(I) == std::get<1>(I)))
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool printRewrittenSources(const tooling::AtomicChanges &Changes,
|
||||
raw_ostream &OS) {
|
||||
std::set<std::string> Files;
|
||||
for (const auto &Change : Changes)
|
||||
Files.insert(Change.getFilePath());
|
||||
tooling::ApplyChangesSpec Spec;
|
||||
Spec.Cleanup = false;
|
||||
for (const auto &File : Files) {
|
||||
llvm::ErrorOr<std::unique_ptr<llvm::MemoryBuffer>> BufferErr =
|
||||
llvm::MemoryBuffer::getFile(File);
|
||||
if (!BufferErr) {
|
||||
llvm::errs() << "failed to open" << File << "\n";
|
||||
return true;
|
||||
}
|
||||
auto Result = tooling::applyAtomicChanges(File, (*BufferErr)->getBuffer(),
|
||||
Changes, Spec);
|
||||
if (!Result) {
|
||||
llvm::errs() << toString(Result.takeError());
|
||||
return true;
|
||||
}
|
||||
OS << *Result;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
class TestRefactoringResultConsumer final
|
||||
: public tooling::RefactoringResultConsumer {
|
||||
public:
|
||||
TestRefactoringResultConsumer(const TestSelectionRangesInFile &TestRanges)
|
||||
: TestRanges(TestRanges) {
|
||||
Results.push_back({});
|
||||
}
|
||||
|
||||
~TestRefactoringResultConsumer() {
|
||||
// Ensure all results are checked.
|
||||
for (auto &Group : Results) {
|
||||
for (auto &Result : Group) {
|
||||
if (!Result) {
|
||||
(void)llvm::toString(Result.takeError());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void handleError(llvm::Error Err) override { handleResult(std::move(Err)); }
|
||||
|
||||
void handle(tooling::AtomicChanges Changes) override {
|
||||
handleResult(std::move(Changes));
|
||||
}
|
||||
|
||||
void handle(tooling::SymbolOccurrences Occurrences) override {
|
||||
tooling::RefactoringResultConsumer::handle(std::move(Occurrences));
|
||||
}
|
||||
|
||||
private:
|
||||
bool handleAllResults();
|
||||
|
||||
void handleResult(Expected<tooling::AtomicChanges> Result) {
|
||||
Results.back().push_back(std::move(Result));
|
||||
size_t GroupIndex = Results.size() - 1;
|
||||
if (Results.back().size() >=
|
||||
TestRanges.GroupedRanges[GroupIndex].Ranges.size()) {
|
||||
++GroupIndex;
|
||||
if (GroupIndex >= TestRanges.GroupedRanges.size()) {
|
||||
if (handleAllResults())
|
||||
exit(1); // error has occurred.
|
||||
return;
|
||||
}
|
||||
Results.push_back({});
|
||||
}
|
||||
}
|
||||
|
||||
const TestSelectionRangesInFile &TestRanges;
|
||||
std::vector<std::vector<Expected<tooling::AtomicChanges>>> Results;
|
||||
};
|
||||
|
||||
std::pair<unsigned, unsigned> getLineColumn(StringRef Filename,
|
||||
unsigned Offset) {
|
||||
ErrorOr<std::unique_ptr<MemoryBuffer>> ErrOrFile =
|
||||
MemoryBuffer::getFile(Filename);
|
||||
if (!ErrOrFile)
|
||||
return {0, 0};
|
||||
StringRef Source = ErrOrFile.get()->getBuffer();
|
||||
Source = Source.take_front(Offset);
|
||||
size_t LastLine = Source.find_last_of("\r\n");
|
||||
return {Source.count('\n') + 1,
|
||||
(LastLine == StringRef::npos ? Offset : Offset - LastLine) + 1};
|
||||
}
|
||||
|
||||
} // end anonymous namespace
|
||||
|
||||
bool TestRefactoringResultConsumer::handleAllResults() {
|
||||
bool Failed = false;
|
||||
for (auto &Group : llvm::enumerate(Results)) {
|
||||
// All ranges in the group must produce the same result.
|
||||
Optional<tooling::AtomicChanges> CanonicalResult;
|
||||
Optional<std::string> CanonicalErrorMessage;
|
||||
for (auto &I : llvm::enumerate(Group.value())) {
|
||||
Expected<tooling::AtomicChanges> &Result = I.value();
|
||||
std::string ErrorMessage;
|
||||
bool HasResult = !!Result;
|
||||
if (!HasResult) {
|
||||
// FIXME: Handle diagnostic error as well.
|
||||
handleAllErrors(Result.takeError(), [&](StringError &Err) {
|
||||
ErrorMessage = Err.getMessage();
|
||||
});
|
||||
}
|
||||
if (!CanonicalResult && !CanonicalErrorMessage) {
|
||||
if (HasResult)
|
||||
CanonicalResult = std::move(*Result);
|
||||
else
|
||||
CanonicalErrorMessage = std::move(ErrorMessage);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Verify that this result corresponds to the canonical result.
|
||||
if (CanonicalErrorMessage) {
|
||||
// The error messages must match.
|
||||
if (!HasResult && ErrorMessage == *CanonicalErrorMessage)
|
||||
continue;
|
||||
} else {
|
||||
assert(CanonicalResult && "missing canonical result");
|
||||
// The results must match.
|
||||
if (HasResult && areChangesSame(*Result, *CanonicalResult))
|
||||
continue;
|
||||
}
|
||||
Failed = true;
|
||||
// Report the mismatch.
|
||||
std::pair<unsigned, unsigned> LineColumn = getLineColumn(
|
||||
TestRanges.Filename,
|
||||
TestRanges.GroupedRanges[Group.index()].Ranges[I.index()].Begin);
|
||||
llvm::errs()
|
||||
<< "error: unexpected refactoring result for range starting at "
|
||||
<< LineColumn.first << ':' << LineColumn.second << " in group '"
|
||||
<< TestRanges.GroupedRanges[Group.index()].Name << "':\n ";
|
||||
if (HasResult)
|
||||
llvm::errs() << "valid result";
|
||||
else
|
||||
llvm::errs() << "error '" << ErrorMessage << "'";
|
||||
llvm::errs() << " does not match initial ";
|
||||
if (CanonicalErrorMessage)
|
||||
llvm::errs() << "error '" << *CanonicalErrorMessage << "'\n";
|
||||
else
|
||||
llvm::errs() << "valid result\n";
|
||||
if (HasResult && !CanonicalErrorMessage) {
|
||||
llvm::errs() << " Expected to Produce:\n";
|
||||
dumpChanges(*CanonicalResult, llvm::errs());
|
||||
llvm::errs() << " Produced:\n";
|
||||
dumpChanges(*Result, llvm::errs());
|
||||
}
|
||||
}
|
||||
|
||||
// Dump the results:
|
||||
const auto &TestGroup = TestRanges.GroupedRanges[Group.index()];
|
||||
if (!CanonicalResult) {
|
||||
llvm::errs() << TestGroup.Ranges.size() << " '" << TestGroup.Name
|
||||
<< "' results:\n";
|
||||
llvm::errs() << *CanonicalErrorMessage << "\n";
|
||||
} else {
|
||||
llvm::outs() << TestGroup.Ranges.size() << " '" << TestGroup.Name
|
||||
<< "' results:\n";
|
||||
if (printRewrittenSources(*CanonicalResult, llvm::outs()))
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return Failed;
|
||||
}
|
||||
|
||||
std::unique_ptr<tooling::RefactoringResultConsumer>
|
||||
TestSelectionRangesInFile::createConsumer() const {
|
||||
return llvm::make_unique<TestRefactoringResultConsumer>(*this);
|
||||
}
|
||||
|
||||
/// Adds the \p ColumnOffset to file offset \p Offset, without going past a
|
||||
/// newline.
|
||||
static unsigned addColumnOffset(StringRef Source, unsigned Offset,
|
||||
unsigned ColumnOffset) {
|
||||
if (!ColumnOffset)
|
||||
return Offset;
|
||||
StringRef Substr = Source.drop_front(Offset).take_front(ColumnOffset);
|
||||
size_t NewlinePos = Substr.find_first_of("\r\n");
|
||||
return Offset +
|
||||
(NewlinePos == StringRef::npos ? ColumnOffset : (unsigned)NewlinePos);
|
||||
}
|
||||
|
||||
Optional<TestSelectionRangesInFile>
|
||||
findTestSelectionRanges(StringRef Filename) {
|
||||
ErrorOr<std::unique_ptr<MemoryBuffer>> ErrOrFile =
|
||||
MemoryBuffer::getFile(Filename);
|
||||
if (!ErrOrFile) {
|
||||
llvm::errs() << "error: -selection=test:" << Filename
|
||||
<< " : could not open the given file";
|
||||
return None;
|
||||
}
|
||||
StringRef Source = ErrOrFile.get()->getBuffer();
|
||||
|
||||
// FIXME (Alex L): 3rd capture groups for +line:column.
|
||||
// See the doc comment for this function for the explanation of this
|
||||
// syntax.
|
||||
static Regex RangeRegex("range[[:blank:]]*([[:alpha:]_]*)?[[:blank:]]*=[[:"
|
||||
"blank:]]*(\\+[[:digit:]]+)?");
|
||||
|
||||
std::map<std::string, SmallVector<TestSelectionRange, 8>> GroupedRanges;
|
||||
|
||||
LangOptions LangOpts;
|
||||
LangOpts.CPlusPlus = 1;
|
||||
LangOpts.CPlusPlus11 = 1;
|
||||
Lexer Lex(SourceLocation::getFromRawEncoding(0), LangOpts, Source.begin(),
|
||||
Source.begin(), Source.end());
|
||||
Lex.SetCommentRetentionState(true);
|
||||
Token Tok;
|
||||
for (Lex.LexFromRawLexer(Tok); Tok.isNot(tok::eof);
|
||||
Lex.LexFromRawLexer(Tok)) {
|
||||
if (Tok.isNot(tok::comment))
|
||||
continue;
|
||||
StringRef Comment =
|
||||
Source.substr(Tok.getLocation().getRawEncoding(), Tok.getLength());
|
||||
SmallVector<StringRef, 4> Matches;
|
||||
// Allow CHECK: comments to contain range= commands.
|
||||
if (!RangeRegex.match(Comment, &Matches) || Comment.contains("CHECK")) {
|
||||
// Try to detect mistyped 'range:' comments to ensure tests don't miss
|
||||
// anything.
|
||||
if (Comment.contains_lower("range") && Comment.contains("=") &&
|
||||
!Comment.contains_lower("run") && !Comment.contains("CHECK")) {
|
||||
llvm::errs() << "error: suspicious comment '" << Comment
|
||||
<< "' that "
|
||||
"resembles the range command found\n";
|
||||
llvm::errs() << "note: please reword if this isn't a range command\n";
|
||||
return None;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
unsigned Offset = Tok.getEndLoc().getRawEncoding();
|
||||
unsigned ColumnOffset = 0;
|
||||
if (!Matches[2].empty()) {
|
||||
// Don't forget to drop the '+'!
|
||||
if (Matches[2].drop_front().getAsInteger(10, ColumnOffset))
|
||||
assert(false && "regex should have produced a number");
|
||||
}
|
||||
// FIXME (Alex L): Support true ranges.
|
||||
Offset = addColumnOffset(Source, Offset, ColumnOffset);
|
||||
TestSelectionRange Range = {Offset, Offset};
|
||||
auto It = GroupedRanges.insert(std::make_pair(
|
||||
Matches[1].str(), SmallVector<TestSelectionRange, 8>{Range}));
|
||||
if (!It.second)
|
||||
It.first->second.push_back(Range);
|
||||
}
|
||||
if (GroupedRanges.empty()) {
|
||||
llvm::errs() << "error: -selection=test:" << Filename
|
||||
<< ": no 'range' commands";
|
||||
return None;
|
||||
}
|
||||
|
||||
TestSelectionRangesInFile TestRanges = {Filename.str(), {}};
|
||||
for (auto &Group : GroupedRanges)
|
||||
TestRanges.GroupedRanges.push_back({Group.first, std::move(Group.second)});
|
||||
return std::move(TestRanges);
|
||||
}
|
||||
|
||||
} // end namespace refactor
|
||||
} // end namespace clang
|
|
@ -0,0 +1,107 @@
|
|||
//===--- TestSupport.h - Clang-based refactoring tool -----------*- C++ -*-===//
|
||||
//
|
||||
// The LLVM Compiler Infrastructure
|
||||
//
|
||||
// This file is distributed under the University of Illinois Open Source
|
||||
// License. See LICENSE.TXT for details.
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
///
|
||||
/// \file
|
||||
/// \brief Declares datatypes and routines that are used by test-specific code
|
||||
/// in clang-refactor.
|
||||
///
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
#ifndef LLVM_CLANG_TOOLS_CLANG_REFACTOR_TEST_SUPPORT_H
|
||||
#define LLVM_CLANG_TOOLS_CLANG_REFACTOR_TEST_SUPPORT_H
|
||||
|
||||
#include "clang/Basic/LLVM.h"
|
||||
#include "clang/Basic/SourceLocation.h"
|
||||
#include "clang/Tooling/Refactoring/RefactoringResultConsumer.h"
|
||||
#include "llvm/ADT/Optional.h"
|
||||
#include "llvm/ADT/SmallVector.h"
|
||||
#include "llvm/Support/Error.h"
|
||||
#include <map>
|
||||
#include <string>
|
||||
|
||||
namespace clang {
|
||||
|
||||
class SourceManager;
|
||||
|
||||
namespace refactor {
|
||||
|
||||
/// A source selection range that's specified in a test file using an inline
|
||||
/// command in the comment. These commands can take the following forms:
|
||||
///
|
||||
/// - /*range=*/ will create an empty selection range in the default group
|
||||
/// right after the comment.
|
||||
/// - /*range a=*/ will create an empty selection range in the 'a' group right
|
||||
/// after the comment.
|
||||
/// - /*range = +1*/ will create an empty selection range at a location that's
|
||||
/// right after the comment with one offset to the column.
|
||||
/// - /*range= -> +2:3*/ will create a selection range that starts at the
|
||||
/// location right after the comment, and ends at column 3 of the 2nd line
|
||||
/// after the line of the starting location.
|
||||
///
|
||||
/// Clang-refactor will expected all ranges in one test group to produce
|
||||
/// identical results.
|
||||
struct TestSelectionRange {
|
||||
unsigned Begin, End;
|
||||
};
|
||||
|
||||
/// A set of test selection ranges specified in one file.
|
||||
struct TestSelectionRangesInFile {
|
||||
std::string Filename;
|
||||
struct RangeGroup {
|
||||
std::string Name;
|
||||
SmallVector<TestSelectionRange, 8> Ranges;
|
||||
};
|
||||
std::vector<RangeGroup> GroupedRanges;
|
||||
|
||||
TestSelectionRangesInFile(TestSelectionRangesInFile &&) = default;
|
||||
TestSelectionRangesInFile &operator=(TestSelectionRangesInFile &&) = default;
|
||||
|
||||
bool foreachRange(const SourceManager &SM,
|
||||
llvm::function_ref<void(SourceRange)> Callback) const;
|
||||
|
||||
std::unique_ptr<tooling::RefactoringResultConsumer> createConsumer() const;
|
||||
|
||||
void dump(llvm::raw_ostream &OS) const;
|
||||
};
|
||||
|
||||
/// Extracts the grouped selection ranges from the file that's specified in
|
||||
/// the -selection=test:<filename> option.
|
||||
///
|
||||
/// The grouped ranges are specified in comments using the following syntax:
|
||||
/// "range" [ group-name ] "=" [ "+" starting-column-offset ] [ "->"
|
||||
/// "+" ending-line-offset ":"
|
||||
/// ending-column-position ]
|
||||
///
|
||||
/// The selection range is then computed from this command by taking the ending
|
||||
/// location of the comment, and adding 'starting-column-offset' to the column
|
||||
/// for that location. That location in turns becomes the whole selection range,
|
||||
/// unless 'ending-line-offset' and 'ending-column-position' are specified. If
|
||||
/// they are specified, then the ending location of the selection range is
|
||||
/// the starting location's line + 'ending-line-offset' and the
|
||||
/// 'ending-column-position' column.
|
||||
///
|
||||
/// All selection ranges in one group are expected to produce the same
|
||||
/// refactoring result.
|
||||
///
|
||||
/// When testing, zero is returned from clang-refactor even when a group
|
||||
/// produces an initiation error, which is different from normal invocation
|
||||
/// that returns a non-zero value. This is done on purpose, to ensure that group
|
||||
/// consistency checks can return non-zero, but still print the output of
|
||||
/// the group. So even if a test matches the output of group, it will still fail
|
||||
/// because clang-refactor should return zero on exit when the group results are
|
||||
/// consistent.
|
||||
///
|
||||
/// \returns None on failure (errors are emitted to stderr), or a set of
|
||||
/// grouped source ranges in the given file otherwise.
|
||||
Optional<TestSelectionRangesInFile> findTestSelectionRanges(StringRef Filename);
|
||||
|
||||
} // end namespace refactor
|
||||
} // end namespace clang
|
||||
|
||||
#endif // LLVM_CLANG_TOOLS_CLANG_REFACTOR_TEST_SUPPORT_H
|
|
@ -57,7 +57,8 @@ createReplacements(const std::unique_ptr<RefactoringActionRule> &Rule,
|
|||
|
||||
TEST_F(RefactoringActionRulesTest, MyFirstRefactoringRule) {
|
||||
auto ReplaceAWithB =
|
||||
[](std::pair<selection::SourceSelectionRange, int> Selection)
|
||||
[](const RefactoringRuleContext &,
|
||||
std::pair<selection::SourceSelectionRange, int> Selection)
|
||||
-> Expected<AtomicChanges> {
|
||||
const SourceManager &SM = Selection.first.getSources();
|
||||
SourceLocation Loc = Selection.first.getRange().getBegin().getLocWithOffset(
|
||||
|
@ -71,7 +72,8 @@ TEST_F(RefactoringActionRulesTest, MyFirstRefactoringRule) {
|
|||
class SelectionRequirement : public selection::Requirement {
|
||||
public:
|
||||
std::pair<selection::SourceSelectionRange, int>
|
||||
evaluateSelection(selection::SourceSelectionRange Selection) const {
|
||||
evaluateSelection(const RefactoringRuleContext &,
|
||||
selection::SourceSelectionRange Selection) const {
|
||||
return std::make_pair(Selection, 20);
|
||||
}
|
||||
};
|
||||
|
@ -127,8 +129,10 @@ TEST_F(RefactoringActionRulesTest, MyFirstRefactoringRule) {
|
|||
}
|
||||
|
||||
TEST_F(RefactoringActionRulesTest, ReturnError) {
|
||||
Expected<AtomicChanges> (*Func)(selection::SourceSelectionRange) =
|
||||
[](selection::SourceSelectionRange) -> Expected<AtomicChanges> {
|
||||
Expected<AtomicChanges> (*Func)(const RefactoringRuleContext &,
|
||||
selection::SourceSelectionRange) =
|
||||
[](const RefactoringRuleContext &,
|
||||
selection::SourceSelectionRange) -> Expected<AtomicChanges> {
|
||||
return llvm::make_error<llvm::StringError>(
|
||||
"Error", llvm::make_error_code(llvm::errc::invalid_argument));
|
||||
};
|
||||
|
@ -155,13 +159,14 @@ TEST_F(RefactoringActionRulesTest, ReturnInitiationDiagnostic) {
|
|||
class SelectionRequirement : public selection::Requirement {
|
||||
public:
|
||||
Expected<Optional<int>>
|
||||
evaluateSelection(selection::SourceSelectionRange Selection) const {
|
||||
evaluateSelection(const RefactoringRuleContext &,
|
||||
selection::SourceSelectionRange Selection) const {
|
||||
return llvm::make_error<llvm::StringError>(
|
||||
"bad selection", llvm::make_error_code(llvm::errc::invalid_argument));
|
||||
}
|
||||
};
|
||||
auto Rule = createRefactoringRule(
|
||||
[](int) -> Expected<AtomicChanges> {
|
||||
[](const RefactoringRuleContext &, int) -> Expected<AtomicChanges> {
|
||||
llvm::report_fatal_error("Should not run!");
|
||||
},
|
||||
requiredSelection(SelectionRequirement()));
|
||||
|
@ -201,7 +206,8 @@ Optional<SymbolOccurrences> findOccurrences(RefactoringActionRule &Rule,
|
|||
|
||||
TEST_F(RefactoringActionRulesTest, ReturnSymbolOccurrences) {
|
||||
auto Rule = createRefactoringRule(
|
||||
[](selection::SourceSelectionRange Selection)
|
||||
[](const RefactoringRuleContext &,
|
||||
selection::SourceSelectionRange Selection)
|
||||
-> Expected<SymbolOccurrences> {
|
||||
SymbolOccurrences Occurrences;
|
||||
Occurrences.push_back(SymbolOccurrence(
|
||||
|
|
Loading…
Reference in New Issue