blob: 047e276d1b824cd6b278f224b94a3fb00f6c181c [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"
18#include "clang/Tooling/Refactoring.h"
19#include "clang/Tooling/Refactoring/RefactoringAction.h"
20#include "clang/Tooling/Refactoring/Rename/RenamingAction.h"
21#include "clang/Tooling/Tooling.h"
22#include "llvm/Support/CommandLine.h"
23#include "llvm/Support/FileSystem.h"
24#include "llvm/Support/raw_ostream.h"
25#include <string>
26
27using namespace clang;
28using namespace tooling;
29using namespace refactor;
30namespace cl = llvm::cl;
31
32namespace opts {
33
34static cl::OptionCategory CommonRefactorOptions("Common refactoring options");
35
36static cl::opt<bool>
37 NoDatabases("no-dbs",
38 cl::desc("Ignore external databases including Clang's "
39 "compilation database and indexer stores"),
40 cl::cat(CommonRefactorOptions), cl::sub(*cl::AllSubCommands));
41
42static cl::opt<bool> Verbose("v", cl::desc("Use verbose output"),
43 cl::cat(CommonRefactorOptions),
44 cl::sub(*cl::AllSubCommands));
45} // end namespace opts
46
47namespace {
48
49/// Stores the parsed `-selection` argument.
50class SourceSelectionArgument {
51public:
52 virtual ~SourceSelectionArgument() {}
53
54 /// Parse the `-selection` argument.
55 ///
56 /// \returns A valid argument when the parse succedeed, null otherwise.
57 static std::unique_ptr<SourceSelectionArgument> fromString(StringRef Value);
58
59 /// Prints any additional state associated with the selection argument to
60 /// the given output stream.
61 virtual void print(raw_ostream &OS) = 0;
62
63 /// Returns a replacement refactoring result consumer (if any) that should
64 /// consume the results of a refactoring operation.
65 ///
66 /// The replacement refactoring result consumer is used by \c
67 /// TestSourceSelectionArgument to inject a test-specific result handling
68 /// logic into the refactoring operation. The test-specific consumer
69 /// ensures that the individual results in a particular test group are
70 /// identical.
71 virtual std::unique_ptr<RefactoringResultConsumer> createCustomConsumer() {
72 return nullptr;
73 }
74
75 /// Runs the give refactoring function for each specified selection.
76 ///
77 /// \returns true if an error occurred, false otherwise.
78 virtual bool
79 forAllRanges(const SourceManager &SM,
80 llvm::function_ref<void(SourceRange R)> Callback) = 0;
81};
82
83/// Stores the parsed -selection=test:<filename> option.
84class TestSourceSelectionArgument final : public SourceSelectionArgument {
85public:
86 TestSourceSelectionArgument(TestSelectionRangesInFile TestSelections)
87 : TestSelections(std::move(TestSelections)) {}
88
89 void print(raw_ostream &OS) override { TestSelections.dump(OS); }
90
91 std::unique_ptr<RefactoringResultConsumer> createCustomConsumer() override {
92 return TestSelections.createConsumer();
93 }
94
95 /// Testing support: invokes the selection action for each selection range in
96 /// the test file.
97 bool forAllRanges(const SourceManager &SM,
98 llvm::function_ref<void(SourceRange R)> Callback) override {
99 return TestSelections.foreachRange(SM, Callback);
100 }
101
102private:
103 TestSelectionRangesInFile TestSelections;
104};
105
106std::unique_ptr<SourceSelectionArgument>
107SourceSelectionArgument::fromString(StringRef Value) {
108 if (Value.startswith("test:")) {
109 StringRef Filename = Value.drop_front(strlen("test:"));
110 Optional<TestSelectionRangesInFile> ParsedTestSelection =
111 findTestSelectionRanges(Filename);
112 if (!ParsedTestSelection)
113 return nullptr; // A parsing error was already reported.
114 return llvm::make_unique<TestSourceSelectionArgument>(
115 std::move(*ParsedTestSelection));
116 }
117 // FIXME: Support true selection ranges.
118 llvm::errs() << "error: '-selection' option must be specified using "
119 "<file>:<line>:<column> or "
120 "<file>:<line>:<column>-<line>:<column> format";
121 return nullptr;
122}
123
124/// A subcommand that corresponds to individual refactoring action.
125class RefactoringActionSubcommand : public cl::SubCommand {
126public:
127 RefactoringActionSubcommand(std::unique_ptr<RefactoringAction> Action,
128 RefactoringActionRules ActionRules,
129 cl::OptionCategory &Category)
130 : SubCommand(Action->getCommand(), Action->getDescription()),
131 Action(std::move(Action)), ActionRules(std::move(ActionRules)) {
132 Sources = llvm::make_unique<cl::list<std::string>>(
133 cl::Positional, cl::ZeroOrMore, cl::desc("<source0> [... <sourceN>]"),
134 cl::cat(Category), cl::sub(*this));
135
136 // Check if the selection option is supported.
137 bool HasSelection = false;
138 for (const auto &Rule : this->ActionRules) {
139 if ((HasSelection = Rule->hasSelectionRequirement()))
140 break;
141 }
142 if (HasSelection) {
143 Selection = llvm::make_unique<cl::opt<std::string>>(
144 "selection",
145 cl::desc("The selected source range in which the refactoring should "
146 "be initiated (<file>:<line>:<column>-<line>:<column> or "
147 "<file>:<line>:<column>)"),
148 cl::cat(Category), cl::sub(*this));
149 }
150 }
151
152 ~RefactoringActionSubcommand() { unregisterSubCommand(); }
153
154 const RefactoringActionRules &getActionRules() const { return ActionRules; }
155
156 /// Parses the command-line arguments that are specific to this rule.
157 ///
158 /// \returns true on error, false otherwise.
159 bool parseArguments() {
160 if (Selection) {
161 ParsedSelection = SourceSelectionArgument::fromString(*Selection);
162 if (!ParsedSelection)
163 return true;
164 }
165 return false;
166 }
167
168 SourceSelectionArgument *getSelection() const {
169 assert(Selection && "selection not supported!");
170 return ParsedSelection.get();
171 }
172
173 ArrayRef<std::string> getSources() const { return *Sources; }
174
175private:
176 std::unique_ptr<RefactoringAction> Action;
177 RefactoringActionRules ActionRules;
178 std::unique_ptr<cl::list<std::string>> Sources;
179 std::unique_ptr<cl::opt<std::string>> Selection;
180 std::unique_ptr<SourceSelectionArgument> ParsedSelection;
181};
182
183class ClangRefactorConsumer : public RefactoringResultConsumer {
184public:
185 void handleError(llvm::Error Err) {
186 llvm::errs() << llvm::toString(std::move(Err)) << "\n";
187 }
188
189 // FIXME: Consume atomic changes and apply them to files.
190};
191
192class ClangRefactorTool {
193public:
194 std::vector<std::unique_ptr<RefactoringActionSubcommand>> SubCommands;
195
196 ClangRefactorTool() {
197 std::vector<std::unique_ptr<RefactoringAction>> Actions =
198 createRefactoringActions();
199
200 // Actions must have unique command names so that we can map them to one
201 // subcommand.
202 llvm::StringSet<> CommandNames;
203 for (const auto &Action : Actions) {
204 if (!CommandNames.insert(Action->getCommand()).second) {
205 llvm::errs() << "duplicate refactoring action command '"
206 << Action->getCommand() << "'!";
207 exit(1);
208 }
209 }
210
211 // Create subcommands and command-line options.
212 for (auto &Action : Actions) {
213 SubCommands.push_back(llvm::make_unique<RefactoringActionSubcommand>(
214 std::move(Action), Action->createActiveActionRules(),
215 opts::CommonRefactorOptions));
216 }
217 }
218
219 using TUCallbackType = llvm::function_ref<void(ASTContext &)>;
220
221 /// Parses the translation units that were given to the subcommand using
222 /// the 'sources' option and invokes the callback for each parsed
223 /// translation unit.
224 bool foreachTranslationUnit(RefactoringActionSubcommand &Subcommand,
225 TUCallbackType Callback) {
226 std::unique_ptr<CompilationDatabase> Compilations;
227 if (opts::NoDatabases) {
228 // FIXME (Alex L): Support compilation options.
229 Compilations =
230 llvm::make_unique<clang::tooling::FixedCompilationDatabase>(
231 ".", std::vector<std::string>());
232 } else {
233 // FIXME (Alex L): Support compilation database.
234 llvm::errs() << "compilation databases are not supported yet!\n";
235 return true;
236 }
237
238 class ToolASTConsumer : public ASTConsumer {
239 public:
240 TUCallbackType Callback;
241 ToolASTConsumer(TUCallbackType Callback) : Callback(Callback) {}
242
243 void HandleTranslationUnit(ASTContext &Context) override {
244 Callback(Context);
245 }
246 };
247 class ActionWrapper {
248 public:
249 TUCallbackType Callback;
250 ActionWrapper(TUCallbackType Callback) : Callback(Callback) {}
251
252 std::unique_ptr<ASTConsumer> newASTConsumer() {
253 return llvm::make_unique<ToolASTConsumer>(std::move(Callback));
254 }
255 };
256
257 ClangTool Tool(*Compilations, Subcommand.getSources());
258 ActionWrapper ToolAction(std::move(Callback));
259 std::unique_ptr<tooling::FrontendActionFactory> Factory =
260 tooling::newFrontendActionFactory(&ToolAction);
261 return Tool.run(Factory.get());
262 }
263
264 /// Logs an individual refactoring action invocation to STDOUT.
265 void logInvocation(RefactoringActionSubcommand &Subcommand,
266 const RefactoringRuleContext &Context) {
267 if (!opts::Verbose)
268 return;
269 llvm::outs() << "invoking action '" << Subcommand.getName() << "':\n";
270 if (Context.getSelectionRange().isValid()) {
271 SourceRange R = Context.getSelectionRange();
272 llvm::outs() << " -selection=";
273 R.getBegin().print(llvm::outs(), Context.getSources());
274 llvm::outs() << " -> ";
275 R.getEnd().print(llvm::outs(), Context.getSources());
276 llvm::outs() << "\n";
277 }
278 }
279
280 bool invokeAction(RefactoringActionSubcommand &Subcommand) {
281 // Find a set of matching rules.
282 SmallVector<RefactoringActionRule *, 4> MatchingRules;
283 llvm::StringSet<> MissingOptions;
284
285 bool HasSelection = false;
286 for (const auto &Rule : Subcommand.getActionRules()) {
287 if (Rule->hasSelectionRequirement()) {
288 HasSelection = true;
289 if (Subcommand.getSelection())
290 MatchingRules.push_back(Rule.get());
291 else
292 MissingOptions.insert("selection");
293 }
294 // FIXME (Alex L): Support custom options.
295 }
296 if (MatchingRules.empty()) {
297 llvm::errs() << "error: '" << Subcommand.getName()
298 << "' can't be invoked with the given arguments:\n";
299 for (const auto &Opt : MissingOptions)
300 llvm::errs() << " missing '-" << Opt.getKey() << "' option\n";
301 return true;
302 }
303
304 bool HasFailed = false;
305 ClangRefactorConsumer Consumer;
306 if (foreachTranslationUnit(Subcommand, [&](ASTContext &AST) {
307 RefactoringRuleContext Context(AST.getSourceManager());
308 Context.setASTContext(AST);
309
310 auto InvokeRule = [&](RefactoringResultConsumer &Consumer) {
311 logInvocation(Subcommand, Context);
312 for (RefactoringActionRule *Rule : MatchingRules) {
313 if (!Rule->hasSelectionRequirement())
314 continue;
315 Rule->invoke(Consumer, Context);
316 return;
317 }
318 // FIXME (Alex L): If more than one initiation succeeded, then the
319 // rules are ambiguous.
320 llvm_unreachable(
321 "The action must have at least one selection rule");
322 };
323
324 if (HasSelection) {
325 assert(Subcommand.getSelection() && "Missing selection argument?");
326 if (opts::Verbose)
327 Subcommand.getSelection()->print(llvm::outs());
328 auto CustomConsumer =
329 Subcommand.getSelection()->createCustomConsumer();
330 if (Subcommand.getSelection()->forAllRanges(
331 Context.getSources(), [&](SourceRange R) {
332 Context.setSelectionRange(R);
333 InvokeRule(CustomConsumer ? *CustomConsumer : Consumer);
334 }))
335 HasFailed = true;
336 return;
337 }
338 // FIXME (Alex L): Implement non-selection based invocation path.
339 }))
340 return true;
341 return HasFailed;
342 }
343};
344
345} // end anonymous namespace
346
347int main(int argc, const char **argv) {
348 ClangRefactorTool Tool;
349
350 // FIXME: Use LibTooling's CommonOptions parser when subcommands are supported
351 // by it.
352 cl::HideUnrelatedOptions(opts::CommonRefactorOptions);
353 cl::ParseCommandLineOptions(
354 argc, argv, "Clang-based refactoring tool for C, C++ and Objective-C");
355 cl::PrintOptionValues();
356
357 // Figure out which action is specified by the user. The user must specify
358 // the action using a command-line subcommand, e.g. the invocation
359 // `clang-refactor local-rename` corresponds to the `LocalRename` refactoring
360 // action. All subcommands must have a unique names. This allows us to figure
361 // out which refactoring action should be invoked by looking at the first
362 // subcommand that's enabled by LLVM's command-line parser.
363 auto It = llvm::find_if(
364 Tool.SubCommands,
365 [](const std::unique_ptr<RefactoringActionSubcommand> &SubCommand) {
366 return !!(*SubCommand);
367 });
368 if (It == Tool.SubCommands.end()) {
369 llvm::errs() << "error: no refactoring action given\n";
370 llvm::errs() << "note: the following actions are supported:\n";
371 for (const auto &Subcommand : Tool.SubCommands)
372 llvm::errs().indent(2) << Subcommand->getName() << "\n";
373 return 1;
374 }
375 RefactoringActionSubcommand &ActionCommand = **It;
376
377 ArrayRef<std::string> Sources = ActionCommand.getSources();
378 // When -no-dbs is used, at least one file (TU) must be given to any
379 // subcommand.
380 if (opts::NoDatabases && Sources.empty()) {
381 llvm::errs() << "error: must provide paths to the source files when "
382 "'-no-dbs' is used\n";
383 return 1;
384 }
385 if (ActionCommand.parseArguments())
386 return 1;
387 if (Tool.invokeAction(ActionCommand))
388 return 1;
389
390 return 0;
391}