//===-- ClangMove.cpp - move definition to new file -------------*- C++ -*-===// // // 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 // //===----------------------------------------------------------------------===// #include "Move.h" #include "clang/Frontend/TextDiagnosticPrinter.h" #include "clang/Rewrite/Core/Rewriter.h" #include "clang/Tooling/ArgumentsAdjusters.h" #include "clang/Tooling/CommonOptionsParser.h" #include "clang/Tooling/Refactoring.h" #include "clang/Tooling/Tooling.h" #include "llvm/ADT/StringRef.h" #include "llvm/Support/CommandLine.h" #include "llvm/Support/Path.h" #include "llvm/Support/Process.h" #include "llvm/Support/Signals.h" #include "llvm/Support/YAMLTraits.h" #include #include using namespace clang; using namespace llvm; namespace { std::error_code CreateNewFile(const llvm::Twine &path) { int fd = 0; if (std::error_code ec = llvm::sys::fs::openFileForWrite( path, fd, llvm::sys::fs::CD_CreateAlways, llvm::sys::fs::OF_Text)) return ec; return llvm::sys::Process::SafelyCloseFileDescriptor(fd); } cl::OptionCategory ClangMoveCategory("clang-move options"); cl::list Names("names", cl::CommaSeparated, cl::desc("The list of the names of classes being " "moved, e.g. \"Foo,a::Foo,b::Foo\"."), cl::cat(ClangMoveCategory)); cl::opt OldHeader("old_header", cl::desc("The relative/absolute file path of old header."), cl::cat(ClangMoveCategory)); cl::opt OldCC("old_cc", cl::desc("The relative/absolute file path of old cc."), cl::cat(ClangMoveCategory)); cl::opt NewHeader("new_header", cl::desc("The relative/absolute file path of new header."), cl::cat(ClangMoveCategory)); cl::opt NewCC("new_cc", cl::desc("The relative/absolute file path of new cc."), cl::cat(ClangMoveCategory)); cl::opt OldDependOnNew("old_depend_on_new", cl::desc("Whether old header will depend on new header. If " "true, clang-move will " "add #include of new header to old header."), cl::init(false), cl::cat(ClangMoveCategory)); cl::opt NewDependOnOld("new_depend_on_old", cl::desc("Whether new header will depend on old header. If " "true, clang-move will " "add #include of old header to new header."), cl::init(false), cl::cat(ClangMoveCategory)); cl::opt Style("style", cl::desc("The style name used for reformatting. Default is \"llvm\""), cl::init("llvm"), cl::cat(ClangMoveCategory)); cl::opt Dump("dump_result", cl::desc("Dump results in JSON format to stdout."), cl::cat(ClangMoveCategory)); cl::opt DumpDecls( "dump_decls", cl::desc("Dump all declarations in old header (JSON format) to stdout. If " "the option is specified, other command options will be ignored. " "An empty JSON will be returned if old header isn't specified."), cl::cat(ClangMoveCategory)); } // namespace int main(int argc, const char **argv) { llvm::sys::PrintStackTraceOnErrorSignal(argv[0]); tooling::CommonOptionsParser OptionsParser(argc, argv, ClangMoveCategory); if (OldDependOnNew && NewDependOnOld) { llvm::errs() << "Provide either --old_depend_on_new or " "--new_depend_on_old. clang-move doesn't support these two " "options at same time (It will introduce include cycle).\n"; return 1; } tooling::RefactoringTool Tool(OptionsParser.getCompilations(), OptionsParser.getSourcePathList()); // Add "-fparse-all-comments" compile option to make clang parse all comments. Tool.appendArgumentsAdjuster(tooling::getInsertArgumentAdjuster( "-fparse-all-comments", tooling::ArgumentInsertPosition::BEGIN)); move::MoveDefinitionSpec Spec; Spec.Names = {Names.begin(), Names.end()}; Spec.OldHeader = OldHeader; Spec.NewHeader = NewHeader; Spec.OldCC = OldCC; Spec.NewCC = NewCC; Spec.OldDependOnNew = OldDependOnNew; Spec.NewDependOnOld = NewDependOnOld; llvm::SmallString<128> InitialDirectory; if (std::error_code EC = llvm::sys::fs::current_path(InitialDirectory)) llvm::report_fatal_error("Cannot detect current path: " + Twine(EC.message())); move::ClangMoveContext Context{Spec, Tool.getReplacements(), std::string(InitialDirectory.str()), Style, DumpDecls}; move::DeclarationReporter Reporter; move::ClangMoveActionFactory Factory(&Context, &Reporter); int CodeStatus = Tool.run(&Factory); if (CodeStatus) return CodeStatus; if (DumpDecls) { llvm::outs() << "[\n"; const auto &Declarations = Reporter.getDeclarationList(); for (auto I = Declarations.begin(), E = Declarations.end(); I != E; ++I) { llvm::outs() << " {\n"; llvm::outs() << " \"DeclarationName\": \"" << I->QualifiedName << "\",\n"; llvm::outs() << " \"DeclarationType\": \"" << I->Kind << "\",\n"; llvm::outs() << " \"Templated\": " << (I->Templated ? "true" : "false") << "\n"; llvm::outs() << " }"; // Don't print trailing "," at the end of last element. if (I != std::prev(E)) llvm::outs() << ",\n"; } llvm::outs() << "\n]\n"; return 0; } if (!NewCC.empty()) { std::error_code EC = CreateNewFile(NewCC); if (EC) { llvm::errs() << "Failed to create " << NewCC << ": " << EC.message() << "\n"; return EC.value(); } } if (!NewHeader.empty()) { std::error_code EC = CreateNewFile(NewHeader); if (EC) { llvm::errs() << "Failed to create " << NewHeader << ": " << EC.message() << "\n"; return EC.value(); } } IntrusiveRefCntPtr DiagOpts(new DiagnosticOptions()); clang::TextDiagnosticPrinter DiagnosticPrinter(errs(), &*DiagOpts); DiagnosticsEngine Diagnostics( IntrusiveRefCntPtr(new DiagnosticIDs()), &*DiagOpts, &DiagnosticPrinter, false); auto &FileMgr = Tool.getFiles(); SourceManager SM(Diagnostics, FileMgr); Rewriter Rewrite(SM, LangOptions()); if (!formatAndApplyAllReplacements(Tool.getReplacements(), Rewrite, Style)) { llvm::errs() << "Failed applying all replacements.\n"; return 1; } if (Dump) { std::set Files; for (const auto &it : Tool.getReplacements()) Files.insert(it.first); auto WriteToJson = [&](llvm::raw_ostream &OS) { OS << "[\n"; for (auto I = Files.begin(), E = Files.end(); I != E; ++I) { OS << " {\n"; OS << " \"FilePath\": \"" << *I << "\",\n"; const auto Entry = FileMgr.getFile(*I); auto ID = SM.translateFile(*Entry); std::string Content; llvm::raw_string_ostream ContentStream(Content); Rewrite.getEditBuffer(ID).write(ContentStream); OS << " \"SourceText\": \"" << llvm::yaml::escape(ContentStream.str()) << "\"\n"; OS << " }"; if (I != std::prev(E)) OS << ",\n"; } OS << "\n]\n"; }; WriteToJson(llvm::outs()); return 0; } return Rewrite.overwriteChangedFiles(); }