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
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 Name: path, ResultFD&: fd, Disp: llvm::sys::fs::CD_CreateAlways,
34 Flags: llvm::sys::fs::OF_TextWithCRLF))
35 return ec;
36
37 return llvm::sys::Process::SafelyCloseFileDescriptor(FD: 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(Val: 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(Val: 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(Val: "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} // namespace
96
97int main(int argc, const char **argv) {
98 llvm::sys::PrintStackTraceOnErrorSignal(Argv0: argv[0]);
99 auto ExpectedParser =
100 tooling::CommonOptionsParser::create(argc, argv, Category&: 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(Adjuster: tooling::getInsertArgumentAdjuster(
118 Extra: "-fparse-all-comments", Pos: 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(result&: InitialDirectory))
130 llvm::report_fatal_error(reason: "Cannot detect current path: " +
131 Twine(EC.message()));
132
133 move::ClangMoveContext Context{.Spec: Spec, .FileToReplacements: Tool.getReplacements(),
134 .OriginalRunningDirectory: std::string(InitialDirectory), .FallbackStyle: Style,
135 .DumpDeclarations: DumpDecls};
136 move::DeclarationReporter Reporter;
137 move::ClangMoveActionFactory Factory(&Context, &Reporter);
138
139 int CodeStatus = Tool.run(Action: &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(x: 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(path: 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(path: 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(FileToReplaces: 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(x: 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.getFile(Filename: *I);
203 auto ID = SM.translateFile(SourceFile: *Entry);
204 std::string Content;
205 llvm::raw_string_ostream ContentStream(Content);
206 Rewrite.getEditBuffer(FID: ID).write(Stream&: ContentStream);
207 OS << " \"SourceText\": \""
208 << llvm::yaml::escape(Input: ContentStream.str()) << "\"\n";
209 OS << " }";
210 if (I != std::prev(x: 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

source code of clang-tools-extra/clang-move/tool/ClangMove.cpp