summaryrefslogtreecommitdiff
path: root/tools/clang-refactor
diff options
context:
space:
mode:
Diffstat (limited to 'tools/clang-refactor')
-rw-r--r--tools/clang-refactor/CMakeLists.txt24
-rw-r--r--tools/clang-refactor/ClangRefactor.cpp638
-rw-r--r--tools/clang-refactor/TestSupport.cpp392
-rw-r--r--tools/clang-refactor/TestSupport.h107
-rw-r--r--tools/clang-refactor/ToolRefactoringResultConsumer.h48
5 files changed, 1209 insertions, 0 deletions
diff --git a/tools/clang-refactor/CMakeLists.txt b/tools/clang-refactor/CMakeLists.txt
new file mode 100644
index 000000000000..d2029066b9b7
--- /dev/null
+++ b/tools/clang-refactor/CMakeLists.txt
@@ -0,0 +1,24 @@
+set(LLVM_LINK_COMPONENTS
+ Option
+ Support
+ )
+
+add_clang_tool(clang-refactor
+ ClangRefactor.cpp
+ TestSupport.cpp
+ )
+
+target_link_libraries(clang-refactor
+ PRIVATE
+ clangAST
+ clangBasic
+ clangFormat
+ clangFrontend
+ clangLex
+ clangRewrite
+ clangTooling
+ clangToolingCore
+ clangToolingRefactor
+ )
+
+install(TARGETS clang-refactor RUNTIME DESTINATION bin)
diff --git a/tools/clang-refactor/ClangRefactor.cpp b/tools/clang-refactor/ClangRefactor.cpp
new file mode 100644
index 000000000000..950b80062cd9
--- /dev/null
+++ b/tools/clang-refactor/ClangRefactor.cpp
@@ -0,0 +1,638 @@
+//===--- 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/Frontend/CommandLineSourceLoc.h"
+#include "clang/Frontend/TextDiagnosticPrinter.h"
+#include "clang/Rewrite/Core/Rewriter.h"
+#include "clang/Tooling/CommonOptionsParser.h"
+#include "clang/Tooling/Refactoring.h"
+#include "clang/Tooling/Refactoring/RefactoringAction.h"
+#include "clang/Tooling/Refactoring/RefactoringOptions.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/Signals.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("Refactoring options");
+
+static cl::opt<bool> Verbose("v", cl::desc("Use verbose output"),
+ cl::cat(cl::GeneralCategory),
+ cl::sub(*cl::AllSubCommands));
+
+static cl::opt<bool> Inplace("i", cl::desc("Inplace edit <file>s"),
+ cl::cat(cl::GeneralCategory),
+ 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) {}
+
+ /// 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<ClangRefactorToolConsumerInterface>
+ 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<ClangRefactorToolConsumerInterface>
+ 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;
+};
+
+/// Stores the parsed -selection=filename:line:column[-line:column] option.
+class SourceRangeSelectionArgument final : public SourceSelectionArgument {
+public:
+ SourceRangeSelectionArgument(ParsedSourceRange Range)
+ : Range(std::move(Range)) {}
+
+ bool forAllRanges(const SourceManager &SM,
+ llvm::function_ref<void(SourceRange R)> Callback) override {
+ const FileEntry *FE = SM.getFileManager().getFile(Range.FileName);
+ FileID FID = FE ? SM.translateFile(FE) : FileID();
+ if (!FE || FID.isInvalid()) {
+ llvm::errs() << "error: -selection=" << Range.FileName
+ << ":... : given file is not in the target TU\n";
+ return true;
+ }
+
+ SourceLocation Start = SM.getMacroArgExpandedLocation(
+ SM.translateLineCol(FID, Range.Begin.first, Range.Begin.second));
+ SourceLocation End = SM.getMacroArgExpandedLocation(
+ SM.translateLineCol(FID, Range.End.first, Range.End.second));
+ if (Start.isInvalid() || End.isInvalid()) {
+ llvm::errs() << "error: -selection=" << Range.FileName << ':'
+ << Range.Begin.first << ':' << Range.Begin.second << '-'
+ << Range.End.first << ':' << Range.End.second
+ << " : invalid source location\n";
+ return true;
+ }
+ Callback(SourceRange(Start, End));
+ return false;
+ }
+
+private:
+ ParsedSourceRange Range;
+};
+
+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));
+ }
+ Optional<ParsedSourceRange> Range = ParsedSourceRange::fromString(Value);
+ if (Range)
+ return llvm::make_unique<SourceRangeSelectionArgument>(std::move(*Range));
+ llvm::errs() << "error: '-selection' option must be specified using "
+ "<file>:<line>:<column> or "
+ "<file>:<line>:<column>-<line>:<column> format\n";
+ return nullptr;
+}
+
+/// A container that stores the command-line options used by a single
+/// refactoring option.
+class RefactoringActionCommandLineOptions {
+public:
+ void addStringOption(const RefactoringOption &Option,
+ std::unique_ptr<cl::opt<std::string>> CLOption) {
+ StringOptions[&Option] = std::move(CLOption);
+ }
+
+ const cl::opt<std::string> &
+ getStringOption(const RefactoringOption &Opt) const {
+ auto It = StringOptions.find(&Opt);
+ return *It->second;
+ }
+
+private:
+ llvm::DenseMap<const RefactoringOption *,
+ std::unique_ptr<cl::opt<std::string>>>
+ StringOptions;
+};
+
+/// Passes the command-line option values to the options used by a single
+/// refactoring action rule.
+class CommandLineRefactoringOptionVisitor final
+ : public RefactoringOptionVisitor {
+public:
+ CommandLineRefactoringOptionVisitor(
+ const RefactoringActionCommandLineOptions &Options)
+ : Options(Options) {}
+
+ void visit(const RefactoringOption &Opt,
+ Optional<std::string> &Value) override {
+ const cl::opt<std::string> &CLOpt = Options.getStringOption(Opt);
+ if (!CLOpt.getValue().empty()) {
+ Value = CLOpt.getValue();
+ return;
+ }
+ Value = None;
+ if (Opt.isRequired())
+ MissingRequiredOptions.push_back(&Opt);
+ }
+
+ ArrayRef<const RefactoringOption *> getMissingRequiredOptions() const {
+ return MissingRequiredOptions;
+ }
+
+private:
+ llvm::SmallVector<const RefactoringOption *, 4> MissingRequiredOptions;
+ const RefactoringActionCommandLineOptions &Options;
+};
+
+/// Creates the refactoring options used by all the rules in a single
+/// refactoring action.
+class CommandLineRefactoringOptionCreator final
+ : public RefactoringOptionVisitor {
+public:
+ CommandLineRefactoringOptionCreator(
+ cl::OptionCategory &Category, cl::SubCommand &Subcommand,
+ RefactoringActionCommandLineOptions &Options)
+ : Category(Category), Subcommand(Subcommand), Options(Options) {}
+
+ void visit(const RefactoringOption &Opt, Optional<std::string> &) override {
+ if (Visited.insert(&Opt).second)
+ Options.addStringOption(Opt, create<std::string>(Opt));
+ }
+
+private:
+ template <typename T>
+ std::unique_ptr<cl::opt<T>> create(const RefactoringOption &Opt) {
+ if (!OptionNames.insert(Opt.getName()).second)
+ llvm::report_fatal_error("Multiple identical refactoring options "
+ "specified for one refactoring action");
+ // FIXME: cl::Required can be specified when this option is present
+ // in all rules in an action.
+ return llvm::make_unique<cl::opt<T>>(
+ Opt.getName(), cl::desc(Opt.getDescription()), cl::Optional,
+ cl::cat(Category), cl::sub(Subcommand));
+ }
+
+ llvm::SmallPtrSet<const RefactoringOption *, 8> Visited;
+ llvm::StringSet<> OptionNames;
+ cl::OptionCategory &Category;
+ cl::SubCommand &Subcommand;
+ RefactoringActionCommandLineOptions &Options;
+};
+
+/// 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)) {
+ // Check if the selection option is supported.
+ for (const auto &Rule : this->ActionRules) {
+ if (Rule->hasSelectionRequirement()) {
+ 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));
+ break;
+ }
+ }
+ // Create the refactoring options.
+ for (const auto &Rule : this->ActionRules) {
+ CommandLineRefactoringOptionCreator OptionCreator(Category, *this,
+ Options);
+ Rule->visitRefactoringOptions(OptionCreator);
+ }
+ }
+
+ ~RefactoringActionSubcommand() { unregisterSubCommand(); }
+
+ const RefactoringActionRules &getActionRules() const { return ActionRules; }
+
+ /// Parses the "-selection" command-line argument.
+ ///
+ /// \returns true on error, false otherwise.
+ bool parseSelectionArgument() {
+ if (Selection) {
+ ParsedSelection = SourceSelectionArgument::fromString(*Selection);
+ if (!ParsedSelection)
+ return true;
+ }
+ return false;
+ }
+
+ SourceSelectionArgument *getSelection() const {
+ assert(Selection && "selection not supported!");
+ return ParsedSelection.get();
+ }
+
+ const RefactoringActionCommandLineOptions &getOptions() const {
+ return Options;
+ }
+
+private:
+ std::unique_ptr<RefactoringAction> Action;
+ RefactoringActionRules ActionRules;
+ std::unique_ptr<cl::opt<std::string>> Selection;
+ std::unique_ptr<SourceSelectionArgument> ParsedSelection;
+ RefactoringActionCommandLineOptions Options;
+};
+
+class ClangRefactorConsumer final : public ClangRefactorToolConsumerInterface {
+public:
+ ClangRefactorConsumer(AtomicChanges &Changes) : SourceChanges(&Changes) {}
+
+ void handleError(llvm::Error Err) override {
+ Optional<PartialDiagnosticAt> Diag = DiagnosticError::take(Err);
+ if (!Diag) {
+ llvm::errs() << llvm::toString(std::move(Err)) << "\n";
+ return;
+ }
+ llvm::cantFail(std::move(Err)); // This is a success.
+ DiagnosticBuilder DB(
+ getDiags().Report(Diag->first, Diag->second.getDiagID()));
+ Diag->second.Emit(DB);
+ }
+
+ void handle(AtomicChanges Changes) override {
+ SourceChanges->insert(SourceChanges->begin(), Changes.begin(),
+ Changes.end());
+ }
+
+ void handle(SymbolOccurrences Occurrences) override {
+ llvm_unreachable("symbol occurrence results are not handled yet");
+ }
+
+private:
+ AtomicChanges *SourceChanges;
+};
+
+class ClangRefactorTool {
+public:
+ ClangRefactorTool()
+ : SelectedSubcommand(nullptr), MatchingRule(nullptr),
+ Consumer(new ClangRefactorConsumer(Changes)), HasFailed(false) {
+ 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));
+ }
+ }
+
+ // Initializes the selected subcommand and refactoring rule based on the
+ // command line options.
+ llvm::Error Init() {
+ auto Subcommand = getSelectedSubcommand();
+ if (!Subcommand)
+ return Subcommand.takeError();
+ auto Rule = getMatchingRule(**Subcommand);
+ if (!Rule)
+ return Rule.takeError();
+
+ SelectedSubcommand = *Subcommand;
+ MatchingRule = *Rule;
+
+ return llvm::Error::success();
+ }
+
+ bool hasFailed() const { return HasFailed; }
+
+ using TUCallbackType = std::function<void(ASTContext &)>;
+
+ // Callback of an AST action. This invokes the matching rule on the given AST.
+ void callback(ASTContext &AST) {
+ assert(SelectedSubcommand && MatchingRule && Consumer);
+ RefactoringRuleContext Context(AST.getSourceManager());
+ Context.setASTContext(AST);
+
+ // If the selection option is test specific, we use a test-specific
+ // consumer.
+ std::unique_ptr<ClangRefactorToolConsumerInterface> TestConsumer;
+ bool HasSelection = MatchingRule->hasSelectionRequirement();
+ if (HasSelection)
+ TestConsumer = SelectedSubcommand->getSelection()->createCustomConsumer();
+ ClangRefactorToolConsumerInterface *ActiveConsumer =
+ TestConsumer ? TestConsumer.get() : Consumer.get();
+ ActiveConsumer->beginTU(AST);
+
+ auto InvokeRule = [&](RefactoringResultConsumer &Consumer) {
+ if (opts::Verbose)
+ logInvocation(*SelectedSubcommand, Context);
+ MatchingRule->invoke(*ActiveConsumer, Context);
+ };
+ if (HasSelection) {
+ assert(SelectedSubcommand->getSelection() &&
+ "Missing selection argument?");
+ if (opts::Verbose)
+ SelectedSubcommand->getSelection()->print(llvm::outs());
+ if (SelectedSubcommand->getSelection()->forAllRanges(
+ Context.getSources(), [&](SourceRange R) {
+ Context.setSelectionRange(R);
+ InvokeRule(*ActiveConsumer);
+ }))
+ HasFailed = true;
+ ActiveConsumer->endTU();
+ return;
+ }
+ InvokeRule(*ActiveConsumer);
+ ActiveConsumer->endTU();
+ }
+
+ llvm::Expected<std::unique_ptr<FrontendActionFactory>>
+ getFrontendActionFactory() {
+ class ToolASTConsumer : public ASTConsumer {
+ public:
+ TUCallbackType Callback;
+ ToolASTConsumer(TUCallbackType Callback)
+ : Callback(std::move(Callback)) {}
+
+ void HandleTranslationUnit(ASTContext &Context) override {
+ Callback(Context);
+ }
+ };
+ class ToolASTAction : public ASTFrontendAction {
+ public:
+ explicit ToolASTAction(TUCallbackType Callback)
+ : Callback(std::move(Callback)) {}
+
+ protected:
+ std::unique_ptr<clang::ASTConsumer>
+ CreateASTConsumer(clang::CompilerInstance &compiler,
+ StringRef /* dummy */) override {
+ std::unique_ptr<clang::ASTConsumer> Consumer{
+ new ToolASTConsumer(Callback)};
+ return Consumer;
+ }
+
+ private:
+ TUCallbackType Callback;
+ };
+
+ class ToolActionFactory : public FrontendActionFactory {
+ public:
+ ToolActionFactory(TUCallbackType Callback)
+ : Callback(std::move(Callback)) {}
+
+ FrontendAction *create() override { return new ToolASTAction(Callback); }
+
+ private:
+ TUCallbackType Callback;
+ };
+
+ return llvm::make_unique<ToolActionFactory>(
+ [this](ASTContext &AST) { return callback(AST); });
+ }
+
+ // FIXME(ioeric): this seems to only works for changes in a single file at
+ // this point.
+ bool applySourceChanges() {
+ std::set<std::string> Files;
+ for (const auto &Change : Changes)
+ Files.insert(Change.getFilePath());
+ // FIXME: Add automatic formatting support as well.
+ tooling::ApplyChangesSpec Spec;
+ // FIXME: We should probably cleanup the result by default as well.
+ Spec.Cleanup = false;
+ for (const auto &File : Files) {
+ llvm::ErrorOr<std::unique_ptr<llvm::MemoryBuffer>> BufferErr =
+ llvm::MemoryBuffer::getFile(File);
+ if (!BufferErr) {
+ llvm::errs() << "error: failed to open " << File << " for rewriting\n";
+ return true;
+ }
+ auto Result = tooling::applyAtomicChanges(File, (*BufferErr)->getBuffer(),
+ Changes, Spec);
+ if (!Result) {
+ llvm::errs() << toString(Result.takeError());
+ return true;
+ }
+
+ if (opts::Inplace) {
+ std::error_code EC;
+ llvm::raw_fd_ostream OS(File, EC, llvm::sys::fs::F_Text);
+ if (EC) {
+ llvm::errs() << EC.message() << "\n";
+ return true;
+ }
+ OS << *Result;
+ continue;
+ }
+
+ llvm::outs() << *Result;
+ }
+ return false;
+ }
+
+private:
+ /// Logs an individual refactoring action invocation to STDOUT.
+ void logInvocation(RefactoringActionSubcommand &Subcommand,
+ const RefactoringRuleContext &Context) {
+ 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";
+ }
+ }
+
+ llvm::Expected<RefactoringActionRule *>
+ getMatchingRule(RefactoringActionSubcommand &Subcommand) {
+ SmallVector<RefactoringActionRule *, 4> MatchingRules;
+ llvm::StringSet<> MissingOptions;
+
+ for (const auto &Rule : Subcommand.getActionRules()) {
+ CommandLineRefactoringOptionVisitor Visitor(Subcommand.getOptions());
+ Rule->visitRefactoringOptions(Visitor);
+ if (Visitor.getMissingRequiredOptions().empty()) {
+ if (!Rule->hasSelectionRequirement()) {
+ MatchingRules.push_back(Rule.get());
+ } else {
+ Subcommand.parseSelectionArgument();
+ if (Subcommand.getSelection()) {
+ MatchingRules.push_back(Rule.get());
+ } else {
+ MissingOptions.insert("selection");
+ }
+ }
+ }
+ for (const RefactoringOption *Opt : Visitor.getMissingRequiredOptions())
+ MissingOptions.insert(Opt->getName());
+ }
+ if (MatchingRules.empty()) {
+ std::string Error;
+ llvm::raw_string_ostream OS(Error);
+ OS << "ERROR: '" << Subcommand.getName()
+ << "' can't be invoked with the given arguments:\n";
+ for (const auto &Opt : MissingOptions)
+ OS << " missing '-" << Opt.getKey() << "' option\n";
+ OS.flush();
+ return llvm::make_error<llvm::StringError>(
+ Error, llvm::inconvertibleErrorCode());
+ }
+ if (MatchingRules.size() != 1) {
+ return llvm::make_error<llvm::StringError>(
+ llvm::Twine("ERROR: more than one matching rule of action") +
+ Subcommand.getName() + "was found with given options.",
+ llvm::inconvertibleErrorCode());
+ }
+ return MatchingRules.front();
+ }
+ // 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.
+ llvm::Expected<RefactoringActionSubcommand *> getSelectedSubcommand() {
+ auto It = llvm::find_if(
+ SubCommands,
+ [](const std::unique_ptr<RefactoringActionSubcommand> &SubCommand) {
+ return !!(*SubCommand);
+ });
+ if (It == SubCommands.end()) {
+ std::string Error;
+ llvm::raw_string_ostream OS(Error);
+ OS << "error: no refactoring action given\n";
+ OS << "note: the following actions are supported:\n";
+ for (const auto &Subcommand : SubCommands)
+ OS.indent(2) << Subcommand->getName() << "\n";
+ OS.flush();
+ return llvm::make_error<llvm::StringError>(
+ Error, llvm::inconvertibleErrorCode());
+ }
+ RefactoringActionSubcommand *Subcommand = &(**It);
+ return Subcommand;
+ }
+
+ std::vector<std::unique_ptr<RefactoringActionSubcommand>> SubCommands;
+ RefactoringActionSubcommand *SelectedSubcommand;
+ RefactoringActionRule *MatchingRule;
+ std::unique_ptr<ClangRefactorToolConsumerInterface> Consumer;
+ AtomicChanges Changes;
+ bool HasFailed;
+};
+
+} // end anonymous namespace
+
+int main(int argc, const char **argv) {
+ llvm::sys::PrintStackTraceOnErrorSignal(argv[0]);
+
+ ClangRefactorTool RefactorTool;
+
+ CommonOptionsParser Options(
+ argc, argv, cl::GeneralCategory, cl::ZeroOrMore,
+ "Clang-based refactoring tool for C, C++ and Objective-C");
+
+ if (auto Err = RefactorTool.Init()) {
+ llvm::errs() << llvm::toString(std::move(Err)) << "\n";
+ return 1;
+ }
+
+ auto ActionFactory = RefactorTool.getFrontendActionFactory();
+ if (!ActionFactory) {
+ llvm::errs() << llvm::toString(ActionFactory.takeError()) << "\n";
+ return 1;
+ }
+ ClangTool Tool(Options.getCompilations(), Options.getSourcePathList());
+ bool Failed = false;
+ if (Tool.run(ActionFactory->get()) != 0) {
+ llvm::errs() << "Failed to run refactoring action on files\n";
+ // It is possible that TUs are broken while changes are generated correctly,
+ // so we still try applying changes.
+ Failed = true;
+ }
+ return RefactorTool.applySourceChanges() || Failed ||
+ RefactorTool.hasFailed();
+}
diff --git a/tools/clang-refactor/TestSupport.cpp b/tools/clang-refactor/TestSupport.cpp
new file mode 100644
index 000000000000..9331dfd92eb4
--- /dev/null
+++ b/tools/clang-refactor/TestSupport.cpp
@@ -0,0 +1,392 @@
+//===--- 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/DiagnosticError.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/LineIterator.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 ClangRefactorToolConsumerInterface {
+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) {
+ handleAllErrors(
+ Result.takeError(),
+ [&](StringError &Err) { ErrorMessage = Err.getMessage(); },
+ [&](DiagnosticError &Err) {
+ const PartialDiagnosticAt &Diag = Err.getDiagnostic();
+ llvm::SmallString<100> DiagText;
+ Diag.second.EmitToString(getDiags(), DiagText);
+ ErrorMessage = DiagText.str().str();
+ });
+ }
+ 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::outs() << TestGroup.Ranges.size() << " '" << TestGroup.Name
+ << "' results:\n";
+ llvm::outs() << *CanonicalErrorMessage << "\n";
+ } else {
+ llvm::outs() << TestGroup.Ranges.size() << " '" << TestGroup.Name
+ << "' results:\n";
+ if (printRewrittenSources(*CanonicalResult, llvm::outs()))
+ return true;
+ }
+ }
+ return Failed;
+}
+
+std::unique_ptr<ClangRefactorToolConsumerInterface>
+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);
+}
+
+static unsigned addEndLineOffsetAndEndColumn(StringRef Source, unsigned Offset,
+ unsigned LineNumberOffset,
+ unsigned Column) {
+ StringRef Line = Source.drop_front(Offset);
+ unsigned LineOffset = 0;
+ for (; LineNumberOffset != 0; --LineNumberOffset) {
+ size_t NewlinePos = Line.find_first_of("\r\n");
+ // Line offset goes out of bounds.
+ if (NewlinePos == StringRef::npos)
+ break;
+ LineOffset += NewlinePos + 1;
+ Line = Line.drop_front(NewlinePos + 1);
+ }
+ // Source now points to the line at +lineOffset;
+ size_t LineStart = Source.find_last_of("\r\n", /*From=*/Offset + LineOffset);
+ return addColumnOffset(
+ Source, LineStart == StringRef::npos ? 0 : LineStart + 1, Column - 1);
+}
+
+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();
+
+ // See the doc comment for this function for the explanation of this
+ // syntax.
+ static Regex RangeRegex("range[[:blank:]]*([[:alpha:]_]*)?[[:blank:]]*=[[:"
+ "blank:]]*(\\+[[:digit:]]+)?[[: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;
+ // Try to detect mistyped 'range:' comments to ensure tests don't miss
+ // anything.
+ auto DetectMistypedCommand = [&]() -> bool {
+ 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 false;
+ };
+ // Allow CHECK: comments to contain range= commands.
+ if (!RangeRegex.match(Comment, &Matches) || Comment.contains("CHECK")) {
+ if (DetectMistypedCommand())
+ 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");
+ }
+ Offset = addColumnOffset(Source, Offset, ColumnOffset);
+ unsigned EndOffset;
+
+ if (!Matches[3].empty()) {
+ static Regex EndLocRegex(
+ "->[[:blank:]]*(\\+[[:digit:]]+):([[:digit:]]+)");
+ SmallVector<StringRef, 4> EndLocMatches;
+ if (!EndLocRegex.match(Matches[3], &EndLocMatches)) {
+ if (DetectMistypedCommand())
+ return None;
+ continue;
+ }
+ unsigned EndLineOffset = 0, EndColumn = 0;
+ if (EndLocMatches[1].drop_front().getAsInteger(10, EndLineOffset) ||
+ EndLocMatches[2].getAsInteger(10, EndColumn))
+ assert(false && "regex should have produced a number");
+ EndOffset = addEndLineOffsetAndEndColumn(Source, Offset, EndLineOffset,
+ EndColumn);
+ } else {
+ EndOffset = Offset;
+ }
+ TestSelectionRange Range = {Offset, EndOffset};
+ 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
diff --git a/tools/clang-refactor/TestSupport.h b/tools/clang-refactor/TestSupport.h
new file mode 100644
index 000000000000..101f35bd94f3
--- /dev/null
+++ b/tools/clang-refactor/TestSupport.h
@@ -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 "ToolRefactoringResultConsumer.h"
+#include "clang/Basic/LLVM.h"
+#include "clang/Basic/SourceLocation.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<ClangRefactorToolConsumerInterface> 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
diff --git a/tools/clang-refactor/ToolRefactoringResultConsumer.h b/tools/clang-refactor/ToolRefactoringResultConsumer.h
new file mode 100644
index 000000000000..64a994d88b9d
--- /dev/null
+++ b/tools/clang-refactor/ToolRefactoringResultConsumer.h
@@ -0,0 +1,48 @@
+//===--- ToolRefactoringResultConsumer.h - ----------------------*- C++ -*-===//
+//
+// The LLVM Compiler Infrastructure
+//
+// This file is distributed under the University of Illinois Open Source
+// License. See LICENSE.TXT for details.
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_CLANG_TOOLS_CLANG_REFACTOR_TOOL_REFACTORING_RESULT_CONSUMER_H
+#define LLVM_CLANG_TOOLS_CLANG_REFACTOR_TOOL_REFACTORING_RESULT_CONSUMER_H
+
+#include "clang/AST/ASTContext.h"
+#include "clang/Tooling/Refactoring/RefactoringResultConsumer.h"
+
+namespace clang {
+namespace refactor {
+
+/// An interface that subclasses the \c RefactoringResultConsumer interface
+/// that stores the reference to the TU-specific diagnostics engine.
+class ClangRefactorToolConsumerInterface
+ : public tooling::RefactoringResultConsumer {
+public:
+ /// Called when a TU is entered.
+ void beginTU(ASTContext &Context) {
+ assert(!Diags && "Diags has been set");
+ Diags = &Context.getDiagnostics();
+ }
+
+ /// Called when the tool is done with a TU.
+ void endTU() {
+ assert(Diags && "Diags unset");
+ Diags = nullptr;
+ }
+
+ DiagnosticsEngine &getDiags() const {
+ assert(Diags && "no diags");
+ return *Diags;
+ }
+
+private:
+ DiagnosticsEngine *Diags = nullptr;
+};
+
+} // end namespace refactor
+} // end namespace clang
+
+#endif // LLVM_CLANG_TOOLS_CLANG_REFACTOR_TOOL_REFACTORING_RESULT_CONSUMER_H