1 //===-- ClangMove.cpp - move definition to new file -------------*- C++ -*-===//
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
7 //===----------------------------------------------------------------------===//
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"
25 using namespace clang
;
30 std::error_code
CreateNewFile(const llvm::Twine
&path
) {
32 if (std::error_code ec
= llvm::sys::fs::openFileForWrite(
33 path
, fd
, llvm::sys::fs::CD_CreateAlways
,
34 llvm::sys::fs::OF_TextWithCRLF
))
37 return llvm::sys::Process::SafelyCloseFileDescriptor(fd
);
40 cl::OptionCategory
ClangMoveCategory("clang-move options");
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
));
48 OldHeader("old_header",
49 cl::desc("The relative/absolute file path of old header."),
50 cl::cat(ClangMoveCategory
));
53 OldCC("old_cc", cl::desc("The relative/absolute file path of old cc."),
54 cl::cat(ClangMoveCategory
));
57 NewHeader("new_header",
58 cl::desc("The relative/absolute file path of new header."),
59 cl::cat(ClangMoveCategory
));
62 NewCC("new_cc", cl::desc("The relative/absolute file path of new cc."),
63 cl::cat(ClangMoveCategory
));
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
));
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
));
81 cl::desc("The style name used for reformatting. Default is \"llvm\""),
82 cl::init("llvm"), cl::cat(ClangMoveCategory
));
84 cl::opt
<bool> Dump("dump_result",
85 cl::desc("Dump results in JSON format to stdout."),
86 cl::cat(ClangMoveCategory
));
88 cl::opt
<bool> DumpDecls(
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
));
97 int main(int argc
, const char **argv
) {
98 llvm::sys::PrintStackTraceOnErrorSignal(argv
[0]);
100 tooling::CommonOptionsParser::create(argc
, argv
, ClangMoveCategory
);
101 if (!ExpectedParser
) {
102 llvm::errs() << ExpectedParser
.takeError();
105 tooling::CommonOptionsParser
&OptionsParser
= ExpectedParser
.get();
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";
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
;
125 Spec
.OldDependOnNew
= OldDependOnNew
;
126 Spec
.NewDependOnOld
= NewDependOnOld
;
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()));
133 move::ClangMoveContext Context
{Spec
, Tool
.getReplacements(),
134 std::string(InitialDirectory
), Style
,
136 move::DeclarationReporter Reporter
;
137 move::ClangMoveActionFactory
Factory(&Context
, &Reporter
);
139 int CodeStatus
= Tool
.run(&Factory
);
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
150 llvm::outs() << " \"DeclarationType\": \"" << I
->Kind
<< "\",\n";
151 llvm::outs() << " \"Templated\": " << (I
->Templated
? "true" : "false")
153 llvm::outs() << " }";
154 // Don't print trailing "," at the end of last element.
155 if (I
!= std::prev(E
))
156 llvm::outs() << ",\n";
158 llvm::outs() << "\n]\n";
162 if (!NewCC
.empty()) {
163 std::error_code EC
= CreateNewFile(NewCC
);
165 llvm::errs() << "Failed to create " << NewCC
<< ": " << EC
.message()
170 if (!NewHeader
.empty()) {
171 std::error_code EC
= CreateNewFile(NewHeader
);
173 llvm::errs() << "Failed to create " << NewHeader
<< ": " << EC
.message()
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());
188 if (!formatAndApplyAllReplacements(Tool
.getReplacements(), Rewrite
, Style
)) {
189 llvm::errs() << "Failed applying all replacements.\n";
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
) {
199 for (auto I
= Files
.begin(), E
= Files
.end(); I
!= E
; ++I
) {
201 OS
<< " \"FilePath\": \"" << *I
<< "\",\n";
202 const auto Entry
= FileMgr
.getOptionalFileRef(*I
);
203 auto ID
= SM
.translateFile(*Entry
);
205 llvm::raw_string_ostream
ContentStream(Content
);
206 Rewrite
.getEditBuffer(ID
).write(ContentStream
);
207 OS
<< " \"SourceText\": \""
208 << llvm::yaml::escape(ContentStream
.str()) << "\"\n";
210 if (I
!= std::prev(E
))
215 WriteToJson(llvm::outs());
219 return Rewrite
.overwriteChangedFiles();