123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637 |
- //===--- ClangRefactor.cpp - Clang-based refactoring tool -----------------===//
- //
- // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
- // See https://llvm.org/LICENSE.txt for license information.
- // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
- //
- //===----------------------------------------------------------------------===//
- ///
- /// \file
- /// This file implements a clang-refactor tool that performs various
- /// source transformations.
- ///
- //===----------------------------------------------------------------------===//
- #include "TestSupport.h"
- #include "clang/Frontend/CommandLineSourceLoc.h"
- #include "clang/Frontend/TextDiagnosticPrinter.h"
- #include "clang/Rewrite/Core/Rewriter.h"
- #include "clang/Tooling/CommonOptionsParser.h"
- #include "clang/Tooling/Refactoring.h"
- #include "clang/Tooling/Refactoring/RefactoringAction.h"
- #include "clang/Tooling/Refactoring/RefactoringOptions.h"
- #include "clang/Tooling/Refactoring/Rename/RenamingAction.h"
- #include "clang/Tooling/Tooling.h"
- #include "llvm/Support/CommandLine.h"
- #include "llvm/Support/FileSystem.h"
- #include "llvm/Support/Signals.h"
- #include "llvm/Support/raw_ostream.h"
- #include <string>
- using namespace clang;
- using namespace tooling;
- using namespace refactor;
- namespace cl = llvm::cl;
- namespace opts {
- static cl::OptionCategory CommonRefactorOptions("Refactoring options");
- static cl::opt<bool> Verbose("v", cl::desc("Use verbose output"),
- cl::cat(cl::GeneralCategory),
- cl::sub(*cl::AllSubCommands));
- static cl::opt<bool> Inplace("i", cl::desc("Inplace edit <file>s"),
- cl::cat(cl::GeneralCategory),
- cl::sub(*cl::AllSubCommands));
- } // end namespace opts
- namespace {
- /// Stores the parsed `-selection` argument.
- class SourceSelectionArgument {
- public:
- virtual ~SourceSelectionArgument() {}
- /// Parse the `-selection` argument.
- ///
- /// \returns A valid argument when the parse succedeed, null otherwise.
- static std::unique_ptr<SourceSelectionArgument> fromString(StringRef Value);
- /// Prints any additional state associated with the selection argument to
- /// the given output stream.
- virtual void print(raw_ostream &OS) {}
- /// Returns a replacement refactoring result consumer (if any) that should
- /// consume the results of a refactoring operation.
- ///
- /// The replacement refactoring result consumer is used by \c
- /// TestSourceSelectionArgument to inject a test-specific result handling
- /// logic into the refactoring operation. The test-specific consumer
- /// ensures that the individual results in a particular test group are
- /// identical.
- virtual std::unique_ptr<ClangRefactorToolConsumerInterface>
- createCustomConsumer() {
- return nullptr;
- }
- /// Runs the give refactoring function for each specified selection.
- ///
- /// \returns true if an error occurred, false otherwise.
- virtual bool
- forAllRanges(const SourceManager &SM,
- llvm::function_ref<void(SourceRange R)> Callback) = 0;
- };
- /// Stores the parsed -selection=test:<filename> option.
- class TestSourceSelectionArgument final : public SourceSelectionArgument {
- public:
- TestSourceSelectionArgument(TestSelectionRangesInFile TestSelections)
- : TestSelections(std::move(TestSelections)) {}
- void print(raw_ostream &OS) override { TestSelections.dump(OS); }
- std::unique_ptr<ClangRefactorToolConsumerInterface>
- createCustomConsumer() override {
- return TestSelections.createConsumer();
- }
- /// Testing support: invokes the selection action for each selection range in
- /// the test file.
- bool forAllRanges(const SourceManager &SM,
- llvm::function_ref<void(SourceRange R)> Callback) override {
- return TestSelections.foreachRange(SM, Callback);
- }
- private:
- TestSelectionRangesInFile TestSelections;
- };
- /// Stores the parsed -selection=filename:line:column[-line:column] option.
- class SourceRangeSelectionArgument final : public SourceSelectionArgument {
- public:
- SourceRangeSelectionArgument(ParsedSourceRange Range)
- : Range(std::move(Range)) {}
- bool forAllRanges(const SourceManager &SM,
- llvm::function_ref<void(SourceRange R)> Callback) override {
- auto FE = SM.getFileManager().getFile(Range.FileName);
- FileID FID = FE ? SM.translateFile(*FE) : FileID();
- if (!FE || FID.isInvalid()) {
- llvm::errs() << "error: -selection=" << Range.FileName
- << ":... : given file is not in the target TU\n";
- return true;
- }
- SourceLocation Start = SM.getMacroArgExpandedLocation(
- SM.translateLineCol(FID, Range.Begin.first, Range.Begin.second));
- SourceLocation End = SM.getMacroArgExpandedLocation(
- SM.translateLineCol(FID, Range.End.first, Range.End.second));
- if (Start.isInvalid() || End.isInvalid()) {
- llvm::errs() << "error: -selection=" << Range.FileName << ':'
- << Range.Begin.first << ':' << Range.Begin.second << '-'
- << Range.End.first << ':' << Range.End.second
- << " : invalid source location\n";
- return true;
- }
- Callback(SourceRange(Start, End));
- return false;
- }
- private:
- ParsedSourceRange Range;
- };
- std::unique_ptr<SourceSelectionArgument>
- SourceSelectionArgument::fromString(StringRef Value) {
- if (Value.startswith("test:")) {
- StringRef Filename = Value.drop_front(strlen("test:"));
- Optional<TestSelectionRangesInFile> ParsedTestSelection =
- findTestSelectionRanges(Filename);
- if (!ParsedTestSelection)
- return nullptr; // A parsing error was already reported.
- return llvm::make_unique<TestSourceSelectionArgument>(
- std::move(*ParsedTestSelection));
- }
- Optional<ParsedSourceRange> Range = ParsedSourceRange::fromString(Value);
- if (Range)
- return llvm::make_unique<SourceRangeSelectionArgument>(std::move(*Range));
- llvm::errs() << "error: '-selection' option must be specified using "
- "<file>:<line>:<column> or "
- "<file>:<line>:<column>-<line>:<column> format\n";
- return nullptr;
- }
- /// A container that stores the command-line options used by a single
- /// refactoring option.
- class RefactoringActionCommandLineOptions {
- public:
- void addStringOption(const RefactoringOption &Option,
- std::unique_ptr<cl::opt<std::string>> CLOption) {
- StringOptions[&Option] = std::move(CLOption);
- }
- const cl::opt<std::string> &
- getStringOption(const RefactoringOption &Opt) const {
- auto It = StringOptions.find(&Opt);
- return *It->second;
- }
- private:
- llvm::DenseMap<const RefactoringOption *,
- std::unique_ptr<cl::opt<std::string>>>
- StringOptions;
- };
- /// Passes the command-line option values to the options used by a single
- /// refactoring action rule.
- class CommandLineRefactoringOptionVisitor final
- : public RefactoringOptionVisitor {
- public:
- CommandLineRefactoringOptionVisitor(
- const RefactoringActionCommandLineOptions &Options)
- : Options(Options) {}
- void visit(const RefactoringOption &Opt,
- Optional<std::string> &Value) override {
- const cl::opt<std::string> &CLOpt = Options.getStringOption(Opt);
- if (!CLOpt.getValue().empty()) {
- Value = CLOpt.getValue();
- return;
- }
- Value = None;
- if (Opt.isRequired())
- MissingRequiredOptions.push_back(&Opt);
- }
- ArrayRef<const RefactoringOption *> getMissingRequiredOptions() const {
- return MissingRequiredOptions;
- }
- private:
- llvm::SmallVector<const RefactoringOption *, 4> MissingRequiredOptions;
- const RefactoringActionCommandLineOptions &Options;
- };
- /// Creates the refactoring options used by all the rules in a single
- /// refactoring action.
- class CommandLineRefactoringOptionCreator final
- : public RefactoringOptionVisitor {
- public:
- CommandLineRefactoringOptionCreator(
- cl::OptionCategory &Category, cl::SubCommand &Subcommand,
- RefactoringActionCommandLineOptions &Options)
- : Category(Category), Subcommand(Subcommand), Options(Options) {}
- void visit(const RefactoringOption &Opt, Optional<std::string> &) override {
- if (Visited.insert(&Opt).second)
- Options.addStringOption(Opt, create<std::string>(Opt));
- }
- private:
- template <typename T>
- std::unique_ptr<cl::opt<T>> create(const RefactoringOption &Opt) {
- if (!OptionNames.insert(Opt.getName()).second)
- llvm::report_fatal_error("Multiple identical refactoring options "
- "specified for one refactoring action");
- // FIXME: cl::Required can be specified when this option is present
- // in all rules in an action.
- return llvm::make_unique<cl::opt<T>>(
- Opt.getName(), cl::desc(Opt.getDescription()), cl::Optional,
- cl::cat(Category), cl::sub(Subcommand));
- }
- llvm::SmallPtrSet<const RefactoringOption *, 8> Visited;
- llvm::StringSet<> OptionNames;
- cl::OptionCategory &Category;
- cl::SubCommand &Subcommand;
- RefactoringActionCommandLineOptions &Options;
- };
- /// A subcommand that corresponds to individual refactoring action.
- class RefactoringActionSubcommand : public cl::SubCommand {
- public:
- RefactoringActionSubcommand(std::unique_ptr<RefactoringAction> Action,
- RefactoringActionRules ActionRules,
- cl::OptionCategory &Category)
- : SubCommand(Action->getCommand(), Action->getDescription()),
- Action(std::move(Action)), ActionRules(std::move(ActionRules)) {
- // Check if the selection option is supported.
- for (const auto &Rule : this->ActionRules) {
- if (Rule->hasSelectionRequirement()) {
- Selection = llvm::make_unique<cl::opt<std::string>>(
- "selection",
- cl::desc(
- "The selected source range in which the refactoring should "
- "be initiated (<file>:<line>:<column>-<line>:<column> or "
- "<file>:<line>:<column>)"),
- cl::cat(Category), cl::sub(*this));
- break;
- }
- }
- // Create the refactoring options.
- for (const auto &Rule : this->ActionRules) {
- CommandLineRefactoringOptionCreator OptionCreator(Category, *this,
- Options);
- Rule->visitRefactoringOptions(OptionCreator);
- }
- }
- ~RefactoringActionSubcommand() { unregisterSubCommand(); }
- const RefactoringActionRules &getActionRules() const { return ActionRules; }
- /// Parses the "-selection" command-line argument.
- ///
- /// \returns true on error, false otherwise.
- bool parseSelectionArgument() {
- if (Selection) {
- ParsedSelection = SourceSelectionArgument::fromString(*Selection);
- if (!ParsedSelection)
- return true;
- }
- return false;
- }
- SourceSelectionArgument *getSelection() const {
- assert(Selection && "selection not supported!");
- return ParsedSelection.get();
- }
- const RefactoringActionCommandLineOptions &getOptions() const {
- return Options;
- }
- private:
- std::unique_ptr<RefactoringAction> Action;
- RefactoringActionRules ActionRules;
- std::unique_ptr<cl::opt<std::string>> Selection;
- std::unique_ptr<SourceSelectionArgument> ParsedSelection;
- RefactoringActionCommandLineOptions Options;
- };
- class ClangRefactorConsumer final : public ClangRefactorToolConsumerInterface {
- public:
- ClangRefactorConsumer(AtomicChanges &Changes) : SourceChanges(&Changes) {}
- void handleError(llvm::Error Err) override {
- Optional<PartialDiagnosticAt> Diag = DiagnosticError::take(Err);
- if (!Diag) {
- llvm::errs() << llvm::toString(std::move(Err)) << "\n";
- return;
- }
- llvm::cantFail(std::move(Err)); // This is a success.
- DiagnosticBuilder DB(
- getDiags().Report(Diag->first, Diag->second.getDiagID()));
- Diag->second.Emit(DB);
- }
- void handle(AtomicChanges Changes) override {
- SourceChanges->insert(SourceChanges->begin(), Changes.begin(),
- Changes.end());
- }
- void handle(SymbolOccurrences Occurrences) override {
- llvm_unreachable("symbol occurrence results are not handled yet");
- }
- private:
- AtomicChanges *SourceChanges;
- };
- class ClangRefactorTool {
- public:
- ClangRefactorTool()
- : SelectedSubcommand(nullptr), MatchingRule(nullptr),
- Consumer(new ClangRefactorConsumer(Changes)), HasFailed(false) {
- std::vector<std::unique_ptr<RefactoringAction>> Actions =
- createRefactoringActions();
- // Actions must have unique command names so that we can map them to one
- // subcommand.
- llvm::StringSet<> CommandNames;
- for (const auto &Action : Actions) {
- if (!CommandNames.insert(Action->getCommand()).second) {
- llvm::errs() << "duplicate refactoring action command '"
- << Action->getCommand() << "'!";
- exit(1);
- }
- }
- // Create subcommands and command-line options.
- for (auto &Action : Actions) {
- SubCommands.push_back(llvm::make_unique<RefactoringActionSubcommand>(
- std::move(Action), Action->createActiveActionRules(),
- opts::CommonRefactorOptions));
- }
- }
- // Initializes the selected subcommand and refactoring rule based on the
- // command line options.
- llvm::Error Init() {
- auto Subcommand = getSelectedSubcommand();
- if (!Subcommand)
- return Subcommand.takeError();
- auto Rule = getMatchingRule(**Subcommand);
- if (!Rule)
- return Rule.takeError();
- SelectedSubcommand = *Subcommand;
- MatchingRule = *Rule;
- return llvm::Error::success();
- }
- bool hasFailed() const { return HasFailed; }
- using TUCallbackType = std::function<void(ASTContext &)>;
- // Callback of an AST action. This invokes the matching rule on the given AST.
- void callback(ASTContext &AST) {
- assert(SelectedSubcommand && MatchingRule && Consumer);
- RefactoringRuleContext Context(AST.getSourceManager());
- Context.setASTContext(AST);
- // If the selection option is test specific, we use a test-specific
- // consumer.
- std::unique_ptr<ClangRefactorToolConsumerInterface> TestConsumer;
- bool HasSelection = MatchingRule->hasSelectionRequirement();
- if (HasSelection)
- TestConsumer = SelectedSubcommand->getSelection()->createCustomConsumer();
- ClangRefactorToolConsumerInterface *ActiveConsumer =
- TestConsumer ? TestConsumer.get() : Consumer.get();
- ActiveConsumer->beginTU(AST);
- auto InvokeRule = [&](RefactoringResultConsumer &Consumer) {
- if (opts::Verbose)
- logInvocation(*SelectedSubcommand, Context);
- MatchingRule->invoke(*ActiveConsumer, Context);
- };
- if (HasSelection) {
- assert(SelectedSubcommand->getSelection() &&
- "Missing selection argument?");
- if (opts::Verbose)
- SelectedSubcommand->getSelection()->print(llvm::outs());
- if (SelectedSubcommand->getSelection()->forAllRanges(
- Context.getSources(), [&](SourceRange R) {
- Context.setSelectionRange(R);
- InvokeRule(*ActiveConsumer);
- }))
- HasFailed = true;
- ActiveConsumer->endTU();
- return;
- }
- InvokeRule(*ActiveConsumer);
- ActiveConsumer->endTU();
- }
- llvm::Expected<std::unique_ptr<FrontendActionFactory>>
- getFrontendActionFactory() {
- class ToolASTConsumer : public ASTConsumer {
- public:
- TUCallbackType Callback;
- ToolASTConsumer(TUCallbackType Callback)
- : Callback(std::move(Callback)) {}
- void HandleTranslationUnit(ASTContext &Context) override {
- Callback(Context);
- }
- };
- class ToolASTAction : public ASTFrontendAction {
- public:
- explicit ToolASTAction(TUCallbackType Callback)
- : Callback(std::move(Callback)) {}
- protected:
- std::unique_ptr<clang::ASTConsumer>
- CreateASTConsumer(clang::CompilerInstance &compiler,
- StringRef /* dummy */) override {
- std::unique_ptr<clang::ASTConsumer> Consumer{
- new ToolASTConsumer(Callback)};
- return Consumer;
- }
- private:
- TUCallbackType Callback;
- };
- class ToolActionFactory : public FrontendActionFactory {
- public:
- ToolActionFactory(TUCallbackType Callback)
- : Callback(std::move(Callback)) {}
- FrontendAction *create() override { return new ToolASTAction(Callback); }
- private:
- TUCallbackType Callback;
- };
- return llvm::make_unique<ToolActionFactory>(
- [this](ASTContext &AST) { return callback(AST); });
- }
- // FIXME(ioeric): this seems to only works for changes in a single file at
- // this point.
- bool applySourceChanges() {
- std::set<std::string> Files;
- for (const auto &Change : Changes)
- Files.insert(Change.getFilePath());
- // FIXME: Add automatic formatting support as well.
- tooling::ApplyChangesSpec Spec;
- // FIXME: We should probably cleanup the result by default as well.
- Spec.Cleanup = false;
- for (const auto &File : Files) {
- llvm::ErrorOr<std::unique_ptr<llvm::MemoryBuffer>> BufferErr =
- llvm::MemoryBuffer::getFile(File);
- if (!BufferErr) {
- llvm::errs() << "error: failed to open " << File << " for rewriting\n";
- return true;
- }
- auto Result = tooling::applyAtomicChanges(File, (*BufferErr)->getBuffer(),
- Changes, Spec);
- if (!Result) {
- llvm::errs() << toString(Result.takeError());
- return true;
- }
- if (opts::Inplace) {
- std::error_code EC;
- llvm::raw_fd_ostream OS(File, EC, llvm::sys::fs::OF_Text);
- if (EC) {
- llvm::errs() << EC.message() << "\n";
- return true;
- }
- OS << *Result;
- continue;
- }
- llvm::outs() << *Result;
- }
- return false;
- }
- private:
- /// Logs an individual refactoring action invocation to STDOUT.
- void logInvocation(RefactoringActionSubcommand &Subcommand,
- const RefactoringRuleContext &Context) {
- llvm::outs() << "invoking action '" << Subcommand.getName() << "':\n";
- if (Context.getSelectionRange().isValid()) {
- SourceRange R = Context.getSelectionRange();
- llvm::outs() << " -selection=";
- R.getBegin().print(llvm::outs(), Context.getSources());
- llvm::outs() << " -> ";
- R.getEnd().print(llvm::outs(), Context.getSources());
- llvm::outs() << "\n";
- }
- }
- llvm::Expected<RefactoringActionRule *>
- getMatchingRule(RefactoringActionSubcommand &Subcommand) {
- SmallVector<RefactoringActionRule *, 4> MatchingRules;
- llvm::StringSet<> MissingOptions;
- for (const auto &Rule : Subcommand.getActionRules()) {
- CommandLineRefactoringOptionVisitor Visitor(Subcommand.getOptions());
- Rule->visitRefactoringOptions(Visitor);
- if (Visitor.getMissingRequiredOptions().empty()) {
- if (!Rule->hasSelectionRequirement()) {
- MatchingRules.push_back(Rule.get());
- } else {
- Subcommand.parseSelectionArgument();
- if (Subcommand.getSelection()) {
- MatchingRules.push_back(Rule.get());
- } else {
- MissingOptions.insert("selection");
- }
- }
- }
- for (const RefactoringOption *Opt : Visitor.getMissingRequiredOptions())
- MissingOptions.insert(Opt->getName());
- }
- if (MatchingRules.empty()) {
- std::string Error;
- llvm::raw_string_ostream OS(Error);
- OS << "ERROR: '" << Subcommand.getName()
- << "' can't be invoked with the given arguments:\n";
- for (const auto &Opt : MissingOptions)
- OS << " missing '-" << Opt.getKey() << "' option\n";
- OS.flush();
- return llvm::make_error<llvm::StringError>(
- Error, llvm::inconvertibleErrorCode());
- }
- if (MatchingRules.size() != 1) {
- return llvm::make_error<llvm::StringError>(
- llvm::Twine("ERROR: more than one matching rule of action") +
- Subcommand.getName() + "was found with given options.",
- llvm::inconvertibleErrorCode());
- }
- return MatchingRules.front();
- }
- // Figure out which action is specified by the user. The user must specify the
- // action using a command-line subcommand, e.g. the invocation `clang-refactor
- // local-rename` corresponds to the `LocalRename` refactoring action. All
- // subcommands must have a unique names. This allows us to figure out which
- // refactoring action should be invoked by looking at the first subcommand
- // that's enabled by LLVM's command-line parser.
- llvm::Expected<RefactoringActionSubcommand *> getSelectedSubcommand() {
- auto It = llvm::find_if(
- SubCommands,
- [](const std::unique_ptr<RefactoringActionSubcommand> &SubCommand) {
- return !!(*SubCommand);
- });
- if (It == SubCommands.end()) {
- std::string Error;
- llvm::raw_string_ostream OS(Error);
- OS << "error: no refactoring action given\n";
- OS << "note: the following actions are supported:\n";
- for (const auto &Subcommand : SubCommands)
- OS.indent(2) << Subcommand->getName() << "\n";
- OS.flush();
- return llvm::make_error<llvm::StringError>(
- Error, llvm::inconvertibleErrorCode());
- }
- RefactoringActionSubcommand *Subcommand = &(**It);
- return Subcommand;
- }
- std::vector<std::unique_ptr<RefactoringActionSubcommand>> SubCommands;
- RefactoringActionSubcommand *SelectedSubcommand;
- RefactoringActionRule *MatchingRule;
- std::unique_ptr<ClangRefactorToolConsumerInterface> Consumer;
- AtomicChanges Changes;
- bool HasFailed;
- };
- } // end anonymous namespace
- int main(int argc, const char **argv) {
- llvm::sys::PrintStackTraceOnErrorSignal(argv[0]);
- ClangRefactorTool RefactorTool;
- CommonOptionsParser Options(
- argc, argv, cl::GeneralCategory, cl::ZeroOrMore,
- "Clang-based refactoring tool for C, C++ and Objective-C");
- if (auto Err = RefactorTool.Init()) {
- llvm::errs() << llvm::toString(std::move(Err)) << "\n";
- return 1;
- }
- auto ActionFactory = RefactorTool.getFrontendActionFactory();
- if (!ActionFactory) {
- llvm::errs() << llvm::toString(ActionFactory.takeError()) << "\n";
- return 1;
- }
- ClangTool Tool(Options.getCompilations(), Options.getSourcePathList());
- bool Failed = false;
- if (Tool.run(ActionFactory->get()) != 0) {
- llvm::errs() << "Failed to run refactoring action on files\n";
- // It is possible that TUs are broken while changes are generated correctly,
- // so we still try applying changes.
- Failed = true;
- }
- return RefactorTool.applySourceChanges() || Failed ||
- RefactorTool.hasFailed();
- }
|