blob: 6943afd51dc23461c9bec0b5ffec67dc5fd77cbf [file] [log] [blame]
Eric Liu495b2112016-09-19 17:40:32 +00001//===-- ChangeNamespace.cpp - Change namespace implementation -------------===//
2//
3// The LLVM Compiler Infrastructure
4//
5// This file is distributed under the University of Illinois Open Source
6// License. See LICENSE.TXT for details.
7//
8//===----------------------------------------------------------------------===//
9#include "ChangeNamespace.h"
10#include "clang/Format/Format.h"
11#include "clang/Lex/Lexer.h"
12
13using namespace clang::ast_matchers;
14
15namespace clang {
16namespace change_namespace {
17
18namespace {
19
20inline std::string
21joinNamespaces(const llvm::SmallVectorImpl<StringRef> &Namespaces) {
22 if (Namespaces.empty())
23 return "";
24 std::string Result = Namespaces.front();
25 for (auto I = Namespaces.begin() + 1, E = Namespaces.end(); I != E; ++I)
26 Result += ("::" + *I).str();
27 return Result;
28}
29
30SourceLocation startLocationForType(TypeLoc TLoc) {
31 // For elaborated types (e.g. `struct a::A`) we want the portion after the
32 // `struct` but including the namespace qualifier, `a::`.
33 if (TLoc.getTypeLocClass() == TypeLoc::Elaborated) {
34 NestedNameSpecifierLoc NestedNameSpecifier =
35 TLoc.castAs<ElaboratedTypeLoc>().getQualifierLoc();
36 if (NestedNameSpecifier.getNestedNameSpecifier())
37 return NestedNameSpecifier.getBeginLoc();
38 TLoc = TLoc.getNextTypeLoc();
39 }
40 return TLoc.getLocStart();
41}
42
43SourceLocation EndLocationForType(TypeLoc TLoc) {
44 // Dig past any namespace or keyword qualifications.
45 while (TLoc.getTypeLocClass() == TypeLoc::Elaborated ||
46 TLoc.getTypeLocClass() == TypeLoc::Qualified)
47 TLoc = TLoc.getNextTypeLoc();
48
49 // The location for template specializations (e.g. Foo<int>) includes the
50 // templated types in its location range. We want to restrict this to just
51 // before the `<` character.
52 if (TLoc.getTypeLocClass() == TypeLoc::TemplateSpecialization)
53 return TLoc.castAs<TemplateSpecializationTypeLoc>()
54 .getLAngleLoc()
55 .getLocWithOffset(-1);
56 return TLoc.getEndLoc();
57}
58
59// Returns the containing namespace of `InnerNs` by skipping `PartialNsName`.
60// If the `InnerNs` does not have `PartialNsName` as suffix, nullptr is
61// returned.
62// For example, if `InnerNs` is "a::b::c" and `PartialNsName` is "b::c", then
63// the NamespaceDecl of namespace "a" will be returned.
64const NamespaceDecl *getOuterNamespace(const NamespaceDecl *InnerNs,
65 llvm::StringRef PartialNsName) {
66 const auto *CurrentContext = llvm::cast<DeclContext>(InnerNs);
67 const auto *CurrentNs = InnerNs;
68 llvm::SmallVector<llvm::StringRef, 4> PartialNsNameSplitted;
69 PartialNsName.split(PartialNsNameSplitted, "::");
70 while (!PartialNsNameSplitted.empty()) {
71 // Get the inner-most namespace in CurrentContext.
72 while (CurrentContext && !llvm::isa<NamespaceDecl>(CurrentContext))
73 CurrentContext = CurrentContext->getParent();
74 if (!CurrentContext)
75 return nullptr;
76 CurrentNs = llvm::cast<NamespaceDecl>(CurrentContext);
77 if (PartialNsNameSplitted.back() != CurrentNs->getNameAsString())
78 return nullptr;
79 PartialNsNameSplitted.pop_back();
80 CurrentContext = CurrentContext->getParent();
81 }
82 return CurrentNs;
83}
84
85// FIXME: get rid of this helper function if this is supported in clang-refactor
86// library.
87SourceLocation getStartOfNextLine(SourceLocation Loc, const SourceManager &SM,
88 const LangOptions &LangOpts) {
89 if (Loc.isMacroID() &&
90 !Lexer::isAtEndOfMacroExpansion(Loc, SM, LangOpts, &Loc))
91 return SourceLocation();
92 // Break down the source location.
93 std::pair<FileID, unsigned> LocInfo = SM.getDecomposedLoc(Loc);
94 // Try to load the file buffer.
95 bool InvalidTemp = false;
96 llvm::StringRef File = SM.getBufferData(LocInfo.first, &InvalidTemp);
97 if (InvalidTemp)
98 return SourceLocation();
99
100 const char *TokBegin = File.data() + LocInfo.second;
101 // Lex from the start of the given location.
102 Lexer Lex(SM.getLocForStartOfFile(LocInfo.first), LangOpts, File.begin(),
103 TokBegin, File.end());
104
105 llvm::SmallVector<char, 16> Line;
106 // FIXME: this is a bit hacky to get ReadToEndOfLine work.
107 Lex.setParsingPreprocessorDirective(true);
108 Lex.ReadToEndOfLine(&Line);
Haojian Wuef8a6dc2016-10-04 10:35:53 +0000109 auto End = Loc.getLocWithOffset(Line.size());
110 return SM.getLocForEndOfFile(LocInfo.first) == End ? End
111 : End.getLocWithOffset(1);
Eric Liu495b2112016-09-19 17:40:32 +0000112}
113
114// Returns `R` with new range that refers to code after `Replaces` being
115// applied.
116tooling::Replacement
117getReplacementInChangedCode(const tooling::Replacements &Replaces,
118 const tooling::Replacement &R) {
119 unsigned NewStart = Replaces.getShiftedCodePosition(R.getOffset());
120 unsigned NewEnd =
121 Replaces.getShiftedCodePosition(R.getOffset() + R.getLength());
122 return tooling::Replacement(R.getFilePath(), NewStart, NewEnd - NewStart,
123 R.getReplacementText());
124}
125
126// Adds a replacement `R` into `Replaces` or merges it into `Replaces` by
127// applying all existing Replaces first if there is conflict.
128void addOrMergeReplacement(const tooling::Replacement &R,
129 tooling::Replacements *Replaces) {
130 auto Err = Replaces->add(R);
131 if (Err) {
132 llvm::consumeError(std::move(Err));
133 auto Replace = getReplacementInChangedCode(*Replaces, R);
134 *Replaces = Replaces->merge(tooling::Replacements(Replace));
135 }
136}
137
138tooling::Replacement createReplacement(SourceLocation Start, SourceLocation End,
139 llvm::StringRef ReplacementText,
140 const SourceManager &SM) {
141 if (!Start.isValid() || !End.isValid()) {
142 llvm::errs() << "start or end location were invalid\n";
143 return tooling::Replacement();
144 }
145 if (SM.getDecomposedLoc(Start).first != SM.getDecomposedLoc(End).first) {
146 llvm::errs()
147 << "start or end location were in different macro expansions\n";
148 return tooling::Replacement();
149 }
150 Start = SM.getSpellingLoc(Start);
151 End = SM.getSpellingLoc(End);
152 if (SM.getFileID(Start) != SM.getFileID(End)) {
153 llvm::errs() << "start or end location were in different files\n";
154 return tooling::Replacement();
155 }
156 return tooling::Replacement(
157 SM, CharSourceRange::getTokenRange(SM.getSpellingLoc(Start),
158 SM.getSpellingLoc(End)),
159 ReplacementText);
160}
161
162tooling::Replacement createInsertion(SourceLocation Loc,
163 llvm::StringRef InsertText,
164 const SourceManager &SM) {
165 if (Loc.isInvalid()) {
166 llvm::errs() << "insert Location is invalid.\n";
167 return tooling::Replacement();
168 }
169 Loc = SM.getSpellingLoc(Loc);
170 return tooling::Replacement(SM, Loc, 0, InsertText);
171}
172
173// Returns the shortest qualified name for declaration `DeclName` in the
174// namespace `NsName`. For example, if `DeclName` is "a::b::X" and `NsName`
175// is "a::c::d", then "b::X" will be returned.
Eric Liu447164d2016-10-05 15:52:39 +0000176// \param DeclName A fully qualified name, "::a::b::X" or "a::b::X".
177// \param NsName A fully qualified name, "::a::b" or "a::b". Global namespace
178// will have empty name.
Eric Liu495b2112016-09-19 17:40:32 +0000179std::string getShortestQualifiedNameInNamespace(llvm::StringRef DeclName,
180 llvm::StringRef NsName) {
Eric Liu447164d2016-10-05 15:52:39 +0000181 DeclName = DeclName.ltrim(':');
182 NsName = NsName.ltrim(':');
183 // If `DeclName` is a global variable, we prepend "::" to it if it is not in
184 // the global namespace.
185 if (DeclName.find(':') == llvm::StringRef::npos)
186 return NsName.empty() ? DeclName.str() : ("::" + DeclName).str();
187
188 while (!DeclName.consume_front((NsName + "::").str())) {
Eric Liu495b2112016-09-19 17:40:32 +0000189 const auto Pos = NsName.find_last_of(':');
190 if (Pos == llvm::StringRef::npos)
191 return DeclName;
Eric Liu447164d2016-10-05 15:52:39 +0000192 assert(Pos > 0);
193 NsName = NsName.substr(0, Pos - 1);
Eric Liu495b2112016-09-19 17:40:32 +0000194 }
195 return DeclName;
196}
197
198std::string wrapCodeInNamespace(StringRef NestedNs, std::string Code) {
199 if (Code.back() != '\n')
200 Code += "\n";
201 llvm::SmallVector<StringRef, 4> NsSplitted;
202 NestedNs.split(NsSplitted, "::");
203 while (!NsSplitted.empty()) {
204 // FIXME: consider code style for comments.
205 Code = ("namespace " + NsSplitted.back() + " {\n" + Code +
206 "} // namespace " + NsSplitted.back() + "\n")
207 .str();
208 NsSplitted.pop_back();
209 }
210 return Code;
211}
212
213} // anonymous namespace
214
215ChangeNamespaceTool::ChangeNamespaceTool(
216 llvm::StringRef OldNs, llvm::StringRef NewNs, llvm::StringRef FilePattern,
217 std::map<std::string, tooling::Replacements> *FileToReplacements,
218 llvm::StringRef FallbackStyle)
219 : FallbackStyle(FallbackStyle), FileToReplacements(*FileToReplacements),
220 OldNamespace(OldNs.ltrim(':')), NewNamespace(NewNs.ltrim(':')),
221 FilePattern(FilePattern) {
222 FileToReplacements->clear();
223 llvm::SmallVector<llvm::StringRef, 4> OldNsSplitted;
224 llvm::SmallVector<llvm::StringRef, 4> NewNsSplitted;
225 llvm::StringRef(OldNamespace).split(OldNsSplitted, "::");
226 llvm::StringRef(NewNamespace).split(NewNsSplitted, "::");
227 // Calculates `DiffOldNamespace` and `DiffNewNamespace`.
228 while (!OldNsSplitted.empty() && !NewNsSplitted.empty() &&
229 OldNsSplitted.front() == NewNsSplitted.front()) {
230 OldNsSplitted.erase(OldNsSplitted.begin());
231 NewNsSplitted.erase(NewNsSplitted.begin());
232 }
233 DiffOldNamespace = joinNamespaces(OldNsSplitted);
234 DiffNewNamespace = joinNamespaces(NewNsSplitted);
235}
236
Eric Liu495b2112016-09-19 17:40:32 +0000237void ChangeNamespaceTool::registerMatchers(ast_matchers::MatchFinder *Finder) {
238 // Match old namespace blocks.
239 std::string FullOldNs = "::" + OldNamespace;
240 Finder->addMatcher(
241 namespaceDecl(hasName(FullOldNs), isExpansionInFileMatching(FilePattern))
242 .bind("old_ns"),
243 this);
244
245 auto IsInMovedNs =
246 allOf(hasAncestor(namespaceDecl(hasName(FullOldNs)).bind("ns_decl")),
247 isExpansionInFileMatching(FilePattern));
248
249 // Match forward-declarations in the old namespace.
250 Finder->addMatcher(
251 cxxRecordDecl(unless(anyOf(isImplicit(), isDefinition())), IsInMovedNs)
252 .bind("fwd_decl"),
253 this);
254
255 // Match references to types that are not defined in the old namespace.
256 // Forward-declarations in the old namespace are also matched since they will
257 // be moved back to the old namespace.
258 auto DeclMatcher = namedDecl(
259 hasAncestor(namespaceDecl()),
260 unless(anyOf(
Eric Liu912d0392016-09-27 12:54:48 +0000261 isImplicit(), hasAncestor(namespaceDecl(isAnonymous())),
Eric Liu495b2112016-09-19 17:40:32 +0000262 hasAncestor(cxxRecordDecl()),
263 allOf(IsInMovedNs, unless(cxxRecordDecl(unless(isDefinition())))))));
Eric Liu912d0392016-09-27 12:54:48 +0000264
Eric Liu495b2112016-09-19 17:40:32 +0000265 // Match TypeLocs on the declaration. Carefully match only the outermost
266 // TypeLoc that's directly linked to the old class and don't handle nested
267 // name specifier locs.
Eric Liu495b2112016-09-19 17:40:32 +0000268 Finder->addMatcher(
269 typeLoc(IsInMovedNs,
270 loc(qualType(hasDeclaration(DeclMatcher.bind("from_decl")))),
271 unless(anyOf(hasParent(typeLoc(
272 loc(qualType(hasDeclaration(DeclMatcher))))),
273 hasParent(nestedNameSpecifierLoc()))),
274 hasAncestor(decl().bind("dc")))
275 .bind("type"),
276 this);
Eric Liu912d0392016-09-27 12:54:48 +0000277
Eric Liu68765a82016-09-21 15:06:12 +0000278 // Types in `UsingShadowDecl` is not matched by `typeLoc` above, so we need to
279 // special case it.
280 Finder->addMatcher(
Eric Liu912d0392016-09-27 12:54:48 +0000281 usingDecl(IsInMovedNs, hasAnyUsingShadowDecl(decl())).bind("using_decl"),
282 this);
283
Eric Liu68765a82016-09-21 15:06:12 +0000284 // Handle types in nested name specifier.
285 Finder->addMatcher(nestedNameSpecifierLoc(
286 hasAncestor(decl(IsInMovedNs).bind("dc")),
287 loc(nestedNameSpecifier(specifiesType(
288 hasDeclaration(DeclMatcher.bind("from_decl"))))))
289 .bind("nested_specifier_loc"),
290 this);
Eric Liu12068d82016-09-22 11:54:00 +0000291
292 // Handle function.
Eric Liu912d0392016-09-27 12:54:48 +0000293 // Only handle functions that are defined in a namespace excluding member
294 // function, static methods (qualified by nested specifier), and functions
295 // defined in the global namespace.
Eric Liu12068d82016-09-22 11:54:00 +0000296 // Note that the matcher does not exclude calls to out-of-line static method
297 // definitions, so we need to exclude them in the callback handler.
Eric Liu912d0392016-09-27 12:54:48 +0000298 auto FuncMatcher =
299 functionDecl(unless(anyOf(cxxMethodDecl(), IsInMovedNs,
300 hasAncestor(namespaceDecl(isAnonymous())),
301 hasAncestor(cxxRecordDecl()))),
302 hasParent(namespaceDecl()));
Eric Liu12068d82016-09-22 11:54:00 +0000303 Finder->addMatcher(
304 decl(forEachDescendant(callExpr(callee(FuncMatcher)).bind("call")),
Eric Liu912d0392016-09-27 12:54:48 +0000305 IsInMovedNs, unless(isImplicit()))
Eric Liu12068d82016-09-22 11:54:00 +0000306 .bind("dc"),
307 this);
Eric Liu159f0132016-09-30 04:32:39 +0000308
309 auto GlobalVarMatcher = varDecl(
310 hasGlobalStorage(), hasParent(namespaceDecl()),
311 unless(anyOf(IsInMovedNs, hasAncestor(namespaceDecl(isAnonymous())))));
312 Finder->addMatcher(declRefExpr(IsInMovedNs, hasAncestor(decl().bind("dc")),
313 to(GlobalVarMatcher.bind("var_decl")))
314 .bind("var_ref"),
315 this);
Eric Liu495b2112016-09-19 17:40:32 +0000316}
317
318void ChangeNamespaceTool::run(
319 const ast_matchers::MatchFinder::MatchResult &Result) {
320 if (const auto *NsDecl = Result.Nodes.getNodeAs<NamespaceDecl>("old_ns")) {
321 moveOldNamespace(Result, NsDecl);
322 } else if (const auto *FwdDecl =
323 Result.Nodes.getNodeAs<CXXRecordDecl>("fwd_decl")) {
324 moveClassForwardDeclaration(Result, FwdDecl);
Eric Liu68765a82016-09-21 15:06:12 +0000325 } else if (const auto *UsingDeclaration =
326 Result.Nodes.getNodeAs<UsingDecl>("using_decl")) {
327 fixUsingShadowDecl(Result, UsingDeclaration);
328 } else if (const auto *Specifier =
329 Result.Nodes.getNodeAs<NestedNameSpecifierLoc>(
330 "nested_specifier_loc")) {
331 SourceLocation Start = Specifier->getBeginLoc();
332 SourceLocation End = EndLocationForType(Specifier->getTypeLoc());
333 fixTypeLoc(Result, Start, End, Specifier->getTypeLoc());
Eric Liu12068d82016-09-22 11:54:00 +0000334 } else if (const auto *TLoc = Result.Nodes.getNodeAs<TypeLoc>("type")) {
Eric Liu495b2112016-09-19 17:40:32 +0000335 fixTypeLoc(Result, startLocationForType(*TLoc), EndLocationForType(*TLoc),
336 *TLoc);
Eric Liu159f0132016-09-30 04:32:39 +0000337 } else if (const auto *VarRef = Result.Nodes.getNodeAs<DeclRefExpr>("var_ref")){
338 const auto *Var = Result.Nodes.getNodeAs<VarDecl>("var_decl");
339 assert(Var);
340 if (Var->getCanonicalDecl()->isStaticDataMember())
341 return;
342 std::string Name = Var->getQualifiedNameAsString();
343 const clang::Decl *Context = Result.Nodes.getNodeAs<clang::Decl>("dc");
344 assert(Context && "Empty decl context.");
345 clang::SourceRange VarRefRange = VarRef->getSourceRange();
346 replaceQualifiedSymbolInDeclContext(Result, Context, VarRefRange.getBegin(),
347 VarRefRange.getEnd(), Name);
Eric Liu12068d82016-09-22 11:54:00 +0000348 } else {
Eric Liu159f0132016-09-30 04:32:39 +0000349 const auto *Call = Result.Nodes.getNodeAs<clang::CallExpr>("call");
Eric Liu12068d82016-09-22 11:54:00 +0000350 assert(Call != nullptr &&"Expecting callback for CallExpr.");
351 const clang::FunctionDecl* Func = Call->getDirectCallee();
352 assert(Func != nullptr);
353 // Ignore out-of-line static methods since they will be handled by nested
354 // name specifiers.
355 if (Func->getCanonicalDecl()->getStorageClass() ==
356 clang::StorageClass::SC_Static &&
357 Func->isOutOfLine())
358 return;
359 std::string Name = Func->getQualifiedNameAsString();
360 const clang::Decl *Context = Result.Nodes.getNodeAs<clang::Decl>("dc");
361 assert(Context && "Empty decl context.");
362 clang::SourceRange CalleeRange = Call->getCallee()->getSourceRange();
363 replaceQualifiedSymbolInDeclContext(Result, Context, CalleeRange.getBegin(),
364 CalleeRange.getEnd(), Name);
Eric Liu495b2112016-09-19 17:40:32 +0000365 }
366}
367
368// Stores information about a moved namespace in `MoveNamespaces` and leaves
369// the actual movement to `onEndOfTranslationUnit()`.
370void ChangeNamespaceTool::moveOldNamespace(
371 const ast_matchers::MatchFinder::MatchResult &Result,
372 const NamespaceDecl *NsDecl) {
373 // If the namespace is empty, do nothing.
374 if (Decl::castToDeclContext(NsDecl)->decls_empty())
375 return;
376
377 // Get the range of the code in the old namespace.
378 SourceLocation Start = NsDecl->decls_begin()->getLocStart();
379 SourceLocation End = NsDecl->getRBraceLoc().getLocWithOffset(-1);
380 // Create a replacement that deletes the code in the old namespace merely for
381 // retrieving offset and length from it.
382 const auto R = createReplacement(Start, End, "", *Result.SourceManager);
383 MoveNamespace MoveNs;
384 MoveNs.Offset = R.getOffset();
385 MoveNs.Length = R.getLength();
386
387 // Insert the new namespace after `DiffOldNamespace`. For example, if
388 // `OldNamespace` is "a::b::c" and `NewNamespace` is `a::x::y`, then
389 // "x::y" will be inserted inside the existing namespace "a" and after "a::b".
390 // `OuterNs` is the first namespace in `DiffOldNamespace`, e.g. "namespace b"
391 // in the above example.
392 // FIXME: consider the case where DiffOldNamespace is empty.
393 const NamespaceDecl *OuterNs = getOuterNamespace(NsDecl, DiffOldNamespace);
394 SourceLocation LocAfterNs =
395 getStartOfNextLine(OuterNs->getRBraceLoc(), *Result.SourceManager,
396 Result.Context->getLangOpts());
397 assert(LocAfterNs.isValid() &&
398 "Failed to get location after DiffOldNamespace");
399 MoveNs.InsertionOffset = Result.SourceManager->getFileOffset(
400 Result.SourceManager->getSpellingLoc(LocAfterNs));
401
Eric Liucc83c662016-09-19 17:58:59 +0000402 MoveNs.FID = Result.SourceManager->getFileID(Start);
403 MoveNs.SourceMgr = Result.SourceManager;
Eric Liu495b2112016-09-19 17:40:32 +0000404 MoveNamespaces[R.getFilePath()].push_back(MoveNs);
405}
406
407// Removes a class forward declaration from the code in the moved namespace and
408// creates an `InsertForwardDeclaration` to insert the forward declaration back
409// into the old namespace after moving code from the old namespace to the new
410// namespace.
411// For example, changing "a" to "x":
412// Old code:
413// namespace a {
414// class FWD;
415// class A { FWD *fwd; }
416// } // a
417// New code:
418// namespace a {
419// class FWD;
420// } // a
421// namespace x {
422// class A { a::FWD *fwd; }
423// } // x
424void ChangeNamespaceTool::moveClassForwardDeclaration(
425 const ast_matchers::MatchFinder::MatchResult &Result,
426 const CXXRecordDecl *FwdDecl) {
427 SourceLocation Start = FwdDecl->getLocStart();
428 SourceLocation End = FwdDecl->getLocEnd();
429 SourceLocation AfterSemi = Lexer::findLocationAfterToken(
430 End, tok::semi, *Result.SourceManager, Result.Context->getLangOpts(),
431 /*SkipTrailingWhitespaceAndNewLine=*/true);
432 if (AfterSemi.isValid())
433 End = AfterSemi.getLocWithOffset(-1);
434 // Delete the forward declaration from the code to be moved.
435 const auto Deletion =
436 createReplacement(Start, End, "", *Result.SourceManager);
437 addOrMergeReplacement(Deletion, &FileToReplacements[Deletion.getFilePath()]);
438 llvm::StringRef Code = Lexer::getSourceText(
439 CharSourceRange::getTokenRange(
440 Result.SourceManager->getSpellingLoc(Start),
441 Result.SourceManager->getSpellingLoc(End)),
442 *Result.SourceManager, Result.Context->getLangOpts());
443 // Insert the forward declaration back into the old namespace after moving the
444 // code from old namespace to new namespace.
445 // Insertion information is stored in `InsertFwdDecls` and actual
446 // insertion will be performed in `onEndOfTranslationUnit`.
447 // Get the (old) namespace that contains the forward declaration.
448 const auto *NsDecl = Result.Nodes.getNodeAs<NamespaceDecl>("ns_decl");
449 // The namespace contains the forward declaration, so it must not be empty.
450 assert(!NsDecl->decls_empty());
451 const auto Insertion = createInsertion(NsDecl->decls_begin()->getLocStart(),
452 Code, *Result.SourceManager);
453 InsertForwardDeclaration InsertFwd;
454 InsertFwd.InsertionOffset = Insertion.getOffset();
455 InsertFwd.ForwardDeclText = Insertion.getReplacementText().str();
456 InsertFwdDecls[Insertion.getFilePath()].push_back(InsertFwd);
457}
458
459// Replaces a qualified symbol that refers to a declaration `DeclName` with the
460// shortest qualified name possible when the reference is in `NewNamespace`.
Eric Liu912d0392016-09-27 12:54:48 +0000461// FIXME: don't need to add redundant namespace qualifier when there is
462// UsingShadowDecl or using namespace decl.
Eric Liu495b2112016-09-19 17:40:32 +0000463void ChangeNamespaceTool::replaceQualifiedSymbolInDeclContext(
464 const ast_matchers::MatchFinder::MatchResult &Result, const Decl *DeclCtx,
465 SourceLocation Start, SourceLocation End, llvm::StringRef DeclName) {
466 const auto *NsDeclContext =
467 DeclCtx->getDeclContext()->getEnclosingNamespaceContext();
468 const auto *NsDecl = llvm::dyn_cast<NamespaceDecl>(NsDeclContext);
469 // Calculate the name of the `NsDecl` after it is moved to new namespace.
470 std::string OldNs = NsDecl->getQualifiedNameAsString();
471 llvm::StringRef Postfix = OldNs;
472 bool Consumed = Postfix.consume_front(OldNamespace);
473 assert(Consumed && "Expect OldNS to start with OldNamespace.");
474 (void)Consumed;
475 const std::string NewNs = (NewNamespace + Postfix).str();
476
477 llvm::StringRef NestedName = Lexer::getSourceText(
478 CharSourceRange::getTokenRange(
479 Result.SourceManager->getSpellingLoc(Start),
480 Result.SourceManager->getSpellingLoc(End)),
481 *Result.SourceManager, Result.Context->getLangOpts());
482 // If the symbol is already fully qualified, no change needs to be make.
483 if (NestedName.startswith("::"))
484 return;
485 std::string ReplaceName =
486 getShortestQualifiedNameInNamespace(DeclName, NewNs);
487 // If the new nested name in the new namespace is the same as it was in the
488 // old namespace, we don't create replacement.
489 if (NestedName == ReplaceName)
490 return;
491 auto R = createReplacement(Start, End, ReplaceName, *Result.SourceManager);
492 addOrMergeReplacement(R, &FileToReplacements[R.getFilePath()]);
493}
494
495// Replace the [Start, End] of `Type` with the shortest qualified name when the
496// `Type` is in `NewNamespace`.
497void ChangeNamespaceTool::fixTypeLoc(
498 const ast_matchers::MatchFinder::MatchResult &Result, SourceLocation Start,
499 SourceLocation End, TypeLoc Type) {
500 // FIXME: do not rename template parameter.
501 if (Start.isInvalid() || End.isInvalid())
502 return;
503 // The declaration which this TypeLoc refers to.
504 const auto *FromDecl = Result.Nodes.getNodeAs<NamedDecl>("from_decl");
505 // `hasDeclaration` gives underlying declaration, but if the type is
506 // a typedef type, we need to use the typedef type instead.
507 if (auto *Typedef = Type.getType()->getAs<TypedefType>())
508 FromDecl = Typedef->getDecl();
509
510 const Decl *DeclCtx = Result.Nodes.getNodeAs<Decl>("dc");
511 assert(DeclCtx && "Empty decl context.");
512 replaceQualifiedSymbolInDeclContext(Result, DeclCtx, Start, End,
513 FromDecl->getQualifiedNameAsString());
514}
515
Eric Liu68765a82016-09-21 15:06:12 +0000516void ChangeNamespaceTool::fixUsingShadowDecl(
517 const ast_matchers::MatchFinder::MatchResult &Result,
518 const UsingDecl *UsingDeclaration) {
519 SourceLocation Start = UsingDeclaration->getLocStart();
520 SourceLocation End = UsingDeclaration->getLocEnd();
521 if (Start.isInvalid() || End.isInvalid()) return;
522
523 assert(UsingDeclaration->shadow_size() > 0);
524 // FIXME: it might not be always accurate to use the first using-decl.
525 const NamedDecl *TargetDecl =
526 UsingDeclaration->shadow_begin()->getTargetDecl();
527 std::string TargetDeclName = TargetDecl->getQualifiedNameAsString();
528 // FIXME: check if target_decl_name is in moved ns, which doesn't make much
529 // sense. If this happens, we need to use name with the new namespace.
530 // Use fully qualified name in UsingDecl for now.
531 auto R = createReplacement(Start, End, "using ::" + TargetDeclName,
532 *Result.SourceManager);
533 addOrMergeReplacement(R, &FileToReplacements[R.getFilePath()]);
534}
535
Eric Liu495b2112016-09-19 17:40:32 +0000536void ChangeNamespaceTool::onEndOfTranslationUnit() {
537 // Move namespace blocks and insert forward declaration to old namespace.
538 for (const auto &FileAndNsMoves : MoveNamespaces) {
539 auto &NsMoves = FileAndNsMoves.second;
540 if (NsMoves.empty())
541 continue;
542 const std::string &FilePath = FileAndNsMoves.first;
543 auto &Replaces = FileToReplacements[FilePath];
Eric Liucc83c662016-09-19 17:58:59 +0000544 auto &SM = *NsMoves.begin()->SourceMgr;
545 llvm::StringRef Code = SM.getBufferData(NsMoves.begin()->FID);
Eric Liu495b2112016-09-19 17:40:32 +0000546 auto ChangedCode = tooling::applyAllReplacements(Code, Replaces);
547 if (!ChangedCode) {
548 llvm::errs() << llvm::toString(ChangedCode.takeError()) << "\n";
549 continue;
550 }
551 // Replacements on the changed code for moving namespaces and inserting
552 // forward declarations to old namespaces.
553 tooling::Replacements NewReplacements;
554 // Cut the changed code from the old namespace and paste the code in the new
555 // namespace.
556 for (const auto &NsMove : NsMoves) {
557 // Calculate the range of the old namespace block in the changed
558 // code.
559 const unsigned NewOffset = Replaces.getShiftedCodePosition(NsMove.Offset);
560 const unsigned NewLength =
561 Replaces.getShiftedCodePosition(NsMove.Offset + NsMove.Length) -
562 NewOffset;
563 tooling::Replacement Deletion(FilePath, NewOffset, NewLength, "");
564 std::string MovedCode = ChangedCode->substr(NewOffset, NewLength);
565 std::string MovedCodeWrappedInNewNs =
566 wrapCodeInNamespace(DiffNewNamespace, MovedCode);
567 // Calculate the new offset at which the code will be inserted in the
568 // changed code.
569 unsigned NewInsertionOffset =
570 Replaces.getShiftedCodePosition(NsMove.InsertionOffset);
571 tooling::Replacement Insertion(FilePath, NewInsertionOffset, 0,
572 MovedCodeWrappedInNewNs);
573 addOrMergeReplacement(Deletion, &NewReplacements);
574 addOrMergeReplacement(Insertion, &NewReplacements);
575 }
576 // After moving namespaces, insert forward declarations back to old
577 // namespaces.
578 const auto &FwdDeclInsertions = InsertFwdDecls[FilePath];
579 for (const auto &FwdDeclInsertion : FwdDeclInsertions) {
580 unsigned NewInsertionOffset =
581 Replaces.getShiftedCodePosition(FwdDeclInsertion.InsertionOffset);
582 tooling::Replacement Insertion(FilePath, NewInsertionOffset, 0,
583 FwdDeclInsertion.ForwardDeclText);
584 addOrMergeReplacement(Insertion, &NewReplacements);
585 }
586 // Add replacements referring to the changed code to existing replacements,
587 // which refers to the original code.
588 Replaces = Replaces.merge(NewReplacements);
589 format::FormatStyle Style =
590 format::getStyle("file", FilePath, FallbackStyle);
591 // Clean up old namespaces if there is nothing in it after moving.
592 auto CleanReplacements =
593 format::cleanupAroundReplacements(Code, Replaces, Style);
594 if (!CleanReplacements) {
595 llvm::errs() << llvm::toString(CleanReplacements.takeError()) << "\n";
596 continue;
597 }
598 FileToReplacements[FilePath] = *CleanReplacements;
599 }
600}
601
602} // namespace change_namespace
603} // namespace clang