blob: b1a1ebdf6845eb14866ba0b9bc2ad2e851989615 [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"
Alex Lorenzad38fbf2017-10-13 01:53:13 +000021#include "clang/Tooling/Refactoring/RefactoringOptions.h"
Alex Lorenzb54ef6a2017-09-14 10:06:52 +000022#include "clang/Tooling/Refactoring/Rename/RenamingAction.h"
23#include "clang/Tooling/Tooling.h"
24#include "llvm/Support/CommandLine.h"
25#include "llvm/Support/FileSystem.h"
26#include "llvm/Support/raw_ostream.h"
27#include <string>
28
29using namespace clang;
30using namespace tooling;
31using namespace refactor;
32namespace cl = llvm::cl;
33
34namespace opts {
35
Alex Lorenzad38fbf2017-10-13 01:53:13 +000036static cl::OptionCategory CommonRefactorOptions("Refactoring options");
Alex Lorenzb54ef6a2017-09-14 10:06:52 +000037
Alex Lorenzb54ef6a2017-09-14 10:06:52 +000038static cl::opt<bool> Verbose("v", cl::desc("Use verbose output"),
Alex Lorenzad38fbf2017-10-13 01:53:13 +000039 cl::cat(cl::GeneralCategory),
Alex Lorenzb54ef6a2017-09-14 10:06:52 +000040 cl::sub(*cl::AllSubCommands));
41} // end namespace opts
42
43namespace {
44
45/// Stores the parsed `-selection` argument.
46class SourceSelectionArgument {
47public:
48 virtual ~SourceSelectionArgument() {}
49
50 /// Parse the `-selection` argument.
51 ///
52 /// \returns A valid argument when the parse succedeed, null otherwise.
53 static std::unique_ptr<SourceSelectionArgument> fromString(StringRef Value);
54
55 /// Prints any additional state associated with the selection argument to
56 /// the given output stream.
Alex Lorenz9ce566f2017-10-13 22:47:44 +000057 virtual void print(raw_ostream &OS) = 0;
Alex Lorenzb54ef6a2017-09-14 10:06:52 +000058
59 /// Returns a replacement refactoring result consumer (if any) that should
60 /// consume the results of a refactoring operation.
61 ///
62 /// The replacement refactoring result consumer is used by \c
63 /// TestSourceSelectionArgument to inject a test-specific result handling
64 /// logic into the refactoring operation. The test-specific consumer
65 /// ensures that the individual results in a particular test group are
66 /// identical.
67 virtual std::unique_ptr<RefactoringResultConsumer> createCustomConsumer() {
68 return nullptr;
69 }
70
71 /// Runs the give refactoring function for each specified selection.
72 ///
73 /// \returns true if an error occurred, false otherwise.
74 virtual bool
75 forAllRanges(const SourceManager &SM,
76 llvm::function_ref<void(SourceRange R)> Callback) = 0;
77};
78
79/// Stores the parsed -selection=test:<filename> option.
80class TestSourceSelectionArgument final : public SourceSelectionArgument {
81public:
82 TestSourceSelectionArgument(TestSelectionRangesInFile TestSelections)
83 : TestSelections(std::move(TestSelections)) {}
84
85 void print(raw_ostream &OS) override { TestSelections.dump(OS); }
86
87 std::unique_ptr<RefactoringResultConsumer> createCustomConsumer() override {
88 return TestSelections.createConsumer();
89 }
90
91 /// Testing support: invokes the selection action for each selection range in
92 /// the test file.
93 bool forAllRanges(const SourceManager &SM,
94 llvm::function_ref<void(SourceRange R)> Callback) override {
95 return TestSelections.foreachRange(SM, Callback);
96 }
97
98private:
99 TestSelectionRangesInFile TestSelections;
100};
101
102std::unique_ptr<SourceSelectionArgument>
103SourceSelectionArgument::fromString(StringRef Value) {
104 if (Value.startswith("test:")) {
105 StringRef Filename = Value.drop_front(strlen("test:"));
106 Optional<TestSelectionRangesInFile> ParsedTestSelection =
107 findTestSelectionRanges(Filename);
108 if (!ParsedTestSelection)
109 return nullptr; // A parsing error was already reported.
110 return llvm::make_unique<TestSourceSelectionArgument>(
111 std::move(*ParsedTestSelection));
112 }
Alex Lorenz9ce566f2017-10-13 22:47:44 +0000113 // FIXME: Support true selection ranges.
Alex Lorenzb54ef6a2017-09-14 10:06:52 +0000114 llvm::errs() << "error: '-selection' option must be specified using "
115 "<file>:<line>:<column> or "
Alex Lorenz9ce566f2017-10-13 22:47:44 +0000116 "<file>:<line>:<column>-<line>:<column> format";
Alex Lorenzb54ef6a2017-09-14 10:06:52 +0000117 return nullptr;
118}
119
Alex Lorenzad38fbf2017-10-13 01:53:13 +0000120/// A container that stores the command-line options used by a single
121/// refactoring option.
122class RefactoringActionCommandLineOptions {
123public:
124 void addStringOption(const RefactoringOption &Option,
125 std::unique_ptr<cl::opt<std::string>> CLOption) {
126 StringOptions[&Option] = std::move(CLOption);
127 }
128
129 const cl::opt<std::string> &
130 getStringOption(const RefactoringOption &Opt) const {
131 auto It = StringOptions.find(&Opt);
132 return *It->second;
133 }
134
135private:
136 llvm::DenseMap<const RefactoringOption *,
137 std::unique_ptr<cl::opt<std::string>>>
138 StringOptions;
139};
140
141/// Passes the command-line option values to the options used by a single
142/// refactoring action rule.
143class CommandLineRefactoringOptionVisitor final
144 : public RefactoringOptionVisitor {
145public:
146 CommandLineRefactoringOptionVisitor(
147 const RefactoringActionCommandLineOptions &Options)
148 : Options(Options) {}
149
150 void visit(const RefactoringOption &Opt,
151 Optional<std::string> &Value) override {
152 const cl::opt<std::string> &CLOpt = Options.getStringOption(Opt);
153 if (!CLOpt.getValue().empty()) {
154 Value = CLOpt.getValue();
155 return;
156 }
157 Value = None;
158 if (Opt.isRequired())
159 MissingRequiredOptions.push_back(&Opt);
160 }
161
162 ArrayRef<const RefactoringOption *> getMissingRequiredOptions() const {
163 return MissingRequiredOptions;
164 }
165
166private:
167 llvm::SmallVector<const RefactoringOption *, 4> MissingRequiredOptions;
168 const RefactoringActionCommandLineOptions &Options;
169};
170
171/// Creates the refactoring options used by all the rules in a single
172/// refactoring action.
173class CommandLineRefactoringOptionCreator final
174 : public RefactoringOptionVisitor {
175public:
176 CommandLineRefactoringOptionCreator(
177 cl::OptionCategory &Category, cl::SubCommand &Subcommand,
178 RefactoringActionCommandLineOptions &Options)
179 : Category(Category), Subcommand(Subcommand), Options(Options) {}
180
181 void visit(const RefactoringOption &Opt, Optional<std::string> &) override {
182 if (Visited.insert(&Opt).second)
183 Options.addStringOption(Opt, create<std::string>(Opt));
184 }
185
186private:
187 template <typename T>
188 std::unique_ptr<cl::opt<T>> create(const RefactoringOption &Opt) {
189 if (!OptionNames.insert(Opt.getName()).second)
190 llvm::report_fatal_error("Multiple identical refactoring options "
191 "specified for one refactoring action");
192 // FIXME: cl::Required can be specified when this option is present
193 // in all rules in an action.
194 return llvm::make_unique<cl::opt<T>>(
195 Opt.getName(), cl::desc(Opt.getDescription()), cl::Optional,
196 cl::cat(Category), cl::sub(Subcommand));
197 }
198
199 llvm::SmallPtrSet<const RefactoringOption *, 8> Visited;
200 llvm::StringSet<> OptionNames;
201 cl::OptionCategory &Category;
202 cl::SubCommand &Subcommand;
203 RefactoringActionCommandLineOptions &Options;
204};
205
Alex Lorenzb54ef6a2017-09-14 10:06:52 +0000206/// A subcommand that corresponds to individual refactoring action.
207class RefactoringActionSubcommand : public cl::SubCommand {
208public:
209 RefactoringActionSubcommand(std::unique_ptr<RefactoringAction> Action,
210 RefactoringActionRules ActionRules,
211 cl::OptionCategory &Category)
212 : SubCommand(Action->getCommand(), Action->getDescription()),
213 Action(std::move(Action)), ActionRules(std::move(ActionRules)) {
Alex Lorenzb54ef6a2017-09-14 10:06:52 +0000214 // Check if the selection option is supported.
215 bool HasSelection = false;
216 for (const auto &Rule : this->ActionRules) {
217 if ((HasSelection = Rule->hasSelectionRequirement()))
218 break;
219 }
220 if (HasSelection) {
221 Selection = llvm::make_unique<cl::opt<std::string>>(
222 "selection",
223 cl::desc("The selected source range in which the refactoring should "
224 "be initiated (<file>:<line>:<column>-<line>:<column> or "
225 "<file>:<line>:<column>)"),
226 cl::cat(Category), cl::sub(*this));
227 }
Alex Lorenzad38fbf2017-10-13 01:53:13 +0000228 // Create the refactoring options.
229 for (const auto &Rule : this->ActionRules) {
230 CommandLineRefactoringOptionCreator OptionCreator(Category, *this,
231 Options);
232 Rule->visitRefactoringOptions(OptionCreator);
233 }
Alex Lorenzb54ef6a2017-09-14 10:06:52 +0000234 }
235
236 ~RefactoringActionSubcommand() { unregisterSubCommand(); }
237
238 const RefactoringActionRules &getActionRules() const { return ActionRules; }
239
240 /// Parses the command-line arguments that are specific to this rule.
241 ///
242 /// \returns true on error, false otherwise.
243 bool parseArguments() {
244 if (Selection) {
245 ParsedSelection = SourceSelectionArgument::fromString(*Selection);
246 if (!ParsedSelection)
247 return true;
248 }
249 return false;
250 }
251
252 SourceSelectionArgument *getSelection() const {
253 assert(Selection && "selection not supported!");
254 return ParsedSelection.get();
255 }
Alex Lorenzad38fbf2017-10-13 01:53:13 +0000256
257 const RefactoringActionCommandLineOptions &getOptions() const {
258 return Options;
259 }
260
Alex Lorenzb54ef6a2017-09-14 10:06:52 +0000261private:
262 std::unique_ptr<RefactoringAction> Action;
263 RefactoringActionRules ActionRules;
Alex Lorenzb54ef6a2017-09-14 10:06:52 +0000264 std::unique_ptr<cl::opt<std::string>> Selection;
265 std::unique_ptr<SourceSelectionArgument> ParsedSelection;
Alex Lorenzad38fbf2017-10-13 01:53:13 +0000266 RefactoringActionCommandLineOptions Options;
Alex Lorenzb54ef6a2017-09-14 10:06:52 +0000267};
268
269class ClangRefactorConsumer : public RefactoringResultConsumer {
270public:
Alex Lorenz9ce566f2017-10-13 22:47:44 +0000271 void handleError(llvm::Error Err) {
Alex Lorenzb54ef6a2017-09-14 10:06:52 +0000272 llvm::errs() << llvm::toString(std::move(Err)) << "\n";
273 }
274
Alex Lorenz9ce566f2017-10-13 22:47:44 +0000275 // FIXME: Consume atomic changes and apply them to files.
Alex Lorenzb54ef6a2017-09-14 10:06:52 +0000276};
277
278class ClangRefactorTool {
279public:
280 std::vector<std::unique_ptr<RefactoringActionSubcommand>> SubCommands;
281
282 ClangRefactorTool() {
283 std::vector<std::unique_ptr<RefactoringAction>> Actions =
284 createRefactoringActions();
285
286 // Actions must have unique command names so that we can map them to one
287 // subcommand.
288 llvm::StringSet<> CommandNames;
289 for (const auto &Action : Actions) {
290 if (!CommandNames.insert(Action->getCommand()).second) {
291 llvm::errs() << "duplicate refactoring action command '"
292 << Action->getCommand() << "'!";
293 exit(1);
294 }
295 }
296
297 // Create subcommands and command-line options.
298 for (auto &Action : Actions) {
299 SubCommands.push_back(llvm::make_unique<RefactoringActionSubcommand>(
300 std::move(Action), Action->createActiveActionRules(),
301 opts::CommonRefactorOptions));
302 }
303 }
304
305 using TUCallbackType = llvm::function_ref<void(ASTContext &)>;
306
307 /// Parses the translation units that were given to the subcommand using
308 /// the 'sources' option and invokes the callback for each parsed
309 /// translation unit.
Alex Lorenz3d712c42017-09-14 13:16:14 +0000310 bool foreachTranslationUnit(const CompilationDatabase &DB,
311 ArrayRef<std::string> Sources,
Alex Lorenzb54ef6a2017-09-14 10:06:52 +0000312 TUCallbackType Callback) {
Alex Lorenzb54ef6a2017-09-14 10:06:52 +0000313 class ToolASTConsumer : public ASTConsumer {
314 public:
315 TUCallbackType Callback;
316 ToolASTConsumer(TUCallbackType Callback) : Callback(Callback) {}
317
318 void HandleTranslationUnit(ASTContext &Context) override {
319 Callback(Context);
320 }
321 };
322 class ActionWrapper {
323 public:
324 TUCallbackType Callback;
325 ActionWrapper(TUCallbackType Callback) : Callback(Callback) {}
326
327 std::unique_ptr<ASTConsumer> newASTConsumer() {
Haojian Wu21cc1382017-10-10 09:48:38 +0000328 return llvm::make_unique<ToolASTConsumer>(Callback);
Alex Lorenzb54ef6a2017-09-14 10:06:52 +0000329 }
330 };
331
Alex Lorenz3d712c42017-09-14 13:16:14 +0000332 ClangTool Tool(DB, Sources);
Haojian Wu21cc1382017-10-10 09:48:38 +0000333 ActionWrapper ToolAction(Callback);
Alex Lorenzb54ef6a2017-09-14 10:06:52 +0000334 std::unique_ptr<tooling::FrontendActionFactory> Factory =
335 tooling::newFrontendActionFactory(&ToolAction);
336 return Tool.run(Factory.get());
337 }
338
339 /// Logs an individual refactoring action invocation to STDOUT.
340 void logInvocation(RefactoringActionSubcommand &Subcommand,
341 const RefactoringRuleContext &Context) {
342 if (!opts::Verbose)
343 return;
344 llvm::outs() << "invoking action '" << Subcommand.getName() << "':\n";
345 if (Context.getSelectionRange().isValid()) {
346 SourceRange R = Context.getSelectionRange();
347 llvm::outs() << " -selection=";
348 R.getBegin().print(llvm::outs(), Context.getSources());
349 llvm::outs() << " -> ";
350 R.getEnd().print(llvm::outs(), Context.getSources());
351 llvm::outs() << "\n";
352 }
353 }
354
Alex Lorenz3d712c42017-09-14 13:16:14 +0000355 bool invokeAction(RefactoringActionSubcommand &Subcommand,
356 const CompilationDatabase &DB,
357 ArrayRef<std::string> Sources) {
Alex Lorenzb54ef6a2017-09-14 10:06:52 +0000358 // Find a set of matching rules.
359 SmallVector<RefactoringActionRule *, 4> MatchingRules;
360 llvm::StringSet<> MissingOptions;
361
362 bool HasSelection = false;
363 for (const auto &Rule : Subcommand.getActionRules()) {
Alex Lorenzad38fbf2017-10-13 01:53:13 +0000364 bool SelectionMatches = true;
Alex Lorenzb54ef6a2017-09-14 10:06:52 +0000365 if (Rule->hasSelectionRequirement()) {
366 HasSelection = true;
Alex Lorenzad38fbf2017-10-13 01:53:13 +0000367 if (!Subcommand.getSelection()) {
Alex Lorenzb54ef6a2017-09-14 10:06:52 +0000368 MissingOptions.insert("selection");
Alex Lorenzad38fbf2017-10-13 01:53:13 +0000369 SelectionMatches = false;
370 }
Alex Lorenzb54ef6a2017-09-14 10:06:52 +0000371 }
Alex Lorenzad38fbf2017-10-13 01:53:13 +0000372 CommandLineRefactoringOptionVisitor Visitor(Subcommand.getOptions());
373 Rule->visitRefactoringOptions(Visitor);
374 if (SelectionMatches && Visitor.getMissingRequiredOptions().empty()) {
375 MatchingRules.push_back(Rule.get());
376 continue;
377 }
378 for (const RefactoringOption *Opt : Visitor.getMissingRequiredOptions())
379 MissingOptions.insert(Opt->getName());
Alex Lorenzb54ef6a2017-09-14 10:06:52 +0000380 }
381 if (MatchingRules.empty()) {
382 llvm::errs() << "error: '" << Subcommand.getName()
383 << "' can't be invoked with the given arguments:\n";
384 for (const auto &Opt : MissingOptions)
385 llvm::errs() << " missing '-" << Opt.getKey() << "' option\n";
386 return true;
387 }
388
389 bool HasFailed = false;
390 ClangRefactorConsumer Consumer;
Alex Lorenz3d712c42017-09-14 13:16:14 +0000391 if (foreachTranslationUnit(DB, Sources, [&](ASTContext &AST) {
Alex Lorenzb54ef6a2017-09-14 10:06:52 +0000392 RefactoringRuleContext Context(AST.getSourceManager());
393 Context.setASTContext(AST);
394
395 auto InvokeRule = [&](RefactoringResultConsumer &Consumer) {
396 logInvocation(Subcommand, Context);
397 for (RefactoringActionRule *Rule : MatchingRules) {
398 if (!Rule->hasSelectionRequirement())
399 continue;
400 Rule->invoke(Consumer, Context);
401 return;
402 }
403 // FIXME (Alex L): If more than one initiation succeeded, then the
404 // rules are ambiguous.
405 llvm_unreachable(
406 "The action must have at least one selection rule");
407 };
408
409 if (HasSelection) {
410 assert(Subcommand.getSelection() && "Missing selection argument?");
411 if (opts::Verbose)
412 Subcommand.getSelection()->print(llvm::outs());
413 auto CustomConsumer =
414 Subcommand.getSelection()->createCustomConsumer();
415 if (Subcommand.getSelection()->forAllRanges(
416 Context.getSources(), [&](SourceRange R) {
417 Context.setSelectionRange(R);
418 InvokeRule(CustomConsumer ? *CustomConsumer : Consumer);
419 }))
420 HasFailed = true;
421 return;
422 }
423 // FIXME (Alex L): Implement non-selection based invocation path.
424 }))
425 return true;
Alex Lorenz9ce566f2017-10-13 22:47:44 +0000426 return HasFailed;
Alex Lorenzb54ef6a2017-09-14 10:06:52 +0000427 }
428};
429
430} // end anonymous namespace
431
432int main(int argc, const char **argv) {
433 ClangRefactorTool Tool;
434
Alex Lorenz3d712c42017-09-14 13:16:14 +0000435 CommonOptionsParser Options(
Alex Lorenzad38fbf2017-10-13 01:53:13 +0000436 argc, argv, cl::GeneralCategory, cl::ZeroOrMore,
Alex Lorenz3d712c42017-09-14 13:16:14 +0000437 "Clang-based refactoring tool for C, C++ and Objective-C");
Alex Lorenzb54ef6a2017-09-14 10:06:52 +0000438
439 // Figure out which action is specified by the user. The user must specify
440 // the action using a command-line subcommand, e.g. the invocation
441 // `clang-refactor local-rename` corresponds to the `LocalRename` refactoring
442 // action. All subcommands must have a unique names. This allows us to figure
443 // out which refactoring action should be invoked by looking at the first
444 // subcommand that's enabled by LLVM's command-line parser.
445 auto It = llvm::find_if(
446 Tool.SubCommands,
447 [](const std::unique_ptr<RefactoringActionSubcommand> &SubCommand) {
448 return !!(*SubCommand);
449 });
450 if (It == Tool.SubCommands.end()) {
451 llvm::errs() << "error: no refactoring action given\n";
452 llvm::errs() << "note: the following actions are supported:\n";
453 for (const auto &Subcommand : Tool.SubCommands)
454 llvm::errs().indent(2) << Subcommand->getName() << "\n";
455 return 1;
456 }
457 RefactoringActionSubcommand &ActionCommand = **It;
458
Alex Lorenzb54ef6a2017-09-14 10:06:52 +0000459 if (ActionCommand.parseArguments())
460 return 1;
Alex Lorenz3d712c42017-09-14 13:16:14 +0000461 if (Tool.invokeAction(ActionCommand, Options.getCompilations(),
462 Options.getSourcePathList()))
Alex Lorenzb54ef6a2017-09-14 10:06:52 +0000463 return 1;
464
465 return 0;
466}