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 | 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 | |
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 | ("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 | ("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(Val: 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(Val: 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(Val: "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(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 | |