1 | //===--- ClangRefactor.cpp - Clang-based refactoring tool -----------------===// |
2 | // |
3 | // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. |
4 | // See https://llvm.org/LICENSE.txt for license information. |
5 | // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
6 | // |
7 | //===----------------------------------------------------------------------===// |
8 | /// |
9 | /// \file |
10 | /// This file implements a clang-refactor tool that performs various |
11 | /// source transformations. |
12 | /// |
13 | //===----------------------------------------------------------------------===// |
14 | |
15 | #include "TestSupport.h" |
16 | #include "clang/Frontend/CommandLineSourceLoc.h" |
17 | #include "clang/Frontend/TextDiagnosticPrinter.h" |
18 | #include "clang/Rewrite/Core/Rewriter.h" |
19 | #include "clang/Tooling/CommonOptionsParser.h" |
20 | #include "clang/Tooling/Refactoring.h" |
21 | #include "clang/Tooling/Refactoring/RefactoringAction.h" |
22 | #include "clang/Tooling/Refactoring/RefactoringOptions.h" |
23 | #include "clang/Tooling/Refactoring/Rename/RenamingAction.h" |
24 | #include "clang/Tooling/Tooling.h" |
25 | #include "llvm/Support/CommandLine.h" |
26 | #include "llvm/Support/FileSystem.h" |
27 | #include "llvm/Support/Signals.h" |
28 | #include "llvm/Support/raw_ostream.h" |
29 | #include <optional> |
30 | #include <string> |
31 | |
32 | using namespace clang; |
33 | using namespace tooling; |
34 | using namespace refactor; |
35 | namespace cl = llvm::cl; |
36 | |
37 | namespace opts { |
38 | |
39 | static cl::OptionCategory CommonRefactorOptions("Refactoring options" ); |
40 | |
41 | static cl::opt<bool> Verbose("v" , cl::desc("Use verbose output" ), |
42 | cl::cat(cl::getGeneralCategory()), |
43 | cl::sub(cl::SubCommand::getAll())); |
44 | |
45 | static cl::opt<bool> Inplace("i" , cl::desc("Inplace edit <file>s" ), |
46 | cl::cat(cl::getGeneralCategory()), |
47 | cl::sub(cl::SubCommand::getAll())); |
48 | |
49 | } // end namespace opts |
50 | |
51 | namespace { |
52 | |
53 | /// Stores the parsed `-selection` argument. |
54 | class SourceSelectionArgument { |
55 | public: |
56 | virtual ~SourceSelectionArgument() {} |
57 | |
58 | /// Parse the `-selection` argument. |
59 | /// |
60 | /// \returns A valid argument when the parse succedeed, null otherwise. |
61 | static std::unique_ptr<SourceSelectionArgument> fromString(StringRef Value); |
62 | |
63 | /// Prints any additional state associated with the selection argument to |
64 | /// the given output stream. |
65 | virtual void print(raw_ostream &OS) {} |
66 | |
67 | /// Returns a replacement refactoring result consumer (if any) that should |
68 | /// consume the results of a refactoring operation. |
69 | /// |
70 | /// The replacement refactoring result consumer is used by \c |
71 | /// TestSourceSelectionArgument to inject a test-specific result handling |
72 | /// logic into the refactoring operation. The test-specific consumer |
73 | /// ensures that the individual results in a particular test group are |
74 | /// identical. |
75 | virtual std::unique_ptr<ClangRefactorToolConsumerInterface> |
76 | createCustomConsumer() { |
77 | return nullptr; |
78 | } |
79 | |
80 | /// Runs the give refactoring function for each specified selection. |
81 | /// |
82 | /// \returns true if an error occurred, false otherwise. |
83 | virtual bool |
84 | forAllRanges(const SourceManager &SM, |
85 | llvm::function_ref<void(SourceRange R)> Callback) = 0; |
86 | }; |
87 | |
88 | /// Stores the parsed -selection=test:<filename> option. |
89 | class TestSourceSelectionArgument final : public SourceSelectionArgument { |
90 | public: |
91 | TestSourceSelectionArgument(TestSelectionRangesInFile TestSelections) |
92 | : TestSelections(std::move(TestSelections)) {} |
93 | |
94 | void print(raw_ostream &OS) override { TestSelections.dump(OS); } |
95 | |
96 | std::unique_ptr<ClangRefactorToolConsumerInterface> |
97 | createCustomConsumer() override { |
98 | return TestSelections.createConsumer(); |
99 | } |
100 | |
101 | /// Testing support: invokes the selection action for each selection range in |
102 | /// the test file. |
103 | bool forAllRanges(const SourceManager &SM, |
104 | llvm::function_ref<void(SourceRange R)> Callback) override { |
105 | return TestSelections.foreachRange(SM, Callback); |
106 | } |
107 | |
108 | private: |
109 | TestSelectionRangesInFile TestSelections; |
110 | }; |
111 | |
112 | /// Stores the parsed -selection=filename:line:column[-line:column] option. |
113 | class SourceRangeSelectionArgument final : public SourceSelectionArgument { |
114 | public: |
115 | SourceRangeSelectionArgument(ParsedSourceRange Range) |
116 | : Range(std::move(Range)) {} |
117 | |
118 | bool forAllRanges(const SourceManager &SM, |
119 | llvm::function_ref<void(SourceRange R)> Callback) override { |
120 | auto FE = SM.getFileManager().getOptionalFileRef(Filename: Range.FileName); |
121 | FileID FID = FE ? SM.translateFile(SourceFile: *FE) : FileID(); |
122 | if (!FE || FID.isInvalid()) { |
123 | llvm::errs() << "error: -selection=" << Range.FileName |
124 | << ":... : given file is not in the target TU\n" ; |
125 | return true; |
126 | } |
127 | |
128 | SourceLocation Start = SM.getMacroArgExpandedLocation( |
129 | Loc: SM.translateLineCol(FID, Line: Range.Begin.first, Col: Range.Begin.second)); |
130 | SourceLocation End = SM.getMacroArgExpandedLocation( |
131 | Loc: SM.translateLineCol(FID, Line: Range.End.first, Col: Range.End.second)); |
132 | if (Start.isInvalid() || End.isInvalid()) { |
133 | llvm::errs() << "error: -selection=" << Range.FileName << ':' |
134 | << Range.Begin.first << ':' << Range.Begin.second << '-' |
135 | << Range.End.first << ':' << Range.End.second |
136 | << " : invalid source location\n" ; |
137 | return true; |
138 | } |
139 | Callback(SourceRange(Start, End)); |
140 | return false; |
141 | } |
142 | |
143 | private: |
144 | ParsedSourceRange Range; |
145 | }; |
146 | |
147 | std::unique_ptr<SourceSelectionArgument> |
148 | SourceSelectionArgument::fromString(StringRef Value) { |
149 | if (Value.starts_with(Prefix: "test:" )) { |
150 | StringRef Filename = Value.drop_front(N: strlen(s: "test:" )); |
151 | std::optional<TestSelectionRangesInFile> ParsedTestSelection = |
152 | findTestSelectionRanges(Filename); |
153 | if (!ParsedTestSelection) |
154 | return nullptr; // A parsing error was already reported. |
155 | return std::make_unique<TestSourceSelectionArgument>( |
156 | args: std::move(*ParsedTestSelection)); |
157 | } |
158 | std::optional<ParsedSourceRange> Range = ParsedSourceRange::fromString(Str: Value); |
159 | if (Range) |
160 | return std::make_unique<SourceRangeSelectionArgument>(args: std::move(*Range)); |
161 | llvm::errs() << "error: '-selection' option must be specified using " |
162 | "<file>:<line>:<column> or " |
163 | "<file>:<line>:<column>-<line>:<column> format, " |
164 | "where <line> and <column> are integers greater than zero.\n" ; |
165 | return nullptr; |
166 | } |
167 | |
168 | /// A container that stores the command-line options used by a single |
169 | /// refactoring option. |
170 | class RefactoringActionCommandLineOptions { |
171 | public: |
172 | void addStringOption(const RefactoringOption &Option, |
173 | std::unique_ptr<cl::opt<std::string>> CLOption) { |
174 | StringOptions[&Option] = std::move(CLOption); |
175 | } |
176 | |
177 | const cl::opt<std::string> & |
178 | getStringOption(const RefactoringOption &Opt) const { |
179 | auto It = StringOptions.find(Val: &Opt); |
180 | return *It->second; |
181 | } |
182 | |
183 | private: |
184 | llvm::DenseMap<const RefactoringOption *, |
185 | std::unique_ptr<cl::opt<std::string>>> |
186 | StringOptions; |
187 | }; |
188 | |
189 | /// Passes the command-line option values to the options used by a single |
190 | /// refactoring action rule. |
191 | class CommandLineRefactoringOptionVisitor final |
192 | : public RefactoringOptionVisitor { |
193 | public: |
194 | CommandLineRefactoringOptionVisitor( |
195 | const RefactoringActionCommandLineOptions &Options) |
196 | : Options(Options) {} |
197 | |
198 | void visit(const RefactoringOption &Opt, |
199 | std::optional<std::string> &Value) override { |
200 | const cl::opt<std::string> &CLOpt = Options.getStringOption(Opt); |
201 | if (!CLOpt.getValue().empty()) { |
202 | Value = CLOpt.getValue(); |
203 | return; |
204 | } |
205 | Value = std::nullopt; |
206 | if (Opt.isRequired()) |
207 | MissingRequiredOptions.push_back(Elt: &Opt); |
208 | } |
209 | |
210 | ArrayRef<const RefactoringOption *> getMissingRequiredOptions() const { |
211 | return MissingRequiredOptions; |
212 | } |
213 | |
214 | private: |
215 | llvm::SmallVector<const RefactoringOption *, 4> MissingRequiredOptions; |
216 | const RefactoringActionCommandLineOptions &Options; |
217 | }; |
218 | |
219 | /// Creates the refactoring options used by all the rules in a single |
220 | /// refactoring action. |
221 | class CommandLineRefactoringOptionCreator final |
222 | : public RefactoringOptionVisitor { |
223 | public: |
224 | CommandLineRefactoringOptionCreator( |
225 | cl::OptionCategory &Category, cl::SubCommand &Subcommand, |
226 | RefactoringActionCommandLineOptions &Options) |
227 | : Category(Category), Subcommand(Subcommand), Options(Options) {} |
228 | |
229 | void visit(const RefactoringOption &Opt, |
230 | std::optional<std::string> &) override { |
231 | if (Visited.insert(Ptr: &Opt).second) |
232 | Options.addStringOption(Option: Opt, CLOption: create<std::string>(Opt)); |
233 | } |
234 | |
235 | private: |
236 | template <typename T> |
237 | std::unique_ptr<cl::opt<T>> create(const RefactoringOption &Opt) { |
238 | if (!OptionNames.insert(key: Opt.getName()).second) |
239 | llvm::report_fatal_error(reason: "Multiple identical refactoring options " |
240 | "specified for one refactoring action" ); |
241 | // FIXME: cl::Required can be specified when this option is present |
242 | // in all rules in an action. |
243 | return std::make_unique<cl::opt<T>>( |
244 | Opt.getName(), cl::desc(Opt.getDescription()), cl::Optional, |
245 | cl::cat(Category), cl::sub(Subcommand)); |
246 | } |
247 | |
248 | llvm::SmallPtrSet<const RefactoringOption *, 8> Visited; |
249 | llvm::StringSet<> OptionNames; |
250 | cl::OptionCategory &Category; |
251 | cl::SubCommand &Subcommand; |
252 | RefactoringActionCommandLineOptions &Options; |
253 | }; |
254 | |
255 | /// A subcommand that corresponds to individual refactoring action. |
256 | class RefactoringActionSubcommand : public cl::SubCommand { |
257 | public: |
258 | RefactoringActionSubcommand(std::unique_ptr<RefactoringAction> Action, |
259 | RefactoringActionRules ActionRules, |
260 | cl::OptionCategory &Category) |
261 | : SubCommand(Action->getCommand(), Action->getDescription()), |
262 | Action(std::move(Action)), ActionRules(std::move(ActionRules)) { |
263 | // Check if the selection option is supported. |
264 | for (const auto &Rule : this->ActionRules) { |
265 | if (Rule->hasSelectionRequirement()) { |
266 | Selection = std::make_unique<cl::opt<std::string>>( |
267 | args: "selection" , |
268 | args: cl::desc( |
269 | "The selected source range in which the refactoring should " |
270 | "be initiated (<file>:<line>:<column>-<line>:<column> or " |
271 | "<file>:<line>:<column>)" ), |
272 | args: cl::cat(Category), args: cl::sub(*this)); |
273 | break; |
274 | } |
275 | } |
276 | // Create the refactoring options. |
277 | for (const auto &Rule : this->ActionRules) { |
278 | CommandLineRefactoringOptionCreator OptionCreator(Category, *this, |
279 | Options); |
280 | Rule->visitRefactoringOptions(Visitor&: OptionCreator); |
281 | } |
282 | } |
283 | |
284 | ~RefactoringActionSubcommand() { unregisterSubCommand(); } |
285 | |
286 | const RefactoringActionRules &getActionRules() const { return ActionRules; } |
287 | |
288 | /// Parses the "-selection" command-line argument. |
289 | /// |
290 | /// \returns true on error, false otherwise. |
291 | bool parseSelectionArgument() { |
292 | if (Selection) { |
293 | ParsedSelection = SourceSelectionArgument::fromString(Value: *Selection); |
294 | if (!ParsedSelection) |
295 | return true; |
296 | } |
297 | return false; |
298 | } |
299 | |
300 | SourceSelectionArgument *getSelection() const { |
301 | assert(Selection && "selection not supported!" ); |
302 | return ParsedSelection.get(); |
303 | } |
304 | |
305 | const RefactoringActionCommandLineOptions &getOptions() const { |
306 | return Options; |
307 | } |
308 | |
309 | private: |
310 | std::unique_ptr<RefactoringAction> Action; |
311 | RefactoringActionRules ActionRules; |
312 | std::unique_ptr<cl::opt<std::string>> Selection; |
313 | std::unique_ptr<SourceSelectionArgument> ParsedSelection; |
314 | RefactoringActionCommandLineOptions Options; |
315 | }; |
316 | |
317 | class ClangRefactorConsumer final : public ClangRefactorToolConsumerInterface { |
318 | public: |
319 | ClangRefactorConsumer(AtomicChanges &Changes) : SourceChanges(&Changes) {} |
320 | |
321 | void handleError(llvm::Error Err) override { |
322 | std::optional<PartialDiagnosticAt> Diag = DiagnosticError::take(Err); |
323 | if (!Diag) { |
324 | llvm::errs() << llvm::toString(E: std::move(Err)) << "\n" ; |
325 | return; |
326 | } |
327 | llvm::cantFail(Err: std::move(Err)); // This is a success. |
328 | DiagnosticBuilder DB( |
329 | getDiags().Report(Loc: Diag->first, DiagID: Diag->second.getDiagID())); |
330 | Diag->second.Emit(DB); |
331 | } |
332 | |
333 | void handle(AtomicChanges Changes) override { |
334 | SourceChanges->insert(position: SourceChanges->begin(), first: Changes.begin(), |
335 | last: Changes.end()); |
336 | } |
337 | |
338 | void handle(SymbolOccurrences Occurrences) override { |
339 | llvm_unreachable("symbol occurrence results are not handled yet" ); |
340 | } |
341 | |
342 | private: |
343 | AtomicChanges *SourceChanges; |
344 | }; |
345 | |
346 | class ClangRefactorTool { |
347 | public: |
348 | ClangRefactorTool() |
349 | : SelectedSubcommand(nullptr), MatchingRule(nullptr), |
350 | Consumer(new ClangRefactorConsumer(Changes)), HasFailed(false) { |
351 | std::vector<std::unique_ptr<RefactoringAction>> Actions = |
352 | createRefactoringActions(); |
353 | |
354 | // Actions must have unique command names so that we can map them to one |
355 | // subcommand. |
356 | llvm::StringSet<> CommandNames; |
357 | for (const auto &Action : Actions) { |
358 | if (!CommandNames.insert(key: Action->getCommand()).second) { |
359 | llvm::errs() << "duplicate refactoring action command '" |
360 | << Action->getCommand() << "'!" ; |
361 | exit(status: 1); |
362 | } |
363 | } |
364 | |
365 | // Create subcommands and command-line options. |
366 | for (auto &Action : Actions) { |
367 | SubCommands.push_back(x: std::make_unique<RefactoringActionSubcommand>( |
368 | args: std::move(Action), args: Action->createActiveActionRules(), |
369 | args&: opts::CommonRefactorOptions)); |
370 | } |
371 | } |
372 | |
373 | // Initializes the selected subcommand and refactoring rule based on the |
374 | // command line options. |
375 | llvm::Error Init() { |
376 | auto Subcommand = getSelectedSubcommand(); |
377 | if (!Subcommand) |
378 | return Subcommand.takeError(); |
379 | auto Rule = getMatchingRule(Subcommand&: **Subcommand); |
380 | if (!Rule) |
381 | return Rule.takeError(); |
382 | |
383 | SelectedSubcommand = *Subcommand; |
384 | MatchingRule = *Rule; |
385 | |
386 | return llvm::Error::success(); |
387 | } |
388 | |
389 | bool hasFailed() const { return HasFailed; } |
390 | |
391 | using TUCallbackType = std::function<void(ASTContext &)>; |
392 | |
393 | // Callback of an AST action. This invokes the matching rule on the given AST. |
394 | void callback(ASTContext &AST) { |
395 | assert(SelectedSubcommand && MatchingRule && Consumer); |
396 | RefactoringRuleContext Context(AST.getSourceManager()); |
397 | Context.setASTContext(AST); |
398 | |
399 | // If the selection option is test specific, we use a test-specific |
400 | // consumer. |
401 | std::unique_ptr<ClangRefactorToolConsumerInterface> TestConsumer; |
402 | bool HasSelection = MatchingRule->hasSelectionRequirement(); |
403 | if (HasSelection) |
404 | TestConsumer = SelectedSubcommand->getSelection()->createCustomConsumer(); |
405 | ClangRefactorToolConsumerInterface *ActiveConsumer = |
406 | TestConsumer ? TestConsumer.get() : Consumer.get(); |
407 | ActiveConsumer->beginTU(Context&: AST); |
408 | |
409 | auto InvokeRule = [&](RefactoringResultConsumer &Consumer) { |
410 | if (opts::Verbose) |
411 | logInvocation(Subcommand&: *SelectedSubcommand, Context); |
412 | MatchingRule->invoke(Consumer&: *ActiveConsumer, Context); |
413 | }; |
414 | if (HasSelection) { |
415 | assert(SelectedSubcommand->getSelection() && |
416 | "Missing selection argument?" ); |
417 | if (opts::Verbose) |
418 | SelectedSubcommand->getSelection()->print(OS&: llvm::outs()); |
419 | if (SelectedSubcommand->getSelection()->forAllRanges( |
420 | SM: Context.getSources(), Callback: [&](SourceRange R) { |
421 | Context.setSelectionRange(R); |
422 | InvokeRule(*ActiveConsumer); |
423 | })) |
424 | HasFailed = true; |
425 | ActiveConsumer->endTU(); |
426 | return; |
427 | } |
428 | InvokeRule(*ActiveConsumer); |
429 | ActiveConsumer->endTU(); |
430 | } |
431 | |
432 | llvm::Expected<std::unique_ptr<FrontendActionFactory>> |
433 | getFrontendActionFactory() { |
434 | class ToolASTConsumer : public ASTConsumer { |
435 | public: |
436 | TUCallbackType Callback; |
437 | ToolASTConsumer(TUCallbackType Callback) |
438 | : Callback(std::move(Callback)) {} |
439 | |
440 | void HandleTranslationUnit(ASTContext &Context) override { |
441 | Callback(Context); |
442 | } |
443 | }; |
444 | class ToolASTAction : public ASTFrontendAction { |
445 | public: |
446 | explicit ToolASTAction(TUCallbackType Callback) |
447 | : Callback(std::move(Callback)) {} |
448 | |
449 | protected: |
450 | std::unique_ptr<clang::ASTConsumer> |
451 | CreateASTConsumer(clang::CompilerInstance &compiler, |
452 | StringRef /* dummy */) override { |
453 | std::unique_ptr<clang::ASTConsumer> Consumer{ |
454 | new ToolASTConsumer(Callback)}; |
455 | return Consumer; |
456 | } |
457 | |
458 | private: |
459 | TUCallbackType Callback; |
460 | }; |
461 | |
462 | class ToolActionFactory : public FrontendActionFactory { |
463 | public: |
464 | ToolActionFactory(TUCallbackType Callback) |
465 | : Callback(std::move(Callback)) {} |
466 | |
467 | std::unique_ptr<FrontendAction> create() override { |
468 | return std::make_unique<ToolASTAction>(args&: Callback); |
469 | } |
470 | |
471 | private: |
472 | TUCallbackType Callback; |
473 | }; |
474 | |
475 | return std::make_unique<ToolActionFactory>( |
476 | args: [this](ASTContext &AST) { return callback(AST); }); |
477 | } |
478 | |
479 | // FIXME(ioeric): this seems to only works for changes in a single file at |
480 | // this point. |
481 | bool applySourceChanges() { |
482 | std::set<std::string> Files; |
483 | for (const auto &Change : Changes) |
484 | Files.insert(x: Change.getFilePath()); |
485 | // FIXME: Add automatic formatting support as well. |
486 | tooling::ApplyChangesSpec Spec; |
487 | // FIXME: We should probably cleanup the result by default as well. |
488 | Spec.Cleanup = false; |
489 | for (const auto &File : Files) { |
490 | llvm::ErrorOr<std::unique_ptr<llvm::MemoryBuffer>> BufferErr = |
491 | llvm::MemoryBuffer::getFile(Filename: File); |
492 | if (!BufferErr) { |
493 | llvm::errs() << "error: failed to open " << File << " for rewriting\n" ; |
494 | return true; |
495 | } |
496 | auto Result = tooling::applyAtomicChanges(FilePath: File, Code: (*BufferErr)->getBuffer(), |
497 | Changes, Spec); |
498 | if (!Result) { |
499 | llvm::errs() << toString(E: Result.takeError()); |
500 | return true; |
501 | } |
502 | |
503 | if (opts::Inplace) { |
504 | std::error_code EC; |
505 | llvm::raw_fd_ostream OS(File, EC, llvm::sys::fs::OF_TextWithCRLF); |
506 | if (EC) { |
507 | llvm::errs() << EC.message() << "\n" ; |
508 | return true; |
509 | } |
510 | OS << *Result; |
511 | continue; |
512 | } |
513 | |
514 | llvm::outs() << *Result; |
515 | } |
516 | return false; |
517 | } |
518 | |
519 | private: |
520 | /// Logs an individual refactoring action invocation to STDOUT. |
521 | void logInvocation(RefactoringActionSubcommand &Subcommand, |
522 | const RefactoringRuleContext &Context) { |
523 | llvm::outs() << "invoking action '" << Subcommand.getName() << "':\n" ; |
524 | if (Context.getSelectionRange().isValid()) { |
525 | SourceRange R = Context.getSelectionRange(); |
526 | llvm::outs() << " -selection=" ; |
527 | R.getBegin().print(OS&: llvm::outs(), SM: Context.getSources()); |
528 | llvm::outs() << " -> " ; |
529 | R.getEnd().print(OS&: llvm::outs(), SM: Context.getSources()); |
530 | llvm::outs() << "\n" ; |
531 | } |
532 | } |
533 | |
534 | llvm::Expected<RefactoringActionRule *> |
535 | getMatchingRule(RefactoringActionSubcommand &Subcommand) { |
536 | SmallVector<RefactoringActionRule *, 4> MatchingRules; |
537 | llvm::StringSet<> MissingOptions; |
538 | |
539 | for (const auto &Rule : Subcommand.getActionRules()) { |
540 | CommandLineRefactoringOptionVisitor Visitor(Subcommand.getOptions()); |
541 | Rule->visitRefactoringOptions(Visitor); |
542 | if (Visitor.getMissingRequiredOptions().empty()) { |
543 | if (!Rule->hasSelectionRequirement()) { |
544 | MatchingRules.push_back(Elt: Rule.get()); |
545 | } else { |
546 | Subcommand.parseSelectionArgument(); |
547 | if (Subcommand.getSelection()) { |
548 | MatchingRules.push_back(Elt: Rule.get()); |
549 | } else { |
550 | MissingOptions.insert(key: "selection" ); |
551 | } |
552 | } |
553 | } |
554 | for (const RefactoringOption *Opt : Visitor.getMissingRequiredOptions()) |
555 | MissingOptions.insert(key: Opt->getName()); |
556 | } |
557 | if (MatchingRules.empty()) { |
558 | std::string Error; |
559 | llvm::raw_string_ostream OS(Error); |
560 | OS << "ERROR: '" << Subcommand.getName() |
561 | << "' can't be invoked with the given arguments:\n" ; |
562 | for (const auto &Opt : MissingOptions) |
563 | OS << " missing '-" << Opt.getKey() << "' option\n" ; |
564 | return llvm::make_error<llvm::StringError>( |
565 | Args&: Error, Args: llvm::inconvertibleErrorCode()); |
566 | } |
567 | if (MatchingRules.size() != 1) { |
568 | return llvm::make_error<llvm::StringError>( |
569 | Args: llvm::Twine("ERROR: more than one matching rule of action" ) + |
570 | Subcommand.getName() + "was found with given options." , |
571 | Args: llvm::inconvertibleErrorCode()); |
572 | } |
573 | return MatchingRules.front(); |
574 | } |
575 | // Figure out which action is specified by the user. The user must specify the |
576 | // action using a command-line subcommand, e.g. the invocation `clang-refactor |
577 | // local-rename` corresponds to the `LocalRename` refactoring action. All |
578 | // subcommands must have a unique names. This allows us to figure out which |
579 | // refactoring action should be invoked by looking at the first subcommand |
580 | // that's enabled by LLVM's command-line parser. |
581 | llvm::Expected<RefactoringActionSubcommand *> getSelectedSubcommand() { |
582 | auto It = llvm::find_if( |
583 | Range&: SubCommands, |
584 | P: [](const std::unique_ptr<RefactoringActionSubcommand> &SubCommand) { |
585 | return !!(*SubCommand); |
586 | }); |
587 | if (It == SubCommands.end()) { |
588 | std::string Error; |
589 | llvm::raw_string_ostream OS(Error); |
590 | OS << "error: no refactoring action given\n" ; |
591 | OS << "note: the following actions are supported:\n" ; |
592 | for (const auto &Subcommand : SubCommands) |
593 | OS.indent(NumSpaces: 2) << Subcommand->getName() << "\n" ; |
594 | return llvm::make_error<llvm::StringError>( |
595 | Args&: Error, Args: llvm::inconvertibleErrorCode()); |
596 | } |
597 | RefactoringActionSubcommand *Subcommand = &(**It); |
598 | return Subcommand; |
599 | } |
600 | |
601 | std::vector<std::unique_ptr<RefactoringActionSubcommand>> SubCommands; |
602 | RefactoringActionSubcommand *SelectedSubcommand; |
603 | RefactoringActionRule *MatchingRule; |
604 | std::unique_ptr<ClangRefactorToolConsumerInterface> Consumer; |
605 | AtomicChanges Changes; |
606 | bool HasFailed; |
607 | }; |
608 | |
609 | } // end anonymous namespace |
610 | |
611 | int main(int argc, const char **argv) { |
612 | llvm::sys::PrintStackTraceOnErrorSignal(Argv0: argv[0]); |
613 | |
614 | ClangRefactorTool RefactorTool; |
615 | |
616 | auto ExpectedParser = CommonOptionsParser::create( |
617 | argc, argv, Category&: cl::getGeneralCategory(), OccurrencesFlag: cl::ZeroOrMore, |
618 | Overview: "Clang-based refactoring tool for C, C++ and Objective-C" ); |
619 | if (!ExpectedParser) { |
620 | llvm::errs() << ExpectedParser.takeError(); |
621 | return 1; |
622 | } |
623 | CommonOptionsParser &Options = ExpectedParser.get(); |
624 | |
625 | if (auto Err = RefactorTool.Init()) { |
626 | llvm::errs() << llvm::toString(E: std::move(Err)) << "\n" ; |
627 | return 1; |
628 | } |
629 | |
630 | auto ActionFactory = RefactorTool.getFrontendActionFactory(); |
631 | if (!ActionFactory) { |
632 | llvm::errs() << llvm::toString(E: ActionFactory.takeError()) << "\n" ; |
633 | return 1; |
634 | } |
635 | ClangTool Tool(Options.getCompilations(), Options.getSourcePathList()); |
636 | bool Failed = false; |
637 | if (Tool.run(Action: ActionFactory->get()) != 0) { |
638 | llvm::errs() << "Failed to run refactoring action on files\n" ; |
639 | // It is possible that TUs are broken while changes are generated correctly, |
640 | // so we still try applying changes. |
641 | Failed = true; |
642 | } |
643 | return RefactorTool.applySourceChanges() || Failed || |
644 | RefactorTool.hasFailed(); |
645 | } |
646 | |