blob: 47b1dfeecaf14ce43dcc9f1e64cefbed4c9a2fff [file] [log] [blame]
Alex Lorenzb54ef6a2017-09-14 10:06:52 +00001//===--- ClangRefactor.cpp - Clang-based refactoring tool -----------------===//
2//
3// The LLVM Compiler Infrastructure
4//
5// This file is distributed under the University of Illinois Open Source
6// License. See LICENSE.TXT for details.
7//
8//===----------------------------------------------------------------------===//
9///
10/// \file
11/// \brief This file implements a clang-refactor tool that performs various
12/// source transformations.
13///
14//===----------------------------------------------------------------------===//
15
16#include "TestSupport.h"
17#include "clang/Rewrite/Core/Rewriter.h"
Alex Lorenz3d712c42017-09-14 13:16:14 +000018#include "clang/Tooling/CommonOptionsParser.h"
Alex Lorenzb54ef6a2017-09-14 10:06:52 +000019#include "clang/Tooling/Refactoring.h"
20#include "clang/Tooling/Refactoring/RefactoringAction.h"
21#include "clang/Tooling/Refactoring/Rename/RenamingAction.h"
22#include "clang/Tooling/Tooling.h"
23#include "llvm/Support/CommandLine.h"
24#include "llvm/Support/FileSystem.h"
25#include "llvm/Support/raw_ostream.h"
26#include <string>
27
28using namespace clang;
29using namespace tooling;
30using namespace refactor;
31namespace cl = llvm::cl;
32
33namespace opts {
34
Alex Lorenzcdb9a052017-10-06 19:49:29 +000035static cl::OptionCategory CommonRefactorOptions("Common refactoring options");
Alex Lorenzb54ef6a2017-09-14 10:06:52 +000036
Alex Lorenzb54ef6a2017-09-14 10:06:52 +000037static cl::opt<bool> Verbose("v", cl::desc("Use verbose output"),
Alex Lorenzcdb9a052017-10-06 19:49:29 +000038 cl::cat(CommonRefactorOptions),
Alex Lorenzb54ef6a2017-09-14 10:06:52 +000039 cl::sub(*cl::AllSubCommands));
40} // end namespace opts
41
42namespace {
43
44/// Stores the parsed `-selection` argument.
45class SourceSelectionArgument {
46public:
47 virtual ~SourceSelectionArgument() {}
48
49 /// Parse the `-selection` argument.
50 ///
51 /// \returns A valid argument when the parse succedeed, null otherwise.
52 static std::unique_ptr<SourceSelectionArgument> fromString(StringRef Value);
53
54 /// Prints any additional state associated with the selection argument to
55 /// the given output stream.
56 virtual void print(raw_ostream &OS) = 0;
57
58 /// Returns a replacement refactoring result consumer (if any) that should
59 /// consume the results of a refactoring operation.
60 ///
61 /// The replacement refactoring result consumer is used by \c
62 /// TestSourceSelectionArgument to inject a test-specific result handling
63 /// logic into the refactoring operation. The test-specific consumer
64 /// ensures that the individual results in a particular test group are
65 /// identical.
66 virtual std::unique_ptr<RefactoringResultConsumer> createCustomConsumer() {
67 return nullptr;
68 }
69
70 /// Runs the give refactoring function for each specified selection.
71 ///
72 /// \returns true if an error occurred, false otherwise.
73 virtual bool
74 forAllRanges(const SourceManager &SM,
75 llvm::function_ref<void(SourceRange R)> Callback) = 0;
76};
77
78/// Stores the parsed -selection=test:<filename> option.
79class TestSourceSelectionArgument final : public SourceSelectionArgument {
80public:
81 TestSourceSelectionArgument(TestSelectionRangesInFile TestSelections)
82 : TestSelections(std::move(TestSelections)) {}
83
84 void print(raw_ostream &OS) override { TestSelections.dump(OS); }
85
86 std::unique_ptr<RefactoringResultConsumer> createCustomConsumer() override {
87 return TestSelections.createConsumer();
88 }
89
90 /// Testing support: invokes the selection action for each selection range in
91 /// the test file.
92 bool forAllRanges(const SourceManager &SM,
93 llvm::function_ref<void(SourceRange R)> Callback) override {
94 return TestSelections.foreachRange(SM, Callback);
95 }
96
97private:
98 TestSelectionRangesInFile TestSelections;
99};
100
101std::unique_ptr<SourceSelectionArgument>
102SourceSelectionArgument::fromString(StringRef Value) {
103 if (Value.startswith("test:")) {
104 StringRef Filename = Value.drop_front(strlen("test:"));
105 Optional<TestSelectionRangesInFile> ParsedTestSelection =
106 findTestSelectionRanges(Filename);
107 if (!ParsedTestSelection)
108 return nullptr; // A parsing error was already reported.
109 return llvm::make_unique<TestSourceSelectionArgument>(
110 std::move(*ParsedTestSelection));
111 }
112 // FIXME: Support true selection ranges.
113 llvm::errs() << "error: '-selection' option must be specified using "
114 "<file>:<line>:<column> or "
115 "<file>:<line>:<column>-<line>:<column> format";
116 return nullptr;
117}
118
119/// A subcommand that corresponds to individual refactoring action.
120class RefactoringActionSubcommand : public cl::SubCommand {
121public:
122 RefactoringActionSubcommand(std::unique_ptr<RefactoringAction> Action,
123 RefactoringActionRules ActionRules,
124 cl::OptionCategory &Category)
125 : SubCommand(Action->getCommand(), Action->getDescription()),
126 Action(std::move(Action)), ActionRules(std::move(ActionRules)) {
Alex Lorenzb54ef6a2017-09-14 10:06:52 +0000127 // Check if the selection option is supported.
128 bool HasSelection = false;
129 for (const auto &Rule : this->ActionRules) {
130 if ((HasSelection = Rule->hasSelectionRequirement()))
131 break;
132 }
133 if (HasSelection) {
134 Selection = llvm::make_unique<cl::opt<std::string>>(
135 "selection",
136 cl::desc("The selected source range in which the refactoring should "
137 "be initiated (<file>:<line>:<column>-<line>:<column> or "
138 "<file>:<line>:<column>)"),
139 cl::cat(Category), cl::sub(*this));
140 }
141 }
142
143 ~RefactoringActionSubcommand() { unregisterSubCommand(); }
144
145 const RefactoringActionRules &getActionRules() const { return ActionRules; }
146
147 /// Parses the command-line arguments that are specific to this rule.
148 ///
149 /// \returns true on error, false otherwise.
150 bool parseArguments() {
151 if (Selection) {
152 ParsedSelection = SourceSelectionArgument::fromString(*Selection);
153 if (!ParsedSelection)
154 return true;
155 }
156 return false;
157 }
158
159 SourceSelectionArgument *getSelection() const {
160 assert(Selection && "selection not supported!");
161 return ParsedSelection.get();
162 }
Alex Lorenzb54ef6a2017-09-14 10:06:52 +0000163private:
164 std::unique_ptr<RefactoringAction> Action;
165 RefactoringActionRules ActionRules;
Alex Lorenzb54ef6a2017-09-14 10:06:52 +0000166 std::unique_ptr<cl::opt<std::string>> Selection;
167 std::unique_ptr<SourceSelectionArgument> ParsedSelection;
168};
169
170class ClangRefactorConsumer : public RefactoringResultConsumer {
171public:
172 void handleError(llvm::Error Err) {
173 llvm::errs() << llvm::toString(std::move(Err)) << "\n";
174 }
175
176 // FIXME: Consume atomic changes and apply them to files.
177};
178
179class ClangRefactorTool {
180public:
181 std::vector<std::unique_ptr<RefactoringActionSubcommand>> SubCommands;
182
183 ClangRefactorTool() {
184 std::vector<std::unique_ptr<RefactoringAction>> Actions =
185 createRefactoringActions();
186
187 // Actions must have unique command names so that we can map them to one
188 // subcommand.
189 llvm::StringSet<> CommandNames;
190 for (const auto &Action : Actions) {
191 if (!CommandNames.insert(Action->getCommand()).second) {
192 llvm::errs() << "duplicate refactoring action command '"
193 << Action->getCommand() << "'!";
194 exit(1);
195 }
196 }
197
198 // Create subcommands and command-line options.
199 for (auto &Action : Actions) {
200 SubCommands.push_back(llvm::make_unique<RefactoringActionSubcommand>(
201 std::move(Action), Action->createActiveActionRules(),
202 opts::CommonRefactorOptions));
203 }
204 }
205
206 using TUCallbackType = llvm::function_ref<void(ASTContext &)>;
207
208 /// Parses the translation units that were given to the subcommand using
209 /// the 'sources' option and invokes the callback for each parsed
210 /// translation unit.
Alex Lorenz3d712c42017-09-14 13:16:14 +0000211 bool foreachTranslationUnit(const CompilationDatabase &DB,
212 ArrayRef<std::string> Sources,
Alex Lorenzb54ef6a2017-09-14 10:06:52 +0000213 TUCallbackType Callback) {
Alex Lorenzb54ef6a2017-09-14 10:06:52 +0000214 class ToolASTConsumer : public ASTConsumer {
215 public:
216 TUCallbackType Callback;
217 ToolASTConsumer(TUCallbackType Callback) : Callback(Callback) {}
218
219 void HandleTranslationUnit(ASTContext &Context) override {
220 Callback(Context);
221 }
222 };
223 class ActionWrapper {
224 public:
225 TUCallbackType Callback;
226 ActionWrapper(TUCallbackType Callback) : Callback(Callback) {}
227
228 std::unique_ptr<ASTConsumer> newASTConsumer() {
Haojian Wu21cc1382017-10-10 09:48:38 +0000229 return llvm::make_unique<ToolASTConsumer>(Callback);
Alex Lorenzb54ef6a2017-09-14 10:06:52 +0000230 }
231 };
232
Alex Lorenz3d712c42017-09-14 13:16:14 +0000233 ClangTool Tool(DB, Sources);
Haojian Wu21cc1382017-10-10 09:48:38 +0000234 ActionWrapper ToolAction(Callback);
Alex Lorenzb54ef6a2017-09-14 10:06:52 +0000235 std::unique_ptr<tooling::FrontendActionFactory> Factory =
236 tooling::newFrontendActionFactory(&ToolAction);
237 return Tool.run(Factory.get());
238 }
239
240 /// Logs an individual refactoring action invocation to STDOUT.
241 void logInvocation(RefactoringActionSubcommand &Subcommand,
242 const RefactoringRuleContext &Context) {
243 if (!opts::Verbose)
244 return;
245 llvm::outs() << "invoking action '" << Subcommand.getName() << "':\n";
246 if (Context.getSelectionRange().isValid()) {
247 SourceRange R = Context.getSelectionRange();
248 llvm::outs() << " -selection=";
249 R.getBegin().print(llvm::outs(), Context.getSources());
250 llvm::outs() << " -> ";
251 R.getEnd().print(llvm::outs(), Context.getSources());
252 llvm::outs() << "\n";
253 }
254 }
255
Alex Lorenz3d712c42017-09-14 13:16:14 +0000256 bool invokeAction(RefactoringActionSubcommand &Subcommand,
257 const CompilationDatabase &DB,
258 ArrayRef<std::string> Sources) {
Alex Lorenzb54ef6a2017-09-14 10:06:52 +0000259 // Find a set of matching rules.
260 SmallVector<RefactoringActionRule *, 4> MatchingRules;
261 llvm::StringSet<> MissingOptions;
262
263 bool HasSelection = false;
264 for (const auto &Rule : Subcommand.getActionRules()) {
265 if (Rule->hasSelectionRequirement()) {
266 HasSelection = true;
Alex Lorenzcdb9a052017-10-06 19:49:29 +0000267 if (Subcommand.getSelection())
268 MatchingRules.push_back(Rule.get());
269 else
Alex Lorenzb54ef6a2017-09-14 10:06:52 +0000270 MissingOptions.insert("selection");
271 }
Alex Lorenzcdb9a052017-10-06 19:49:29 +0000272 // FIXME (Alex L): Support custom options.
Alex Lorenzb54ef6a2017-09-14 10:06:52 +0000273 }
274 if (MatchingRules.empty()) {
275 llvm::errs() << "error: '" << Subcommand.getName()
276 << "' can't be invoked with the given arguments:\n";
277 for (const auto &Opt : MissingOptions)
278 llvm::errs() << " missing '-" << Opt.getKey() << "' option\n";
279 return true;
280 }
281
282 bool HasFailed = false;
283 ClangRefactorConsumer Consumer;
Alex Lorenz3d712c42017-09-14 13:16:14 +0000284 if (foreachTranslationUnit(DB, Sources, [&](ASTContext &AST) {
Alex Lorenzb54ef6a2017-09-14 10:06:52 +0000285 RefactoringRuleContext Context(AST.getSourceManager());
286 Context.setASTContext(AST);
287
288 auto InvokeRule = [&](RefactoringResultConsumer &Consumer) {
289 logInvocation(Subcommand, Context);
290 for (RefactoringActionRule *Rule : MatchingRules) {
291 if (!Rule->hasSelectionRequirement())
292 continue;
293 Rule->invoke(Consumer, Context);
294 return;
295 }
296 // FIXME (Alex L): If more than one initiation succeeded, then the
297 // rules are ambiguous.
298 llvm_unreachable(
299 "The action must have at least one selection rule");
300 };
301
302 if (HasSelection) {
303 assert(Subcommand.getSelection() && "Missing selection argument?");
304 if (opts::Verbose)
305 Subcommand.getSelection()->print(llvm::outs());
306 auto CustomConsumer =
307 Subcommand.getSelection()->createCustomConsumer();
308 if (Subcommand.getSelection()->forAllRanges(
309 Context.getSources(), [&](SourceRange R) {
310 Context.setSelectionRange(R);
311 InvokeRule(CustomConsumer ? *CustomConsumer : Consumer);
312 }))
313 HasFailed = true;
314 return;
315 }
316 // FIXME (Alex L): Implement non-selection based invocation path.
317 }))
318 return true;
319 return HasFailed;
320 }
321};
322
323} // end anonymous namespace
324
325int main(int argc, const char **argv) {
326 ClangRefactorTool Tool;
327
Alex Lorenz3d712c42017-09-14 13:16:14 +0000328 CommonOptionsParser Options(
Alex Lorenzcdb9a052017-10-06 19:49:29 +0000329 argc, argv, opts::CommonRefactorOptions, cl::ZeroOrMore,
Alex Lorenz3d712c42017-09-14 13:16:14 +0000330 "Clang-based refactoring tool for C, C++ and Objective-C");
Alex Lorenzb54ef6a2017-09-14 10:06:52 +0000331
332 // Figure out which action is specified by the user. The user must specify
333 // the action using a command-line subcommand, e.g. the invocation
334 // `clang-refactor local-rename` corresponds to the `LocalRename` refactoring
335 // action. All subcommands must have a unique names. This allows us to figure
336 // out which refactoring action should be invoked by looking at the first
337 // subcommand that's enabled by LLVM's command-line parser.
338 auto It = llvm::find_if(
339 Tool.SubCommands,
340 [](const std::unique_ptr<RefactoringActionSubcommand> &SubCommand) {
341 return !!(*SubCommand);
342 });
343 if (It == Tool.SubCommands.end()) {
344 llvm::errs() << "error: no refactoring action given\n";
345 llvm::errs() << "note: the following actions are supported:\n";
346 for (const auto &Subcommand : Tool.SubCommands)
347 llvm::errs().indent(2) << Subcommand->getName() << "\n";
348 return 1;
349 }
350 RefactoringActionSubcommand &ActionCommand = **It;
351
Alex Lorenzb54ef6a2017-09-14 10:06:52 +0000352 if (ActionCommand.parseArguments())
353 return 1;
Alex Lorenz3d712c42017-09-14 13:16:14 +0000354 if (Tool.invokeAction(ActionCommand, Options.getCompilations(),
355 Options.getSourcePathList()))
Alex Lorenzb54ef6a2017-09-14 10:06:52 +0000356 return 1;
357
358 return 0;
359}