text: //===-- ClangMove.cpp - move defintion 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 <set>
  #include <string>
  
  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<std::string> 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<std::string>
      OldHeader("old_header",
                cl::desc("The relative/absolute file path of old header."),
                cl::cat(ClangMoveCategory));
  
  cl::opt<std::string>
      OldCC("old_cc", cl::desc("The relative/absolute file path of old cc."),
            cl::cat(ClangMoveCategory));
  
  cl::opt<std::string>
      NewHeader("new_header",
                cl::desc("The relative/absolute file path of new header."),
                cl::cat(ClangMoveCategory));
  
  cl::opt<std::string>
      NewCC("new_cc", cl::desc("The relative/absolute file path of new cc."),
            cl::cat(ClangMoveCategory));
  
  cl::opt<bool>
      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<bool>
      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<std::string>
      Style("style",
            cl::desc("The style name used for reformatting. Default is \"llvm\""),
            cl::init("llvm"), cl::cat(ClangMoveCategory));
  
  cl::opt<bool> Dump("dump_result",
                     cl::desc("Dump results in JSON format to stdout."),
                     cl::cat(ClangMoveCategory));
  
  cl::opt<bool> 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(),
                                   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<DiagnosticOptions> DiagOpts(new DiagnosticOptions());
    clang::TextDiagnosticPrinter DiagnosticPrinter(errs(), &*DiagOpts);
    DiagnosticsEngine Diagnostics(
        IntrusiveRefCntPtr<DiagnosticIDs>(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<llvm::StringRef> 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();
  }
  