xref: /llvm-project/clang-tools-extra/clang-change-namespace/tool/ClangChangeNamespace.cpp (revision 27254ae51192d83fd45777cc33b004d06c2ceb50)
1 //===-- ClangChangeNamespace.cpp - Standalone change namespace ------------===//
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 // This tool can be used to change the surrounding namespaces of class/function
9 // definitions.
10 //
11 // Example: test.cc
12 //    namespace na {
13 //    class X {};
14 //    namespace nb {
15 //    class Y { X x; };
16 //    } // namespace nb
17 //    } // namespace na
18 // To move the definition of class Y from namespace "na::nb" to "x::y", run:
19 //    clang-change-namespace --old_namespace "na::nb" \
20 //      --new_namespace "x::y" --file_pattern "test.cc" test.cc --
21 // Output:
22 //    namespace na {
23 //    class X {};
24 //    } // namespace na
25 //    namespace x {
26 //    namespace y {
27 //    class Y { na::X x; };
28 //    } // namespace y
29 //    } // namespace x
30 
31 #include "ChangeNamespace.h"
32 #include "clang/ASTMatchers/ASTMatchFinder.h"
33 #include "clang/Frontend/FrontendActions.h"
34 #include "clang/Frontend/TextDiagnosticPrinter.h"
35 #include "clang/Rewrite/Core/Rewriter.h"
36 #include "clang/Tooling/CommonOptionsParser.h"
37 #include "clang/Tooling/Refactoring.h"
38 #include "clang/Tooling/Tooling.h"
39 #include "llvm/Support/CommandLine.h"
40 #include "llvm/Support/Signals.h"
41 #include "llvm/Support/YAMLTraits.h"
42 
43 using namespace clang;
44 using namespace llvm;
45 
46 namespace {
47 
48 cl::OptionCategory ChangeNamespaceCategory("Change namespace.");
49 
50 cl::opt<std::string> OldNamespace("old_namespace", cl::Required,
51                                   cl::desc("Old namespace."),
52                                   cl::cat(ChangeNamespaceCategory));
53 
54 cl::opt<std::string> NewNamespace("new_namespace", cl::Required,
55                                   cl::desc("New namespace."),
56                                   cl::cat(ChangeNamespaceCategory));
57 
58 cl::opt<std::string> FilePattern(
59     "file_pattern", cl::Required,
60     cl::desc("Only rename namespaces in files that match the given pattern."),
61     cl::cat(ChangeNamespaceCategory));
62 
63 cl::opt<bool> Inplace("i", cl::desc("Inplace edit <file>s, if specified."),
64                       cl::cat(ChangeNamespaceCategory));
65 
66 cl::opt<bool>
67     DumpYAML("dump_result",
68          cl::desc("Dump new file contents in YAML, if specified."),
69          cl::cat(ChangeNamespaceCategory));
70 
71 cl::opt<std::string> Style("style",
72                            cl::desc("The style name used for reformatting."),
73                            cl::init("LLVM"), cl::cat(ChangeNamespaceCategory));
74 
75 cl::opt<std::string> AllowedFile(
76     "allowed_file",
77     cl::desc("A file containing regexes of symbol names that are not expected "
78              "to be updated when changing namespaces around them."),
79     cl::init(""), cl::cat(ChangeNamespaceCategory));
80 
GetAllowedSymbolPatterns()81 llvm::ErrorOr<std::vector<std::string>> GetAllowedSymbolPatterns() {
82   std::vector<std::string> Patterns;
83   if (AllowedFile.empty())
84     return Patterns;
85 
86   llvm::SmallVector<StringRef, 8> Lines;
87   llvm::ErrorOr<std::unique_ptr<llvm::MemoryBuffer>> File =
88       llvm::MemoryBuffer::getFile(AllowedFile);
89   if (!File)
90     return File.getError();
91   llvm::StringRef Content = File.get()->getBuffer();
92   Content.split(Lines, '\n', /*MaxSplit=*/-1, /*KeepEmpty=*/false);
93   for (auto Line : Lines)
94     Patterns.push_back(std::string(Line.trim()));
95   return Patterns;
96 }
97 
98 } // anonymous namespace
99 
main(int argc,const char ** argv)100 int main(int argc, const char **argv) {
101   llvm::sys::PrintStackTraceOnErrorSignal(argv[0]);
102   auto ExpectedParser =
103       tooling::CommonOptionsParser::create(argc, argv, ChangeNamespaceCategory);
104   if (!ExpectedParser) {
105     llvm::errs() << ExpectedParser.takeError();
106     return 1;
107   }
108   tooling::CommonOptionsParser &OptionsParser = ExpectedParser.get();
109   const auto &Files = OptionsParser.getSourcePathList();
110   tooling::RefactoringTool Tool(OptionsParser.getCompilations(), Files);
111   llvm::ErrorOr<std::vector<std::string>> AllowedPatterns =
112       GetAllowedSymbolPatterns();
113   if (!AllowedPatterns) {
114     llvm::errs() << "Failed to open allow file " << AllowedFile << ". "
115                  << AllowedPatterns.getError().message() << "\n";
116     return 1;
117   }
118   change_namespace::ChangeNamespaceTool NamespaceTool(
119       OldNamespace, NewNamespace, FilePattern, *AllowedPatterns,
120       &Tool.getReplacements(), Style);
121   ast_matchers::MatchFinder Finder;
122   NamespaceTool.registerMatchers(&Finder);
123   std::unique_ptr<tooling::FrontendActionFactory> Factory =
124       tooling::newFrontendActionFactory(&Finder);
125 
126   if (int Result = Tool.run(Factory.get()))
127     return Result;
128   LangOptions DefaultLangOptions;
129   IntrusiveRefCntPtr<DiagnosticOptions> DiagOpts = new DiagnosticOptions();
130   clang::TextDiagnosticPrinter DiagnosticPrinter(errs(), &*DiagOpts);
131   DiagnosticsEngine Diagnostics(
132       IntrusiveRefCntPtr<DiagnosticIDs>(new DiagnosticIDs()), &*DiagOpts,
133       &DiagnosticPrinter, false);
134   auto &FileMgr = Tool.getFiles();
135   SourceManager Sources(Diagnostics, FileMgr);
136   Rewriter Rewrite(Sources, DefaultLangOptions);
137 
138   if (!formatAndApplyAllReplacements(Tool.getReplacements(), Rewrite, Style)) {
139     llvm::errs() << "Failed applying all replacements.\n";
140     return 1;
141   }
142   if (Inplace)
143     return Rewrite.overwriteChangedFiles();
144 
145   std::set<llvm::StringRef> ChangedFiles;
146   for (const auto &it : Tool.getReplacements())
147     ChangedFiles.insert(it.first);
148 
149   if (DumpYAML) {
150     auto WriteToYAML = [&](llvm::raw_ostream &OS) {
151       OS << "[\n";
152       for (auto I = ChangedFiles.begin(), E = ChangedFiles.end(); I != E; ++I) {
153         OS << "  {\n";
154         OS << "    \"FilePath\": \"" << *I << "\",\n";
155         auto Entry = llvm::cantFail(FileMgr.getFileRef(*I));
156         auto ID = Sources.getOrCreateFileID(Entry, SrcMgr::C_User);
157         std::string Content;
158         llvm::raw_string_ostream ContentStream(Content);
159         Rewrite.getEditBuffer(ID).write(ContentStream);
160         OS << "    \"SourceText\": \""
161            << llvm::yaml::escape(ContentStream.str()) << "\"\n";
162         OS << "  }";
163         if (I != std::prev(E))
164           OS << ",\n";
165       }
166       OS << "\n]\n";
167     };
168     WriteToYAML(llvm::outs());
169     return 0;
170   }
171 
172   for (const auto &File : ChangedFiles) {
173     auto Entry = llvm::cantFail(FileMgr.getFileRef(File));
174 
175     auto ID = Sources.getOrCreateFileID(Entry, SrcMgr::C_User);
176     outs() << "============== " << File << " ==============\n";
177     Rewrite.getEditBuffer(ID).write(llvm::outs());
178     outs() << "\n============================================\n";
179   }
180 
181   return 0;
182 }
183