summaryrefslogtreecommitdiffstats
path: root/clang/tools/clang-refactor/ClangRefactor.cpp
diff options
context:
space:
mode:
authorAlex Lorenz <arphaman@gmail.com>2017-09-14 10:06:52 +0000
committerAlex Lorenz <arphaman@gmail.com>2017-09-14 10:06:52 +0000
commitb54ef6a2a4c84293f64f90ea344b129eabbb0cd1 (patch)
treebeea03e3a80eaf8bc338ebb66b247f3363562975 /clang/tools/clang-refactor/ClangRefactor.cpp
parentd7b3add7c4a25a558794bf1c1ca30f865b8c6dd7 (diff)
downloadbcm5719-llvm-b54ef6a2a4c84293f64f90ea344b129eabbb0cd1.tar.gz
bcm5719-llvm-b54ef6a2a4c84293f64f90ea344b129eabbb0cd1.zip
[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
Diffstat (limited to 'clang/tools/clang-refactor/ClangRefactor.cpp')
-rw-r--r--clang/tools/clang-refactor/ClangRefactor.cpp391
1 files changed, 391 insertions, 0 deletions
diff --git a/clang/tools/clang-refactor/ClangRefactor.cpp b/clang/tools/clang-refactor/ClangRefactor.cpp
new file mode 100644
index 00000000000..047e276d1b8
--- /dev/null
+++ b/clang/tools/clang-refactor/ClangRefactor.cpp
@@ -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;
+}
OpenPOWER on IntegriCloud