1 //===--- ClangRefactor.cpp - Clang-based refactoring tool -----------------===//
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
7 //===----------------------------------------------------------------------===//
10 /// This file implements a clang-refactor tool that performs various
11 /// source transformations.
13 //===----------------------------------------------------------------------===//
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"
32 using namespace clang
;
33 using namespace tooling
;
34 using namespace refactor
;
35 namespace cl
= llvm::cl
;
39 static cl::OptionCategory
CommonRefactorOptions("Refactoring options");
41 static cl::opt
<bool> Verbose("v", cl::desc("Use verbose output"),
42 cl::cat(cl::getGeneralCategory()),
43 cl::sub(cl::SubCommand::getAll()));
45 static cl::opt
<bool> Inplace("i", cl::desc("Inplace edit <file>s"),
46 cl::cat(cl::getGeneralCategory()),
47 cl::sub(cl::SubCommand::getAll()));
49 } // end namespace opts
53 /// Stores the parsed `-selection` argument.
54 class SourceSelectionArgument
{
56 virtual ~SourceSelectionArgument() {}
58 /// Parse the `-selection` argument.
60 /// \returns A valid argument when the parse succedeed, null otherwise.
61 static std::unique_ptr
<SourceSelectionArgument
> fromString(StringRef Value
);
63 /// Prints any additional state associated with the selection argument to
64 /// the given output stream.
65 virtual void print(raw_ostream
&OS
) {}
67 /// Returns a replacement refactoring result consumer (if any) that should
68 /// consume the results of a refactoring operation.
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
75 virtual std::unique_ptr
<ClangRefactorToolConsumerInterface
>
76 createCustomConsumer() {
80 /// Runs the give refactoring function for each specified selection.
82 /// \returns true if an error occurred, false otherwise.
84 forAllRanges(const SourceManager
&SM
,
85 llvm::function_ref
<void(SourceRange R
)> Callback
) = 0;
88 /// Stores the parsed -selection=test:<filename> option.
89 class TestSourceSelectionArgument final
: public SourceSelectionArgument
{
91 TestSourceSelectionArgument(TestSelectionRangesInFile TestSelections
)
92 : TestSelections(std::move(TestSelections
)) {}
94 void print(raw_ostream
&OS
) override
{ TestSelections
.dump(OS
); }
96 std::unique_ptr
<ClangRefactorToolConsumerInterface
>
97 createCustomConsumer() override
{
98 return TestSelections
.createConsumer();
101 /// Testing support: invokes the selection action for each selection range in
103 bool forAllRanges(const SourceManager
&SM
,
104 llvm::function_ref
<void(SourceRange R
)> Callback
) override
{
105 return TestSelections
.foreachRange(SM
, Callback
);
109 TestSelectionRangesInFile TestSelections
;
112 /// Stores the parsed -selection=filename:line:column[-line:column] option.
113 class SourceRangeSelectionArgument final
: public SourceSelectionArgument
{
115 SourceRangeSelectionArgument(ParsedSourceRange Range
)
116 : Range(std::move(Range
)) {}
118 bool forAllRanges(const SourceManager
&SM
,
119 llvm::function_ref
<void(SourceRange R
)> Callback
) override
{
120 auto FE
= SM
.getFileManager().getFile(Range
.FileName
);
121 FileID FID
= FE
? SM
.translateFile(*FE
) : FileID();
122 if (!FE
|| FID
.isInvalid()) {
123 llvm::errs() << "error: -selection=" << Range
.FileName
124 << ":... : given file is not in the target TU\n";
128 SourceLocation Start
= SM
.getMacroArgExpandedLocation(
129 SM
.translateLineCol(FID
, Range
.Begin
.first
, Range
.Begin
.second
));
130 SourceLocation End
= SM
.getMacroArgExpandedLocation(
131 SM
.translateLineCol(FID
, Range
.End
.first
, 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";
139 Callback(SourceRange(Start
, End
));
144 ParsedSourceRange Range
;
147 std::unique_ptr
<SourceSelectionArgument
>
148 SourceSelectionArgument::fromString(StringRef Value
) {
149 if (Value
.startswith("test:")) {
150 StringRef Filename
= Value
.drop_front(strlen("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 std::move(*ParsedTestSelection
));
158 std::optional
<ParsedSourceRange
> Range
= ParsedSourceRange::fromString(Value
);
160 return std::make_unique
<SourceRangeSelectionArgument
>(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";
167 /// A container that stores the command-line options used by a single
168 /// refactoring option.
169 class RefactoringActionCommandLineOptions
{
171 void addStringOption(const RefactoringOption
&Option
,
172 std::unique_ptr
<cl::opt
<std::string
>> CLOption
) {
173 StringOptions
[&Option
] = std::move(CLOption
);
176 const cl::opt
<std::string
> &
177 getStringOption(const RefactoringOption
&Opt
) const {
178 auto It
= StringOptions
.find(&Opt
);
183 llvm::DenseMap
<const RefactoringOption
*,
184 std::unique_ptr
<cl::opt
<std::string
>>>
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
{
193 CommandLineRefactoringOptionVisitor(
194 const RefactoringActionCommandLineOptions
&Options
)
195 : Options(Options
) {}
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();
204 Value
= std::nullopt
;
205 if (Opt
.isRequired())
206 MissingRequiredOptions
.push_back(&Opt
);
209 ArrayRef
<const RefactoringOption
*> getMissingRequiredOptions() const {
210 return MissingRequiredOptions
;
214 llvm::SmallVector
<const RefactoringOption
*, 4> MissingRequiredOptions
;
215 const RefactoringActionCommandLineOptions
&Options
;
218 /// Creates the refactoring options used by all the rules in a single
219 /// refactoring action.
220 class CommandLineRefactoringOptionCreator final
221 : public RefactoringOptionVisitor
{
223 CommandLineRefactoringOptionCreator(
224 cl::OptionCategory
&Category
, cl::SubCommand
&Subcommand
,
225 RefactoringActionCommandLineOptions
&Options
)
226 : Category(Category
), Subcommand(Subcommand
), Options(Options
) {}
228 void visit(const RefactoringOption
&Opt
,
229 std::optional
<std::string
> &) override
{
230 if (Visited
.insert(&Opt
).second
)
231 Options
.addStringOption(Opt
, create
<std::string
>(Opt
));
235 template <typename T
>
236 std::unique_ptr
<cl::opt
<T
>> create(const RefactoringOption
&Opt
) {
237 if (!OptionNames
.insert(Opt
.getName()).second
)
238 llvm::report_fatal_error("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
));
247 llvm::SmallPtrSet
<const RefactoringOption
*, 8> Visited
;
248 llvm::StringSet
<> OptionNames
;
249 cl::OptionCategory
&Category
;
250 cl::SubCommand
&Subcommand
;
251 RefactoringActionCommandLineOptions
&Options
;
254 /// A subcommand that corresponds to individual refactoring action.
255 class RefactoringActionSubcommand
: public cl::SubCommand
{
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
>>(
268 "The selected source range in which the refactoring should "
269 "be initiated (<file>:<line>:<column>-<line>:<column> or "
270 "<file>:<line>:<column>)"),
271 cl::cat(Category
), cl::sub(*this));
275 // Create the refactoring options.
276 for (const auto &Rule
: this->ActionRules
) {
277 CommandLineRefactoringOptionCreator
OptionCreator(Category
, *this,
279 Rule
->visitRefactoringOptions(OptionCreator
);
283 ~RefactoringActionSubcommand() { unregisterSubCommand(); }
285 const RefactoringActionRules
&getActionRules() const { return ActionRules
; }
287 /// Parses the "-selection" command-line argument.
289 /// \returns true on error, false otherwise.
290 bool parseSelectionArgument() {
292 ParsedSelection
= SourceSelectionArgument::fromString(*Selection
);
293 if (!ParsedSelection
)
299 SourceSelectionArgument
*getSelection() const {
300 assert(Selection
&& "selection not supported!");
301 return ParsedSelection
.get();
304 const RefactoringActionCommandLineOptions
&getOptions() const {
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
;
316 class ClangRefactorConsumer final
: public ClangRefactorToolConsumerInterface
{
318 ClangRefactorConsumer(AtomicChanges
&Changes
) : SourceChanges(&Changes
) {}
320 void handleError(llvm::Error Err
) override
{
321 std::optional
<PartialDiagnosticAt
> Diag
= DiagnosticError::take(Err
);
323 llvm::errs() << llvm::toString(std::move(Err
)) << "\n";
326 llvm::cantFail(std::move(Err
)); // This is a success.
327 DiagnosticBuilder
DB(
328 getDiags().Report(Diag
->first
, Diag
->second
.getDiagID()));
329 Diag
->second
.Emit(DB
);
332 void handle(AtomicChanges Changes
) override
{
333 SourceChanges
->insert(SourceChanges
->begin(), Changes
.begin(),
337 void handle(SymbolOccurrences Occurrences
) override
{
338 llvm_unreachable("symbol occurrence results are not handled yet");
342 AtomicChanges
*SourceChanges
;
345 class ClangRefactorTool
{
348 : SelectedSubcommand(nullptr), MatchingRule(nullptr),
349 Consumer(new ClangRefactorConsumer(Changes
)), HasFailed(false) {
350 std::vector
<std::unique_ptr
<RefactoringAction
>> Actions
=
351 createRefactoringActions();
353 // Actions must have unique command names so that we can map them to one
355 llvm::StringSet
<> CommandNames
;
356 for (const auto &Action
: Actions
) {
357 if (!CommandNames
.insert(Action
->getCommand()).second
) {
358 llvm::errs() << "duplicate refactoring action command '"
359 << Action
->getCommand() << "'!";
364 // Create subcommands and command-line options.
365 for (auto &Action
: Actions
) {
366 SubCommands
.push_back(std::make_unique
<RefactoringActionSubcommand
>(
367 std::move(Action
), Action
->createActiveActionRules(),
368 opts::CommonRefactorOptions
));
372 // Initializes the selected subcommand and refactoring rule based on the
373 // command line options.
375 auto Subcommand
= getSelectedSubcommand();
377 return Subcommand
.takeError();
378 auto Rule
= getMatchingRule(**Subcommand
);
380 return Rule
.takeError();
382 SelectedSubcommand
= *Subcommand
;
383 MatchingRule
= *Rule
;
385 return llvm::Error::success();
388 bool hasFailed() const { return HasFailed
; }
390 using TUCallbackType
= std::function
<void(ASTContext
&)>;
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
);
398 // If the selection option is test specific, we use a test-specific
400 std::unique_ptr
<ClangRefactorToolConsumerInterface
> TestConsumer
;
401 bool HasSelection
= MatchingRule
->hasSelectionRequirement();
403 TestConsumer
= SelectedSubcommand
->getSelection()->createCustomConsumer();
404 ClangRefactorToolConsumerInterface
*ActiveConsumer
=
405 TestConsumer
? TestConsumer
.get() : Consumer
.get();
406 ActiveConsumer
->beginTU(AST
);
408 auto InvokeRule
= [&](RefactoringResultConsumer
&Consumer
) {
410 logInvocation(*SelectedSubcommand
, Context
);
411 MatchingRule
->invoke(*ActiveConsumer
, Context
);
414 assert(SelectedSubcommand
->getSelection() &&
415 "Missing selection argument?");
417 SelectedSubcommand
->getSelection()->print(llvm::outs());
418 if (SelectedSubcommand
->getSelection()->forAllRanges(
419 Context
.getSources(), [&](SourceRange R
) {
420 Context
.setSelectionRange(R
);
421 InvokeRule(*ActiveConsumer
);
424 ActiveConsumer
->endTU();
427 InvokeRule(*ActiveConsumer
);
428 ActiveConsumer
->endTU();
431 llvm::Expected
<std::unique_ptr
<FrontendActionFactory
>>
432 getFrontendActionFactory() {
433 class ToolASTConsumer
: public ASTConsumer
{
435 TUCallbackType Callback
;
436 ToolASTConsumer(TUCallbackType Callback
)
437 : Callback(std::move(Callback
)) {}
439 void HandleTranslationUnit(ASTContext
&Context
) override
{
443 class ToolASTAction
: public ASTFrontendAction
{
445 explicit ToolASTAction(TUCallbackType Callback
)
446 : Callback(std::move(Callback
)) {}
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
)};
458 TUCallbackType Callback
;
461 class ToolActionFactory
: public FrontendActionFactory
{
463 ToolActionFactory(TUCallbackType Callback
)
464 : Callback(std::move(Callback
)) {}
466 std::unique_ptr
<FrontendAction
> create() override
{
467 return std::make_unique
<ToolASTAction
>(Callback
);
471 TUCallbackType Callback
;
474 return std::make_unique
<ToolActionFactory
>(
475 [this](ASTContext
&AST
) { return callback(AST
); });
478 // FIXME(ioeric): this seems to only works for changes in a single file at
480 bool applySourceChanges() {
481 std::set
<std::string
> Files
;
482 for (const auto &Change
: Changes
)
483 Files
.insert(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(File
);
492 llvm::errs() << "error: failed to open " << File
<< " for rewriting\n";
495 auto Result
= tooling::applyAtomicChanges(File
, (*BufferErr
)->getBuffer(),
498 llvm::errs() << toString(Result
.takeError());
504 llvm::raw_fd_ostream
OS(File
, EC
, llvm::sys::fs::OF_TextWithCRLF
);
506 llvm::errs() << EC
.message() << "\n";
513 llvm::outs() << *Result
;
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(llvm::outs(), Context
.getSources());
527 llvm::outs() << " -> ";
528 R
.getEnd().print(llvm::outs(), Context
.getSources());
529 llvm::outs() << "\n";
533 llvm::Expected
<RefactoringActionRule
*>
534 getMatchingRule(RefactoringActionSubcommand
&Subcommand
) {
535 SmallVector
<RefactoringActionRule
*, 4> MatchingRules
;
536 llvm::StringSet
<> MissingOptions
;
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(Rule
.get());
545 Subcommand
.parseSelectionArgument();
546 if (Subcommand
.getSelection()) {
547 MatchingRules
.push_back(Rule
.get());
549 MissingOptions
.insert("selection");
553 for (const RefactoringOption
*Opt
: Visitor
.getMissingRequiredOptions())
554 MissingOptions
.insert(Opt
->getName());
556 if (MatchingRules
.empty()) {
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";
564 return llvm::make_error
<llvm::StringError
>(
565 Error
, llvm::inconvertibleErrorCode());
567 if (MatchingRules
.size() != 1) {
568 return llvm::make_error
<llvm::StringError
>(
569 llvm::Twine("ERROR: more than one matching rule of action") +
570 Subcommand
.getName() + "was found with given options.",
571 llvm::inconvertibleErrorCode());
573 return MatchingRules
.front();
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(
584 [](const std::unique_ptr
<RefactoringActionSubcommand
> &SubCommand
) {
585 return !!(*SubCommand
);
587 if (It
== SubCommands
.end()) {
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(2) << Subcommand
->getName() << "\n";
595 return llvm::make_error
<llvm::StringError
>(
596 Error
, llvm::inconvertibleErrorCode());
598 RefactoringActionSubcommand
*Subcommand
= &(**It
);
602 std::vector
<std::unique_ptr
<RefactoringActionSubcommand
>> SubCommands
;
603 RefactoringActionSubcommand
*SelectedSubcommand
;
604 RefactoringActionRule
*MatchingRule
;
605 std::unique_ptr
<ClangRefactorToolConsumerInterface
> Consumer
;
606 AtomicChanges Changes
;
610 } // end anonymous namespace
612 int main(int argc
, const char **argv
) {
613 llvm::sys::PrintStackTraceOnErrorSignal(argv
[0]);
615 ClangRefactorTool RefactorTool
;
617 auto ExpectedParser
= CommonOptionsParser::create(
618 argc
, argv
, cl::getGeneralCategory(), cl::ZeroOrMore
,
619 "Clang-based refactoring tool for C, C++ and Objective-C");
620 if (!ExpectedParser
) {
621 llvm::errs() << ExpectedParser
.takeError();
624 CommonOptionsParser
&Options
= ExpectedParser
.get();
626 if (auto Err
= RefactorTool
.Init()) {
627 llvm::errs() << llvm::toString(std::move(Err
)) << "\n";
631 auto ActionFactory
= RefactorTool
.getFrontendActionFactory();
632 if (!ActionFactory
) {
633 llvm::errs() << llvm::toString(ActionFactory
.takeError()) << "\n";
636 ClangTool
Tool(Options
.getCompilations(), Options
.getSourcePathList());
638 if (Tool
.run(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.
644 return RefactorTool
.applySourceChanges() || Failed
||
645 RefactorTool
.hasFailed();