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().getFile(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\n" ; |
164 | return nullptr; |
165 | } |
166 | |
167 | /// A container that stores the command-line options used by a single |
168 | /// refactoring option. |
169 | class RefactoringActionCommandLineOptions { |
170 | public: |
171 | void addStringOption(const RefactoringOption &Option, |
172 | std::unique_ptr<cl::opt<std::string>> CLOption) { |
173 | StringOptions[&Option] = std::move(CLOption); |
174 | } |
175 | |
176 | const cl::opt<std::string> & |
177 | getStringOption(const RefactoringOption &Opt) const { |
178 | auto It = StringOptions.find(Val: &Opt); |
179 | return *It->second; |
180 | } |
181 | |
182 | private: |
183 | llvm::DenseMap<const RefactoringOption *, |
184 | std::unique_ptr<cl::opt<std::string>>> |
185 | StringOptions; |
186 | }; |
187 | |
188 | /// Passes the command-line option values to the options used by a single |
189 | /// refactoring action rule. |
190 | class CommandLineRefactoringOptionVisitor final |
191 | : public RefactoringOptionVisitor { |
192 | public: |
193 | CommandLineRefactoringOptionVisitor( |
194 | const RefactoringActionCommandLineOptions &Options) |
195 | : Options(Options) {} |
196 | |
197 | void visit(const RefactoringOption &Opt, |
198 | std::optional<std::string> &Value) override { |
199 | const cl::opt<std::string> &CLOpt = Options.getStringOption(Opt); |
200 | if (!CLOpt.getValue().empty()) { |
201 | Value = CLOpt.getValue(); |
202 | return; |
203 | } |
204 | Value = std::nullopt; |
205 | if (Opt.isRequired()) |
206 | MissingRequiredOptions.push_back(Elt: &Opt); |
207 | } |
208 | |
209 | ArrayRef<const RefactoringOption *> getMissingRequiredOptions() const { |
210 | return MissingRequiredOptions; |
211 | } |
212 | |
213 | private: |
214 | llvm::SmallVector<const RefactoringOption *, 4> MissingRequiredOptions; |
215 | const RefactoringActionCommandLineOptions &Options; |
216 | }; |
217 | |
218 | /// Creates the refactoring options used by all the rules in a single |
219 | /// refactoring action. |
220 | class CommandLineRefactoringOptionCreator final |
221 | : public RefactoringOptionVisitor { |
222 | public: |
223 | CommandLineRefactoringOptionCreator( |
224 | cl::OptionCategory &Category, cl::SubCommand &Subcommand, |
225 | RefactoringActionCommandLineOptions &Options) |
226 | : Category(Category), Subcommand(Subcommand), Options(Options) {} |
227 | |
228 | void visit(const RefactoringOption &Opt, |
229 | std::optional<std::string> &) override { |
230 | if (Visited.insert(Ptr: &Opt).second) |
231 | Options.addStringOption(Option: Opt, CLOption: create<std::string>(Opt)); |
232 | } |
233 | |
234 | private: |
235 | template <typename T> |
236 | std::unique_ptr<cl::opt<T>> create(const RefactoringOption &Opt) { |
237 | if (!OptionNames.insert(key: Opt.getName()).second) |
238 | llvm::report_fatal_error(reason: "Multiple identical refactoring options " |
239 | "specified for one refactoring action" ); |
240 | // FIXME: cl::Required can be specified when this option is present |
241 | // in all rules in an action. |
242 | return std::make_unique<cl::opt<T>>( |
243 | Opt.getName(), cl::desc(Opt.getDescription()), cl::Optional, |
244 | cl::cat(Category), cl::sub(Subcommand)); |
245 | } |
246 | |
247 | llvm::SmallPtrSet<const RefactoringOption *, 8> Visited; |
248 | llvm::StringSet<> OptionNames; |
249 | cl::OptionCategory &Category; |
250 | cl::SubCommand &Subcommand; |
251 | RefactoringActionCommandLineOptions &Options; |
252 | }; |
253 | |
254 | /// A subcommand that corresponds to individual refactoring action. |
255 | class RefactoringActionSubcommand : public cl::SubCommand { |
256 | public: |
257 | RefactoringActionSubcommand(std::unique_ptr<RefactoringAction> Action, |
258 | RefactoringActionRules ActionRules, |
259 | cl::OptionCategory &Category) |
260 | : SubCommand(Action->getCommand(), Action->getDescription()), |
261 | Action(std::move(Action)), ActionRules(std::move(ActionRules)) { |
262 | // Check if the selection option is supported. |
263 | for (const auto &Rule : this->ActionRules) { |
264 | if (Rule->hasSelectionRequirement()) { |
265 | Selection = std::make_unique<cl::opt<std::string>>( |
266 | args: "selection" , |
267 | args: cl::desc( |
268 | "The selected source range in which the refactoring should " |
269 | "be initiated (<file>:<line>:<column>-<line>:<column> or " |
270 | "<file>:<line>:<column>)" ), |
271 | args: cl::cat(Category), args: cl::sub(*this)); |
272 | break; |
273 | } |
274 | } |
275 | // Create the refactoring options. |
276 | for (const auto &Rule : this->ActionRules) { |
277 | CommandLineRefactoringOptionCreator OptionCreator(Category, *this, |
278 | Options); |
279 | Rule->visitRefactoringOptions(Visitor&: OptionCreator); |
280 | } |
281 | } |
282 | |
283 | ~RefactoringActionSubcommand() { unregisterSubCommand(); } |
284 | |
285 | const RefactoringActionRules &getActionRules() const { return ActionRules; } |
286 | |
287 | /// Parses the "-selection" command-line argument. |
288 | /// |
289 | /// \returns true on error, false otherwise. |
290 | bool parseSelectionArgument() { |
291 | if (Selection) { |
292 | ParsedSelection = SourceSelectionArgument::fromString(Value: *Selection); |
293 | if (!ParsedSelection) |
294 | return true; |
295 | } |
296 | return false; |
297 | } |
298 | |
299 | SourceSelectionArgument *getSelection() const { |
300 | assert(Selection && "selection not supported!" ); |
301 | return ParsedSelection.get(); |
302 | } |
303 | |
304 | const RefactoringActionCommandLineOptions &getOptions() const { |
305 | return Options; |
306 | } |
307 | |
308 | private: |
309 | std::unique_ptr<RefactoringAction> Action; |
310 | RefactoringActionRules ActionRules; |
311 | std::unique_ptr<cl::opt<std::string>> Selection; |
312 | std::unique_ptr<SourceSelectionArgument> ParsedSelection; |
313 | RefactoringActionCommandLineOptions Options; |
314 | }; |
315 | |
316 | class ClangRefactorConsumer final : public ClangRefactorToolConsumerInterface { |
317 | public: |
318 | ClangRefactorConsumer(AtomicChanges &Changes) : SourceChanges(&Changes) {} |
319 | |
320 | void handleError(llvm::Error Err) override { |
321 | std::optional<PartialDiagnosticAt> Diag = DiagnosticError::take(Err); |
322 | if (!Diag) { |
323 | llvm::errs() << llvm::toString(E: std::move(Err)) << "\n" ; |
324 | return; |
325 | } |
326 | llvm::cantFail(Err: std::move(Err)); // This is a success. |
327 | DiagnosticBuilder DB( |
328 | getDiags().Report(Loc: Diag->first, DiagID: Diag->second.getDiagID())); |
329 | Diag->second.Emit(DB); |
330 | } |
331 | |
332 | void handle(AtomicChanges Changes) override { |
333 | SourceChanges->insert(position: SourceChanges->begin(), first: Changes.begin(), |
334 | last: Changes.end()); |
335 | } |
336 | |
337 | void handle(SymbolOccurrences Occurrences) override { |
338 | llvm_unreachable("symbol occurrence results are not handled yet" ); |
339 | } |
340 | |
341 | private: |
342 | AtomicChanges *SourceChanges; |
343 | }; |
344 | |
345 | class ClangRefactorTool { |
346 | public: |
347 | ClangRefactorTool() |
348 | : SelectedSubcommand(nullptr), MatchingRule(nullptr), |
349 | Consumer(new ClangRefactorConsumer(Changes)), HasFailed(false) { |
350 | std::vector<std::unique_ptr<RefactoringAction>> Actions = |
351 | createRefactoringActions(); |
352 | |
353 | // Actions must have unique command names so that we can map them to one |
354 | // subcommand. |
355 | llvm::StringSet<> CommandNames; |
356 | for (const auto &Action : Actions) { |
357 | if (!CommandNames.insert(key: Action->getCommand()).second) { |
358 | llvm::errs() << "duplicate refactoring action command '" |
359 | << Action->getCommand() << "'!" ; |
360 | exit(status: 1); |
361 | } |
362 | } |
363 | |
364 | // Create subcommands and command-line options. |
365 | for (auto &Action : Actions) { |
366 | SubCommands.push_back(x: std::make_unique<RefactoringActionSubcommand>( |
367 | args: std::move(Action), args: Action->createActiveActionRules(), |
368 | args&: opts::CommonRefactorOptions)); |
369 | } |
370 | } |
371 | |
372 | // Initializes the selected subcommand and refactoring rule based on the |
373 | // command line options. |
374 | llvm::Error Init() { |
375 | auto Subcommand = getSelectedSubcommand(); |
376 | if (!Subcommand) |
377 | return Subcommand.takeError(); |
378 | auto Rule = getMatchingRule(Subcommand&: **Subcommand); |
379 | if (!Rule) |
380 | return Rule.takeError(); |
381 | |
382 | SelectedSubcommand = *Subcommand; |
383 | MatchingRule = *Rule; |
384 | |
385 | return llvm::Error::success(); |
386 | } |
387 | |
388 | bool hasFailed() const { return HasFailed; } |
389 | |
390 | using TUCallbackType = std::function<void(ASTContext &)>; |
391 | |
392 | // Callback of an AST action. This invokes the matching rule on the given AST. |
393 | void callback(ASTContext &AST) { |
394 | assert(SelectedSubcommand && MatchingRule && Consumer); |
395 | RefactoringRuleContext Context(AST.getSourceManager()); |
396 | Context.setASTContext(AST); |
397 | |
398 | // If the selection option is test specific, we use a test-specific |
399 | // consumer. |
400 | std::unique_ptr<ClangRefactorToolConsumerInterface> TestConsumer; |
401 | bool HasSelection = MatchingRule->hasSelectionRequirement(); |
402 | if (HasSelection) |
403 | TestConsumer = SelectedSubcommand->getSelection()->createCustomConsumer(); |
404 | ClangRefactorToolConsumerInterface *ActiveConsumer = |
405 | TestConsumer ? TestConsumer.get() : Consumer.get(); |
406 | ActiveConsumer->beginTU(Context&: AST); |
407 | |
408 | auto InvokeRule = [&](RefactoringResultConsumer &Consumer) { |
409 | if (opts::Verbose) |
410 | logInvocation(Subcommand&: *SelectedSubcommand, Context); |
411 | MatchingRule->invoke(Consumer&: *ActiveConsumer, Context); |
412 | }; |
413 | if (HasSelection) { |
414 | assert(SelectedSubcommand->getSelection() && |
415 | "Missing selection argument?" ); |
416 | if (opts::Verbose) |
417 | SelectedSubcommand->getSelection()->print(OS&: llvm::outs()); |
418 | if (SelectedSubcommand->getSelection()->forAllRanges( |
419 | SM: Context.getSources(), Callback: [&](SourceRange R) { |
420 | Context.setSelectionRange(R); |
421 | InvokeRule(*ActiveConsumer); |
422 | })) |
423 | HasFailed = true; |
424 | ActiveConsumer->endTU(); |
425 | return; |
426 | } |
427 | InvokeRule(*ActiveConsumer); |
428 | ActiveConsumer->endTU(); |
429 | } |
430 | |
431 | llvm::Expected<std::unique_ptr<FrontendActionFactory>> |
432 | getFrontendActionFactory() { |
433 | class ToolASTConsumer : public ASTConsumer { |
434 | public: |
435 | TUCallbackType Callback; |
436 | ToolASTConsumer(TUCallbackType Callback) |
437 | : Callback(std::move(Callback)) {} |
438 | |
439 | void HandleTranslationUnit(ASTContext &Context) override { |
440 | Callback(Context); |
441 | } |
442 | }; |
443 | class ToolASTAction : public ASTFrontendAction { |
444 | public: |
445 | explicit ToolASTAction(TUCallbackType Callback) |
446 | : Callback(std::move(Callback)) {} |
447 | |
448 | protected: |
449 | std::unique_ptr<clang::ASTConsumer> |
450 | CreateASTConsumer(clang::CompilerInstance &compiler, |
451 | StringRef /* dummy */) override { |
452 | std::unique_ptr<clang::ASTConsumer> Consumer{ |
453 | new ToolASTConsumer(Callback)}; |
454 | return Consumer; |
455 | } |
456 | |
457 | private: |
458 | TUCallbackType Callback; |
459 | }; |
460 | |
461 | class ToolActionFactory : public FrontendActionFactory { |
462 | public: |
463 | ToolActionFactory(TUCallbackType Callback) |
464 | : Callback(std::move(Callback)) {} |
465 | |
466 | std::unique_ptr<FrontendAction> create() override { |
467 | return std::make_unique<ToolASTAction>(args&: Callback); |
468 | } |
469 | |
470 | private: |
471 | TUCallbackType Callback; |
472 | }; |
473 | |
474 | return std::make_unique<ToolActionFactory>( |
475 | args: [this](ASTContext &AST) { return callback(AST); }); |
476 | } |
477 | |
478 | // FIXME(ioeric): this seems to only works for changes in a single file at |
479 | // this point. |
480 | bool applySourceChanges() { |
481 | std::set<std::string> Files; |
482 | for (const auto &Change : Changes) |
483 | Files.insert(x: Change.getFilePath()); |
484 | // FIXME: Add automatic formatting support as well. |
485 | tooling::ApplyChangesSpec Spec; |
486 | // FIXME: We should probably cleanup the result by default as well. |
487 | Spec.Cleanup = false; |
488 | for (const auto &File : Files) { |
489 | llvm::ErrorOr<std::unique_ptr<llvm::MemoryBuffer>> BufferErr = |
490 | llvm::MemoryBuffer::getFile(Filename: File); |
491 | if (!BufferErr) { |
492 | llvm::errs() << "error: failed to open " << File << " for rewriting\n" ; |
493 | return true; |
494 | } |
495 | auto Result = tooling::applyAtomicChanges(FilePath: File, Code: (*BufferErr)->getBuffer(), |
496 | Changes, Spec); |
497 | if (!Result) { |
498 | llvm::errs() << toString(E: Result.takeError()); |
499 | return true; |
500 | } |
501 | |
502 | if (opts::Inplace) { |
503 | std::error_code EC; |
504 | llvm::raw_fd_ostream OS(File, EC, llvm::sys::fs::OF_TextWithCRLF); |
505 | if (EC) { |
506 | llvm::errs() << EC.message() << "\n" ; |
507 | return true; |
508 | } |
509 | OS << *Result; |
510 | continue; |
511 | } |
512 | |
513 | llvm::outs() << *Result; |
514 | } |
515 | return false; |
516 | } |
517 | |
518 | private: |
519 | /// Logs an individual refactoring action invocation to STDOUT. |
520 | void logInvocation(RefactoringActionSubcommand &Subcommand, |
521 | const RefactoringRuleContext &Context) { |
522 | llvm::outs() << "invoking action '" << Subcommand.getName() << "':\n" ; |
523 | if (Context.getSelectionRange().isValid()) { |
524 | SourceRange R = Context.getSelectionRange(); |
525 | llvm::outs() << " -selection=" ; |
526 | R.getBegin().print(OS&: llvm::outs(), SM: Context.getSources()); |
527 | llvm::outs() << " -> " ; |
528 | R.getEnd().print(OS&: llvm::outs(), SM: Context.getSources()); |
529 | llvm::outs() << "\n" ; |
530 | } |
531 | } |
532 | |
533 | llvm::Expected<RefactoringActionRule *> |
534 | getMatchingRule(RefactoringActionSubcommand &Subcommand) { |
535 | SmallVector<RefactoringActionRule *, 4> MatchingRules; |
536 | llvm::StringSet<> MissingOptions; |
537 | |
538 | for (const auto &Rule : Subcommand.getActionRules()) { |
539 | CommandLineRefactoringOptionVisitor Visitor(Subcommand.getOptions()); |
540 | Rule->visitRefactoringOptions(Visitor); |
541 | if (Visitor.getMissingRequiredOptions().empty()) { |
542 | if (!Rule->hasSelectionRequirement()) { |
543 | MatchingRules.push_back(Elt: Rule.get()); |
544 | } else { |
545 | Subcommand.parseSelectionArgument(); |
546 | if (Subcommand.getSelection()) { |
547 | MatchingRules.push_back(Elt: Rule.get()); |
548 | } else { |
549 | MissingOptions.insert(key: "selection" ); |
550 | } |
551 | } |
552 | } |
553 | for (const RefactoringOption *Opt : Visitor.getMissingRequiredOptions()) |
554 | MissingOptions.insert(key: Opt->getName()); |
555 | } |
556 | if (MatchingRules.empty()) { |
557 | std::string Error; |
558 | llvm::raw_string_ostream OS(Error); |
559 | OS << "ERROR: '" << Subcommand.getName() |
560 | << "' can't be invoked with the given arguments:\n" ; |
561 | for (const auto &Opt : MissingOptions) |
562 | OS << " missing '-" << Opt.getKey() << "' option\n" ; |
563 | OS.flush(); |
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 | OS.flush(); |
595 | return llvm::make_error<llvm::StringError>( |
596 | Args&: Error, Args: llvm::inconvertibleErrorCode()); |
597 | } |
598 | RefactoringActionSubcommand *Subcommand = &(**It); |
599 | return Subcommand; |
600 | } |
601 | |
602 | std::vector<std::unique_ptr<RefactoringActionSubcommand>> SubCommands; |
603 | RefactoringActionSubcommand *SelectedSubcommand; |
604 | RefactoringActionRule *MatchingRule; |
605 | std::unique_ptr<ClangRefactorToolConsumerInterface> Consumer; |
606 | AtomicChanges Changes; |
607 | bool HasFailed; |
608 | }; |
609 | |
610 | } // end anonymous namespace |
611 | |
612 | int main(int argc, const char **argv) { |
613 | llvm::sys::PrintStackTraceOnErrorSignal(Argv0: argv[0]); |
614 | |
615 | ClangRefactorTool RefactorTool; |
616 | |
617 | auto ExpectedParser = CommonOptionsParser::create( |
618 | argc, argv, Category&: cl::getGeneralCategory(), OccurrencesFlag: cl::ZeroOrMore, |
619 | Overview: "Clang-based refactoring tool for C, C++ and Objective-C" ); |
620 | if (!ExpectedParser) { |
621 | llvm::errs() << ExpectedParser.takeError(); |
622 | return 1; |
623 | } |
624 | CommonOptionsParser &Options = ExpectedParser.get(); |
625 | |
626 | if (auto Err = RefactorTool.Init()) { |
627 | llvm::errs() << llvm::toString(E: std::move(Err)) << "\n" ; |
628 | return 1; |
629 | } |
630 | |
631 | auto ActionFactory = RefactorTool.getFrontendActionFactory(); |
632 | if (!ActionFactory) { |
633 | llvm::errs() << llvm::toString(E: ActionFactory.takeError()) << "\n" ; |
634 | return 1; |
635 | } |
636 | ClangTool Tool(Options.getCompilations(), Options.getSourcePathList()); |
637 | bool Failed = false; |
638 | if (Tool.run(Action: ActionFactory->get()) != 0) { |
639 | llvm::errs() << "Failed to run refactoring action on files\n" ; |
640 | // It is possible that TUs are broken while changes are generated correctly, |
641 | // so we still try applying changes. |
642 | Failed = true; |
643 | } |
644 | return RefactorTool.applySourceChanges() || Failed || |
645 | RefactorTool.hasFailed(); |
646 | } |
647 | |