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-exception 6 // 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 25 using namespace clang; 26 using namespace llvm; 27 28 namespace { 29 30 std::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 40 cl::OptionCategory ClangMoveCategory("clang-move options"); 41 42 cl::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 47 cl::opt<std::string> 48 OldHeader("old_header", 49 cl::desc("The relative/absolute file path of old header."), 50 cl::cat(ClangMoveCategory)); 51 52 cl::opt<std::string> 53 OldCC("old_cc", cl::desc("The relative/absolute file path of old cc."), 54 cl::cat(ClangMoveCategory)); 55 56 cl::opt<std::string> 57 NewHeader("new_header", 58 cl::desc("The relative/absolute file path of new header."), 59 cl::cat(ClangMoveCategory)); 60 61 cl::opt<std::string> 62 NewCC("new_cc", cl::desc("The relative/absolute file path of new cc."), 63 cl::cat(ClangMoveCategory)); 64 65 cl::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 72 cl::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 79 cl::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 84 cl::opt<bool> Dump("dump_result", 85 cl::desc("Dump results in JSON format to stdout."), 86 cl::cat(ClangMoveCategory)); 87 88 cl::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 } // namespace 96 97 int 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->QualifiedName 149 << "\",\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 IntrusiveRefCntPtr<DiagnosticOptions> DiagOpts(new DiagnosticOptions()); 180 clang::TextDiagnosticPrinter DiagnosticPrinter(errs(), &*DiagOpts); 181 DiagnosticsEngine Diagnostics( 182 IntrusiveRefCntPtr<DiagnosticIDs>(new DiagnosticIDs()), &*DiagOpts, 183 &DiagnosticPrinter, false); 184 auto &FileMgr = Tool.getFiles(); 185 SourceManager SM(Diagnostics, FileMgr); 186 Rewriter Rewrite(SM, LangOptions()); 187 188 if (!formatAndApplyAllReplacements(Tool.getReplacements(), Rewrite, Style)) { 189 llvm::errs() << "Failed applying all replacements.\n"; 190 return 1; 191 } 192 193 if (Dump) { 194 std::set<llvm::StringRef> Files; 195 for (const auto &it : Tool.getReplacements()) 196 Files.insert(it.first); 197 auto WriteToJson = [&](llvm::raw_ostream &OS) { 198 OS << "[\n"; 199 for (auto I = Files.begin(), E = Files.end(); I != E; ++I) { 200 OS << " {\n"; 201 OS << " \"FilePath\": \"" << *I << "\",\n"; 202 const auto Entry = FileMgr.getOptionalFileRef(*I); 203 auto ID = SM.translateFile(*Entry); 204 std::string Content; 205 llvm::raw_string_ostream ContentStream(Content); 206 Rewrite.getEditBuffer(ID).write(ContentStream); 207 OS << " \"SourceText\": \"" 208 << llvm::yaml::escape(ContentStream.str()) << "\"\n"; 209 OS << " }"; 210 if (I != std::prev(E)) 211 OS << ",\n"; 212 } 213 OS << "\n]\n"; 214 }; 215 WriteToJson(llvm::outs()); 216 return 0; 217 } 218 219 return Rewrite.overwriteChangedFiles(); 220 } 221