220 lines · cpp
1//===-- ClangMove.cpp - move definition to new file -------------*- C++ -*-===//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-exception6//7//===----------------------------------------------------------------------===//8 9#include "Move.h"10#include "clang/Frontend/TextDiagnosticPrinter.h"11#include "clang/Rewrite/Core/Rewriter.h"12#include "clang/Tooling/ArgumentsAdjusters.h"13#include "clang/Tooling/CommonOptionsParser.h"14#include "clang/Tooling/Refactoring.h"15#include "clang/Tooling/Tooling.h"16#include "llvm/ADT/StringRef.h"17#include "llvm/Support/CommandLine.h"18#include "llvm/Support/Path.h"19#include "llvm/Support/Process.h"20#include "llvm/Support/Signals.h"21#include "llvm/Support/YAMLTraits.h"22#include <set>23#include <string>24 25using namespace clang;26using namespace llvm;27 28namespace {29 30std::error_code CreateNewFile(const llvm::Twine &path) {31 int fd = 0;32 if (std::error_code ec = llvm::sys::fs::openFileForWrite(33 path, fd, llvm::sys::fs::CD_CreateAlways,34 llvm::sys::fs::OF_TextWithCRLF))35 return ec;36 37 return llvm::sys::Process::SafelyCloseFileDescriptor(fd);38}39 40cl::OptionCategory ClangMoveCategory("clang-move options");41 42cl::list<std::string> Names("names", cl::CommaSeparated,43 cl::desc("The list of the names of classes being "44 "moved, e.g. \"Foo,a::Foo,b::Foo\"."),45 cl::cat(ClangMoveCategory));46 47cl::opt<std::string>48 OldHeader("old_header",49 cl::desc("The relative/absolute file path of old header."),50 cl::cat(ClangMoveCategory));51 52cl::opt<std::string>53 OldCC("old_cc", cl::desc("The relative/absolute file path of old cc."),54 cl::cat(ClangMoveCategory));55 56cl::opt<std::string>57 NewHeader("new_header",58 cl::desc("The relative/absolute file path of new header."),59 cl::cat(ClangMoveCategory));60 61cl::opt<std::string>62 NewCC("new_cc", cl::desc("The relative/absolute file path of new cc."),63 cl::cat(ClangMoveCategory));64 65cl::opt<bool>66 OldDependOnNew("old_depend_on_new",67 cl::desc("Whether old header will depend on new header. If "68 "true, clang-move will "69 "add #include of new header to old header."),70 cl::init(false), cl::cat(ClangMoveCategory));71 72cl::opt<bool>73 NewDependOnOld("new_depend_on_old",74 cl::desc("Whether new header will depend on old header. If "75 "true, clang-move will "76 "add #include of old header to new header."),77 cl::init(false), cl::cat(ClangMoveCategory));78 79cl::opt<std::string>80 Style("style",81 cl::desc("The style name used for reformatting. Default is \"llvm\""),82 cl::init("llvm"), cl::cat(ClangMoveCategory));83 84cl::opt<bool> Dump("dump_result",85 cl::desc("Dump results in JSON format to stdout."),86 cl::cat(ClangMoveCategory));87 88cl::opt<bool> DumpDecls(89 "dump_decls",90 cl::desc("Dump all declarations in old header (JSON format) to stdout. If "91 "the option is specified, other command options will be ignored. "92 "An empty JSON will be returned if old header isn't specified."),93 cl::cat(ClangMoveCategory));94 95} // namespace96 97int main(int argc, const char **argv) {98 llvm::sys::PrintStackTraceOnErrorSignal(argv[0]);99 auto ExpectedParser =100 tooling::CommonOptionsParser::create(argc, argv, ClangMoveCategory);101 if (!ExpectedParser) {102 llvm::errs() << ExpectedParser.takeError();103 return 1;104 }105 tooling::CommonOptionsParser &OptionsParser = ExpectedParser.get();106 107 if (OldDependOnNew && NewDependOnOld) {108 llvm::errs() << "Provide either --old_depend_on_new or "109 "--new_depend_on_old. clang-move doesn't support these two "110 "options at same time (It will introduce include cycle).\n";111 return 1;112 }113 114 tooling::RefactoringTool Tool(OptionsParser.getCompilations(),115 OptionsParser.getSourcePathList());116 // Add "-fparse-all-comments" compile option to make clang parse all comments.117 Tool.appendArgumentsAdjuster(tooling::getInsertArgumentAdjuster(118 "-fparse-all-comments", tooling::ArgumentInsertPosition::BEGIN));119 move::MoveDefinitionSpec Spec;120 Spec.Names = {Names.begin(), Names.end()};121 Spec.OldHeader = OldHeader;122 Spec.NewHeader = NewHeader;123 Spec.OldCC = OldCC;124 Spec.NewCC = NewCC;125 Spec.OldDependOnNew = OldDependOnNew;126 Spec.NewDependOnOld = NewDependOnOld;127 128 llvm::SmallString<128> InitialDirectory;129 if (std::error_code EC = llvm::sys::fs::current_path(InitialDirectory))130 llvm::report_fatal_error("Cannot detect current path: " +131 Twine(EC.message()));132 133 move::ClangMoveContext Context{Spec, Tool.getReplacements(),134 std::string(InitialDirectory), Style,135 DumpDecls};136 move::DeclarationReporter Reporter;137 move::ClangMoveActionFactory Factory(&Context, &Reporter);138 139 int CodeStatus = Tool.run(&Factory);140 if (CodeStatus)141 return CodeStatus;142 143 if (DumpDecls) {144 llvm::outs() << "[\n";145 const auto &Declarations = Reporter.getDeclarationList();146 for (auto I = Declarations.begin(), E = Declarations.end(); I != E; ++I) {147 llvm::outs() << " {\n";148 llvm::outs() << " \"DeclarationName\": \"" << I->QualifiedName149 << "\",\n";150 llvm::outs() << " \"DeclarationType\": \"" << I->Kind << "\",\n";151 llvm::outs() << " \"Templated\": " << (I->Templated ? "true" : "false")152 << "\n";153 llvm::outs() << " }";154 // Don't print trailing "," at the end of last element.155 if (I != std::prev(E))156 llvm::outs() << ",\n";157 }158 llvm::outs() << "\n]\n";159 return 0;160 }161 162 if (!NewCC.empty()) {163 std::error_code EC = CreateNewFile(NewCC);164 if (EC) {165 llvm::errs() << "Failed to create " << NewCC << ": " << EC.message()166 << "\n";167 return EC.value();168 }169 }170 if (!NewHeader.empty()) {171 std::error_code EC = CreateNewFile(NewHeader);172 if (EC) {173 llvm::errs() << "Failed to create " << NewHeader << ": " << EC.message()174 << "\n";175 return EC.value();176 }177 }178 179 DiagnosticOptions DiagOpts;180 clang::TextDiagnosticPrinter DiagnosticPrinter(errs(), DiagOpts);181 DiagnosticsEngine Diagnostics(DiagnosticIDs::create(), DiagOpts,182 &DiagnosticPrinter, false);183 auto &FileMgr = Tool.getFiles();184 SourceManager SM(Diagnostics, FileMgr);185 Rewriter Rewrite(SM, LangOptions());186 187 if (!formatAndApplyAllReplacements(Tool.getReplacements(), Rewrite, Style)) {188 llvm::errs() << "Failed applying all replacements.\n";189 return 1;190 }191 192 if (Dump) {193 std::set<llvm::StringRef> Files;194 for (const auto &it : Tool.getReplacements())195 Files.insert(it.first);196 auto WriteToJson = [&](llvm::raw_ostream &OS) {197 OS << "[\n";198 for (auto I = Files.begin(), E = Files.end(); I != E; ++I) {199 OS << " {\n";200 OS << " \"FilePath\": \"" << *I << "\",\n";201 const auto Entry = FileMgr.getOptionalFileRef(*I);202 auto ID = SM.translateFile(*Entry);203 std::string Content;204 llvm::raw_string_ostream ContentStream(Content);205 Rewrite.getEditBuffer(ID).write(ContentStream);206 OS << " \"SourceText\": \""207 << llvm::yaml::escape(ContentStream.str()) << "\"\n";208 OS << " }";209 if (I != std::prev(E))210 OS << ",\n";211 }212 OS << "\n]\n";213 };214 WriteToJson(llvm::outs());215 return 0;216 }217 218 return Rewrite.overwriteChangedFiles();219}220