xref: /llvm-project/clang-tools-extra/clang-move/tool/ClangMove.cpp (revision b1aea98cfa357e23f4bb52232da5f41781f23bff)
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