summaryrefslogtreecommitdiffstats
path: root/clang-change-namespace/ChangeNamespace.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'clang-change-namespace/ChangeNamespace.cpp')
-rw-r--r--clang-change-namespace/ChangeNamespace.cpp1042
1 files changed, 1042 insertions, 0 deletions
diff --git a/clang-change-namespace/ChangeNamespace.cpp b/clang-change-namespace/ChangeNamespace.cpp
new file mode 100644
index 00000000..eb732639
--- /dev/null
+++ b/clang-change-namespace/ChangeNamespace.cpp
@@ -0,0 +1,1042 @@
+//===-- ChangeNamespace.cpp - Change namespace implementation -------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+#include "ChangeNamespace.h"
+#include "clang/AST/ASTContext.h"
+#include "clang/Format/Format.h"
+#include "clang/Lex/Lexer.h"
+#include "llvm/Support/Casting.h"
+#include "llvm/Support/ErrorHandling.h"
+
+using namespace clang::ast_matchers;
+
+namespace clang {
+namespace change_namespace {
+
+namespace {
+
+inline std::string
+joinNamespaces(const llvm::SmallVectorImpl<StringRef> &Namespaces) {
+ if (Namespaces.empty())
+ return "";
+ std::string Result = Namespaces.front();
+ for (auto I = Namespaces.begin() + 1, E = Namespaces.end(); I != E; ++I)
+ Result += ("::" + *I).str();
+ return Result;
+}
+
+// Given "a::b::c", returns {"a", "b", "c"}.
+llvm::SmallVector<llvm::StringRef, 4> splitSymbolName(llvm::StringRef Name) {
+ llvm::SmallVector<llvm::StringRef, 4> Splitted;
+ Name.split(Splitted, "::", /*MaxSplit=*/-1,
+ /*KeepEmpty=*/false);
+ return Splitted;
+}
+
+SourceLocation startLocationForType(TypeLoc TLoc) {
+ // For elaborated types (e.g. `struct a::A`) we want the portion after the
+ // `struct` but including the namespace qualifier, `a::`.
+ if (TLoc.getTypeLocClass() == TypeLoc::Elaborated) {
+ NestedNameSpecifierLoc NestedNameSpecifier =
+ TLoc.castAs<ElaboratedTypeLoc>().getQualifierLoc();
+ if (NestedNameSpecifier.getNestedNameSpecifier())
+ return NestedNameSpecifier.getBeginLoc();
+ TLoc = TLoc.getNextTypeLoc();
+ }
+ return TLoc.getBeginLoc();
+}
+
+SourceLocation endLocationForType(TypeLoc TLoc) {
+ // Dig past any namespace or keyword qualifications.
+ while (TLoc.getTypeLocClass() == TypeLoc::Elaborated ||
+ TLoc.getTypeLocClass() == TypeLoc::Qualified)
+ TLoc = TLoc.getNextTypeLoc();
+
+ // The location for template specializations (e.g. Foo<int>) includes the
+ // templated types in its location range. We want to restrict this to just
+ // before the `<` character.
+ if (TLoc.getTypeLocClass() == TypeLoc::TemplateSpecialization)
+ return TLoc.castAs<TemplateSpecializationTypeLoc>()
+ .getLAngleLoc()
+ .getLocWithOffset(-1);
+ return TLoc.getEndLoc();
+}
+
+// Returns the containing namespace of `InnerNs` by skipping `PartialNsName`.
+// If the `InnerNs` does not have `PartialNsName` as suffix, or `PartialNsName`
+// is empty, nullptr is returned.
+// For example, if `InnerNs` is "a::b::c" and `PartialNsName` is "b::c", then
+// the NamespaceDecl of namespace "a" will be returned.
+const NamespaceDecl *getOuterNamespace(const NamespaceDecl *InnerNs,
+ llvm::StringRef PartialNsName) {
+ if (!InnerNs || PartialNsName.empty())
+ return nullptr;
+ const auto *CurrentContext = llvm::cast<DeclContext>(InnerNs);
+ const auto *CurrentNs = InnerNs;
+ auto PartialNsNameSplitted = splitSymbolName(PartialNsName);
+ while (!PartialNsNameSplitted.empty()) {
+ // Get the inner-most namespace in CurrentContext.
+ while (CurrentContext && !llvm::isa<NamespaceDecl>(CurrentContext))
+ CurrentContext = CurrentContext->getParent();
+ if (!CurrentContext)
+ return nullptr;
+ CurrentNs = llvm::cast<NamespaceDecl>(CurrentContext);
+ if (PartialNsNameSplitted.back() != CurrentNs->getNameAsString())
+ return nullptr;
+ PartialNsNameSplitted.pop_back();
+ CurrentContext = CurrentContext->getParent();
+ }
+ return CurrentNs;
+}
+
+static std::unique_ptr<Lexer>
+getLexerStartingFromLoc(SourceLocation Loc, const SourceManager &SM,
+ const LangOptions &LangOpts) {
+ if (Loc.isMacroID() &&
+ !Lexer::isAtEndOfMacroExpansion(Loc, SM, LangOpts, &Loc))
+ return nullptr;
+ // Break down the source location.
+ std::pair<FileID, unsigned> LocInfo = SM.getDecomposedLoc(Loc);
+ // Try to load the file buffer.
+ bool InvalidTemp = false;
+ llvm::StringRef File = SM.getBufferData(LocInfo.first, &InvalidTemp);
+ if (InvalidTemp)
+ return nullptr;
+
+ const char *TokBegin = File.data() + LocInfo.second;
+ // Lex from the start of the given location.
+ return llvm::make_unique<Lexer>(SM.getLocForStartOfFile(LocInfo.first),
+ LangOpts, File.begin(), TokBegin, File.end());
+}
+
+// FIXME: get rid of this helper function if this is supported in clang-refactor
+// library.
+static SourceLocation getStartOfNextLine(SourceLocation Loc,
+ const SourceManager &SM,
+ const LangOptions &LangOpts) {
+ std::unique_ptr<Lexer> Lex = getLexerStartingFromLoc(Loc, SM, LangOpts);
+ if (!Lex.get())
+ return SourceLocation();
+ llvm::SmallVector<char, 16> Line;
+ // FIXME: this is a bit hacky to get ReadToEndOfLine work.
+ Lex->setParsingPreprocessorDirective(true);
+ Lex->ReadToEndOfLine(&Line);
+ auto End = Loc.getLocWithOffset(Line.size());
+ return SM.getLocForEndOfFile(SM.getDecomposedLoc(Loc).first) == End
+ ? End
+ : End.getLocWithOffset(1);
+}
+
+// Returns `R` with new range that refers to code after `Replaces` being
+// applied.
+tooling::Replacement
+getReplacementInChangedCode(const tooling::Replacements &Replaces,
+ const tooling::Replacement &R) {
+ unsigned NewStart = Replaces.getShiftedCodePosition(R.getOffset());
+ unsigned NewEnd =
+ Replaces.getShiftedCodePosition(R.getOffset() + R.getLength());
+ return tooling::Replacement(R.getFilePath(), NewStart, NewEnd - NewStart,
+ R.getReplacementText());
+}
+
+// Adds a replacement `R` into `Replaces` or merges it into `Replaces` by
+// applying all existing Replaces first if there is conflict.
+void addOrMergeReplacement(const tooling::Replacement &R,
+ tooling::Replacements *Replaces) {
+ auto Err = Replaces->add(R);
+ if (Err) {
+ llvm::consumeError(std::move(Err));
+ auto Replace = getReplacementInChangedCode(*Replaces, R);
+ *Replaces = Replaces->merge(tooling::Replacements(Replace));
+ }
+}
+
+tooling::Replacement createReplacement(SourceLocation Start, SourceLocation End,
+ llvm::StringRef ReplacementText,
+ const SourceManager &SM) {
+ if (!Start.isValid() || !End.isValid()) {
+ llvm::errs() << "start or end location were invalid\n";
+ return tooling::Replacement();
+ }
+ if (SM.getDecomposedLoc(Start).first != SM.getDecomposedLoc(End).first) {
+ llvm::errs()
+ << "start or end location were in different macro expansions\n";
+ return tooling::Replacement();
+ }
+ Start = SM.getSpellingLoc(Start);
+ End = SM.getSpellingLoc(End);
+ if (SM.getFileID(Start) != SM.getFileID(End)) {
+ llvm::errs() << "start or end location were in different files\n";
+ return tooling::Replacement();
+ }
+ return tooling::Replacement(
+ SM, CharSourceRange::getTokenRange(SM.getSpellingLoc(Start),
+ SM.getSpellingLoc(End)),
+ ReplacementText);
+}
+
+void addReplacementOrDie(
+ SourceLocation Start, SourceLocation End, llvm::StringRef ReplacementText,
+ const SourceManager &SM,
+ std::map<std::string, tooling::Replacements> *FileToReplacements) {
+ const auto R = createReplacement(Start, End, ReplacementText, SM);
+ auto Err = (*FileToReplacements)[R.getFilePath()].add(R);
+ if (Err)
+ llvm_unreachable(llvm::toString(std::move(Err)).c_str());
+}
+
+tooling::Replacement createInsertion(SourceLocation Loc,
+ llvm::StringRef InsertText,
+ const SourceManager &SM) {
+ if (Loc.isInvalid()) {
+ llvm::errs() << "insert Location is invalid.\n";
+ return tooling::Replacement();
+ }
+ Loc = SM.getSpellingLoc(Loc);
+ return tooling::Replacement(SM, Loc, 0, InsertText);
+}
+
+// Returns the shortest qualified name for declaration `DeclName` in the
+// namespace `NsName`. For example, if `DeclName` is "a::b::X" and `NsName`
+// is "a::c::d", then "b::X" will be returned.
+// Note that if `DeclName` is `::b::X` and `NsName` is `::a::b`, this returns
+// "::b::X" instead of "b::X" since there will be a name conflict otherwise.
+// \param DeclName A fully qualified name, "::a::b::X" or "a::b::X".
+// \param NsName A fully qualified name, "::a::b" or "a::b". Global namespace
+// will have empty name.
+std::string getShortestQualifiedNameInNamespace(llvm::StringRef DeclName,
+ llvm::StringRef NsName) {
+ DeclName = DeclName.ltrim(':');
+ NsName = NsName.ltrim(':');
+ if (DeclName.find(':') == llvm::StringRef::npos)
+ return DeclName;
+
+ auto NsNameSplitted = splitSymbolName(NsName);
+ auto DeclNsSplitted = splitSymbolName(DeclName);
+ llvm::StringRef UnqualifiedDeclName = DeclNsSplitted.pop_back_val();
+ // If the Decl is in global namespace, there is no need to shorten it.
+ if (DeclNsSplitted.empty())
+ return UnqualifiedDeclName;
+ // If NsName is the global namespace, we can simply use the DeclName sans
+ // leading "::".
+ if (NsNameSplitted.empty())
+ return DeclName;
+
+ if (NsNameSplitted.front() != DeclNsSplitted.front()) {
+ // The DeclName must be fully-qualified, but we still need to decide if a
+ // leading "::" is necessary. For example, if `NsName` is "a::b::c" and the
+ // `DeclName` is "b::X", then the reference must be qualified as "::b::X"
+ // to avoid conflict.
+ if (llvm::is_contained(NsNameSplitted, DeclNsSplitted.front()))
+ return ("::" + DeclName).str();
+ return DeclName;
+ }
+ // Since there is already an overlap namespace, we know that `DeclName` can be
+ // shortened, so we reduce the longest common prefix.
+ auto DeclI = DeclNsSplitted.begin();
+ auto DeclE = DeclNsSplitted.end();
+ auto NsI = NsNameSplitted.begin();
+ auto NsE = NsNameSplitted.end();
+ for (; DeclI != DeclE && NsI != NsE && *DeclI == *NsI; ++DeclI, ++NsI) {
+ }
+ return (DeclI == DeclE)
+ ? UnqualifiedDeclName.str()
+ : (llvm::join(DeclI, DeclE, "::") + "::" + UnqualifiedDeclName)
+ .str();
+}
+
+std::string wrapCodeInNamespace(StringRef NestedNs, std::string Code) {
+ if (Code.back() != '\n')
+ Code += "\n";
+ auto NsSplitted = splitSymbolName(NestedNs);
+ while (!NsSplitted.empty()) {
+ // FIXME: consider code style for comments.
+ Code = ("namespace " + NsSplitted.back() + " {\n" + Code +
+ "} // namespace " + NsSplitted.back() + "\n")
+ .str();
+ NsSplitted.pop_back();
+ }
+ return Code;
+}
+
+// Returns true if \p D is a nested DeclContext in \p Context
+bool isNestedDeclContext(const DeclContext *D, const DeclContext *Context) {
+ while (D) {
+ if (D == Context)
+ return true;
+ D = D->getParent();
+ }
+ return false;
+}
+
+// Returns true if \p D is visible at \p Loc with DeclContext \p DeclCtx.
+bool isDeclVisibleAtLocation(const SourceManager &SM, const Decl *D,
+ const DeclContext *DeclCtx, SourceLocation Loc) {
+ SourceLocation DeclLoc = SM.getSpellingLoc(D->getBeginLoc());
+ Loc = SM.getSpellingLoc(Loc);
+ return SM.isBeforeInTranslationUnit(DeclLoc, Loc) &&
+ (SM.getFileID(DeclLoc) == SM.getFileID(Loc) &&
+ isNestedDeclContext(DeclCtx, D->getDeclContext()));
+}
+
+// Given a qualified symbol name, returns true if the symbol will be
+// incorrectly qualified without leading "::". For example, a symbol
+// "nx::ny::Foo" in namespace "na::nx::ny" without leading "::"; a symbol
+// "util::X" in namespace "na" can potentially conflict with "na::util" (if this
+// exists).
+bool conflictInNamespace(const ASTContext &AST, llvm::StringRef QualifiedSymbol,
+ llvm::StringRef Namespace) {
+ auto SymbolSplitted = splitSymbolName(QualifiedSymbol.trim(":"));
+ assert(!SymbolSplitted.empty());
+ SymbolSplitted.pop_back(); // We are only interested in namespaces.
+
+ if (SymbolSplitted.size() >= 1 && !Namespace.empty()) {
+ auto SymbolTopNs = SymbolSplitted.front();
+ auto NsSplitted = splitSymbolName(Namespace.trim(":"));
+ assert(!NsSplitted.empty());
+
+ auto LookupDecl = [&AST](const Decl &Scope,
+ llvm::StringRef Name) -> const NamedDecl * {
+ const auto *DC = llvm::dyn_cast<DeclContext>(&Scope);
+ if (!DC)
+ return nullptr;
+ auto LookupRes = DC->lookup(DeclarationName(&AST.Idents.get(Name)));
+ if (LookupRes.empty())
+ return nullptr;
+ return LookupRes.front();
+ };
+ // We do not check the outermost namespace since it would not be a
+ // conflict if it equals to the symbol's outermost namespace and the
+ // symbol name would have been shortened.
+ const NamedDecl *Scope =
+ LookupDecl(*AST.getTranslationUnitDecl(), NsSplitted.front());
+ for (auto I = NsSplitted.begin() + 1, E = NsSplitted.end(); I != E; ++I) {
+ if (*I == SymbolTopNs) // Handles "::ny" in "::nx::ny" case.
+ return true;
+ // Handles "::util" and "::nx::util" conflicts.
+ if (Scope) {
+ if (LookupDecl(*Scope, SymbolTopNs))
+ return true;
+ Scope = LookupDecl(*Scope, *I);
+ }
+ }
+ if (Scope && LookupDecl(*Scope, SymbolTopNs))
+ return true;
+ }
+ return false;
+}
+
+AST_MATCHER(EnumDecl, isScoped) {
+ return Node.isScoped();
+}
+
+bool isTemplateParameter(TypeLoc Type) {
+ while (!Type.isNull()) {
+ if (Type.getTypeLocClass() == TypeLoc::SubstTemplateTypeParm)
+ return true;
+ Type = Type.getNextTypeLoc();
+ }
+ return false;
+}
+
+} // anonymous namespace
+
+ChangeNamespaceTool::ChangeNamespaceTool(
+ llvm::StringRef OldNs, llvm::StringRef NewNs, llvm::StringRef FilePattern,
+ llvm::ArrayRef<std::string> WhiteListedSymbolPatterns,
+ std::map<std::string, tooling::Replacements> *FileToReplacements,
+ llvm::StringRef FallbackStyle)
+ : FallbackStyle(FallbackStyle), FileToReplacements(*FileToReplacements),
+ OldNamespace(OldNs.ltrim(':')), NewNamespace(NewNs.ltrim(':')),
+ FilePattern(FilePattern), FilePatternRE(FilePattern) {
+ FileToReplacements->clear();
+ auto OldNsSplitted = splitSymbolName(OldNamespace);
+ auto NewNsSplitted = splitSymbolName(NewNamespace);
+ // Calculates `DiffOldNamespace` and `DiffNewNamespace`.
+ while (!OldNsSplitted.empty() && !NewNsSplitted.empty() &&
+ OldNsSplitted.front() == NewNsSplitted.front()) {
+ OldNsSplitted.erase(OldNsSplitted.begin());
+ NewNsSplitted.erase(NewNsSplitted.begin());
+ }
+ DiffOldNamespace = joinNamespaces(OldNsSplitted);
+ DiffNewNamespace = joinNamespaces(NewNsSplitted);
+
+ for (const auto &Pattern : WhiteListedSymbolPatterns)
+ WhiteListedSymbolRegexes.emplace_back(Pattern);
+}
+
+void ChangeNamespaceTool::registerMatchers(ast_matchers::MatchFinder *Finder) {
+ std::string FullOldNs = "::" + OldNamespace;
+ // Prefix is the outer-most namespace in DiffOldNamespace. For example, if the
+ // OldNamespace is "a::b::c" and DiffOldNamespace is "b::c", then Prefix will
+ // be "a::b". Declarations in this namespace will not be visible in the new
+ // namespace. If DiffOldNamespace is empty, Prefix will be a invalid name "-".
+ llvm::SmallVector<llvm::StringRef, 4> DiffOldNsSplitted;
+ llvm::StringRef(DiffOldNamespace)
+ .split(DiffOldNsSplitted, "::", /*MaxSplit=*/-1,
+ /*KeepEmpty=*/false);
+ std::string Prefix = "-";
+ if (!DiffOldNsSplitted.empty())
+ Prefix = (StringRef(FullOldNs).drop_back(DiffOldNamespace.size()) +
+ DiffOldNsSplitted.front())
+ .str();
+ auto IsInMovedNs =
+ allOf(hasAncestor(namespaceDecl(hasName(FullOldNs)).bind("ns_decl")),
+ isExpansionInFileMatching(FilePattern));
+ auto IsVisibleInNewNs = anyOf(
+ IsInMovedNs, unless(hasAncestor(namespaceDecl(hasName(Prefix)))));
+ // Match using declarations.
+ Finder->addMatcher(
+ usingDecl(isExpansionInFileMatching(FilePattern), IsVisibleInNewNs)
+ .bind("using"),
+ this);
+ // Match using namespace declarations.
+ Finder->addMatcher(usingDirectiveDecl(isExpansionInFileMatching(FilePattern),
+ IsVisibleInNewNs)
+ .bind("using_namespace"),
+ this);
+ // Match namespace alias declarations.
+ Finder->addMatcher(namespaceAliasDecl(isExpansionInFileMatching(FilePattern),
+ IsVisibleInNewNs)
+ .bind("namespace_alias"),
+ this);
+
+ // Match old namespace blocks.
+ Finder->addMatcher(
+ namespaceDecl(hasName(FullOldNs), isExpansionInFileMatching(FilePattern))
+ .bind("old_ns"),
+ this);
+
+ // Match class forward-declarations in the old namespace.
+ // Note that forward-declarations in classes are not matched.
+ Finder->addMatcher(cxxRecordDecl(unless(anyOf(isImplicit(), isDefinition())),
+ IsInMovedNs, hasParent(namespaceDecl()))
+ .bind("class_fwd_decl"),
+ this);
+
+ // Match template class forward-declarations in the old namespace.
+ Finder->addMatcher(
+ classTemplateDecl(unless(hasDescendant(cxxRecordDecl(isDefinition()))),
+ IsInMovedNs, hasParent(namespaceDecl()))
+ .bind("template_class_fwd_decl"),
+ this);
+
+ // Match references to types that are not defined in the old namespace.
+ // Forward-declarations in the old namespace are also matched since they will
+ // be moved back to the old namespace.
+ auto DeclMatcher = namedDecl(
+ hasAncestor(namespaceDecl()),
+ unless(anyOf(
+ isImplicit(), hasAncestor(namespaceDecl(isAnonymous())),
+ hasAncestor(cxxRecordDecl()),
+ allOf(IsInMovedNs, unless(cxxRecordDecl(unless(isDefinition())))))));
+
+ // Using shadow declarations in classes always refers to base class, which
+ // does not need to be qualified since it can be inferred from inheritance.
+ // Note that this does not match using alias declarations.
+ auto UsingShadowDeclInClass =
+ usingDecl(hasAnyUsingShadowDecl(decl()), hasParent(cxxRecordDecl()));
+
+ // Match TypeLocs on the declaration. Carefully match only the outermost
+ // TypeLoc and template specialization arguments (which are not outermost)
+ // that are directly linked to types matching `DeclMatcher`. Nested name
+ // specifier locs are handled separately below.
+ Finder->addMatcher(
+ typeLoc(IsInMovedNs,
+ loc(qualType(hasDeclaration(DeclMatcher.bind("from_decl")))),
+ unless(anyOf(hasParent(typeLoc(loc(qualType(
+ hasDeclaration(DeclMatcher),
+ unless(templateSpecializationType()))))),
+ hasParent(nestedNameSpecifierLoc()),
+ hasAncestor(isImplicit()),
+ hasAncestor(UsingShadowDeclInClass),
+ hasAncestor(functionDecl(isDefaulted())))),
+ hasAncestor(decl().bind("dc")))
+ .bind("type"),
+ this);
+
+ // Types in `UsingShadowDecl` is not matched by `typeLoc` above, so we need to
+ // special case it.
+ // Since using declarations inside classes must have the base class in the
+ // nested name specifier, we leave it to the nested name specifier matcher.
+ Finder->addMatcher(usingDecl(IsInMovedNs, hasAnyUsingShadowDecl(decl()),
+ unless(UsingShadowDeclInClass))
+ .bind("using_with_shadow"),
+ this);
+
+ // Handle types in nested name specifier. Specifiers that are in a TypeLoc
+ // matched above are not matched, e.g. "A::" in "A::A" is not matched since
+ // "A::A" would have already been fixed.
+ Finder->addMatcher(
+ nestedNameSpecifierLoc(
+ hasAncestor(decl(IsInMovedNs).bind("dc")),
+ loc(nestedNameSpecifier(
+ specifiesType(hasDeclaration(DeclMatcher.bind("from_decl"))))),
+ unless(anyOf(hasAncestor(isImplicit()),
+ hasAncestor(UsingShadowDeclInClass),
+ hasAncestor(functionDecl(isDefaulted())),
+ hasAncestor(typeLoc(loc(qualType(hasDeclaration(
+ decl(equalsBoundNode("from_decl"))))))))))
+ .bind("nested_specifier_loc"),
+ this);
+
+ // Matches base class initializers in constructors. TypeLocs of base class
+ // initializers do not need to be fixed. For example,
+ // class X : public a::b::Y {
+ // public:
+ // X() : Y::Y() {} // Y::Y do not need namespace specifier.
+ // };
+ Finder->addMatcher(
+ cxxCtorInitializer(isBaseInitializer()).bind("base_initializer"), this);
+
+ // Handle function.
+ // Only handle functions that are defined in a namespace excluding member
+ // function, static methods (qualified by nested specifier), and functions
+ // defined in the global namespace.
+ // Note that the matcher does not exclude calls to out-of-line static method
+ // definitions, so we need to exclude them in the callback handler.
+ auto FuncMatcher =
+ functionDecl(unless(anyOf(cxxMethodDecl(), IsInMovedNs,
+ hasAncestor(namespaceDecl(isAnonymous())),
+ hasAncestor(cxxRecordDecl()))),
+ hasParent(namespaceDecl()));
+ Finder->addMatcher(expr(hasAncestor(decl().bind("dc")), IsInMovedNs,
+ unless(hasAncestor(isImplicit())),
+ anyOf(callExpr(callee(FuncMatcher)).bind("call"),
+ declRefExpr(to(FuncMatcher.bind("func_decl")))
+ .bind("func_ref"))),
+ this);
+
+ auto GlobalVarMatcher = varDecl(
+ hasGlobalStorage(), hasParent(namespaceDecl()),
+ unless(anyOf(IsInMovedNs, hasAncestor(namespaceDecl(isAnonymous())))));
+ Finder->addMatcher(declRefExpr(IsInMovedNs, hasAncestor(decl().bind("dc")),
+ to(GlobalVarMatcher.bind("var_decl")))
+ .bind("var_ref"),
+ this);
+
+ // Handle unscoped enum constant.
+ auto UnscopedEnumMatcher = enumConstantDecl(hasParent(enumDecl(
+ hasParent(namespaceDecl()),
+ unless(anyOf(isScoped(), IsInMovedNs, hasAncestor(cxxRecordDecl()),
+ hasAncestor(namespaceDecl(isAnonymous())))))));
+ Finder->addMatcher(
+ declRefExpr(IsInMovedNs, hasAncestor(decl().bind("dc")),
+ to(UnscopedEnumMatcher.bind("enum_const_decl")))
+ .bind("enum_const_ref"),
+ this);
+}
+
+void ChangeNamespaceTool::run(
+ const ast_matchers::MatchFinder::MatchResult &Result) {
+ if (const auto *Using = Result.Nodes.getNodeAs<UsingDecl>("using")) {
+ UsingDecls.insert(Using);
+ } else if (const auto *UsingNamespace =
+ Result.Nodes.getNodeAs<UsingDirectiveDecl>(
+ "using_namespace")) {
+ UsingNamespaceDecls.insert(UsingNamespace);
+ } else if (const auto *NamespaceAlias =
+ Result.Nodes.getNodeAs<NamespaceAliasDecl>(
+ "namespace_alias")) {
+ NamespaceAliasDecls.insert(NamespaceAlias);
+ } else if (const auto *NsDecl =
+ Result.Nodes.getNodeAs<NamespaceDecl>("old_ns")) {
+ moveOldNamespace(Result, NsDecl);
+ } else if (const auto *FwdDecl =
+ Result.Nodes.getNodeAs<CXXRecordDecl>("class_fwd_decl")) {
+ moveClassForwardDeclaration(Result, cast<NamedDecl>(FwdDecl));
+ } else if (const auto *TemplateFwdDecl =
+ Result.Nodes.getNodeAs<ClassTemplateDecl>(
+ "template_class_fwd_decl")) {
+ moveClassForwardDeclaration(Result, cast<NamedDecl>(TemplateFwdDecl));
+ } else if (const auto *UsingWithShadow =
+ Result.Nodes.getNodeAs<UsingDecl>("using_with_shadow")) {
+ fixUsingShadowDecl(Result, UsingWithShadow);
+ } else if (const auto *Specifier =
+ Result.Nodes.getNodeAs<NestedNameSpecifierLoc>(
+ "nested_specifier_loc")) {
+ SourceLocation Start = Specifier->getBeginLoc();
+ SourceLocation End = endLocationForType(Specifier->getTypeLoc());
+ fixTypeLoc(Result, Start, End, Specifier->getTypeLoc());
+ } else if (const auto *BaseInitializer =
+ Result.Nodes.getNodeAs<CXXCtorInitializer>(
+ "base_initializer")) {
+ BaseCtorInitializerTypeLocs.push_back(
+ BaseInitializer->getTypeSourceInfo()->getTypeLoc());
+ } else if (const auto *TLoc = Result.Nodes.getNodeAs<TypeLoc>("type")) {
+ // This avoids fixing types with record types as qualifier, which is not
+ // filtered by matchers in some cases, e.g. the type is templated. We should
+ // handle the record type qualifier instead.
+ TypeLoc Loc = *TLoc;
+ while (Loc.getTypeLocClass() == TypeLoc::Qualified)
+ Loc = Loc.getNextTypeLoc();
+ if (Loc.getTypeLocClass() == TypeLoc::Elaborated) {
+ NestedNameSpecifierLoc NestedNameSpecifier =
+ Loc.castAs<ElaboratedTypeLoc>().getQualifierLoc();
+ // This happens for friend declaration of a base class with injected class
+ // name.
+ if (!NestedNameSpecifier.getNestedNameSpecifier())
+ return;
+ const Type *SpecifierType =
+ NestedNameSpecifier.getNestedNameSpecifier()->getAsType();
+ if (SpecifierType && SpecifierType->isRecordType())
+ return;
+ }
+ fixTypeLoc(Result, startLocationForType(Loc), endLocationForType(Loc), Loc);
+ } else if (const auto *VarRef =
+ Result.Nodes.getNodeAs<DeclRefExpr>("var_ref")) {
+ const auto *Var = Result.Nodes.getNodeAs<VarDecl>("var_decl");
+ assert(Var);
+ if (Var->getCanonicalDecl()->isStaticDataMember())
+ return;
+ const auto *Context = Result.Nodes.getNodeAs<Decl>("dc");
+ assert(Context && "Empty decl context.");
+ fixDeclRefExpr(Result, Context->getDeclContext(),
+ llvm::cast<NamedDecl>(Var), VarRef);
+ } else if (const auto *EnumConstRef =
+ Result.Nodes.getNodeAs<DeclRefExpr>("enum_const_ref")) {
+ // Do not rename the reference if it is already scoped by the EnumDecl name.
+ if (EnumConstRef->hasQualifier() &&
+ EnumConstRef->getQualifier()->getKind() ==
+ NestedNameSpecifier::SpecifierKind::TypeSpec &&
+ EnumConstRef->getQualifier()->getAsType()->isEnumeralType())
+ return;
+ const auto *EnumConstDecl =
+ Result.Nodes.getNodeAs<EnumConstantDecl>("enum_const_decl");
+ assert(EnumConstDecl);
+ const auto *Context = Result.Nodes.getNodeAs<Decl>("dc");
+ assert(Context && "Empty decl context.");
+ // FIXME: this would qualify "ns::VALUE" as "ns::EnumValue::VALUE". Fix it
+ // if it turns out to be an issue.
+ fixDeclRefExpr(Result, Context->getDeclContext(),
+ llvm::cast<NamedDecl>(EnumConstDecl), EnumConstRef);
+ } else if (const auto *FuncRef =
+ Result.Nodes.getNodeAs<DeclRefExpr>("func_ref")) {
+ // If this reference has been processed as a function call, we do not
+ // process it again.
+ if (ProcessedFuncRefs.count(FuncRef))
+ return;
+ ProcessedFuncRefs.insert(FuncRef);
+ const auto *Func = Result.Nodes.getNodeAs<FunctionDecl>("func_decl");
+ assert(Func);
+ const auto *Context = Result.Nodes.getNodeAs<Decl>("dc");
+ assert(Context && "Empty decl context.");
+ fixDeclRefExpr(Result, Context->getDeclContext(),
+ llvm::cast<NamedDecl>(Func), FuncRef);
+ } else {
+ const auto *Call = Result.Nodes.getNodeAs<CallExpr>("call");
+ assert(Call != nullptr && "Expecting callback for CallExpr.");
+ const auto *CalleeFuncRef =
+ llvm::cast<DeclRefExpr>(Call->getCallee()->IgnoreImplicit());
+ ProcessedFuncRefs.insert(CalleeFuncRef);
+ const FunctionDecl *Func = Call->getDirectCallee();
+ assert(Func != nullptr);
+ // FIXME: ignore overloaded operators. This would miss cases where operators
+ // are called by qualified names (i.e. "ns::operator <"). Ignore such
+ // cases for now.
+ if (Func->isOverloadedOperator())
+ return;
+ // Ignore out-of-line static methods since they will be handled by nested
+ // name specifiers.
+ if (Func->getCanonicalDecl()->getStorageClass() ==
+ StorageClass::SC_Static &&
+ Func->isOutOfLine())
+ return;
+ const auto *Context = Result.Nodes.getNodeAs<Decl>("dc");
+ assert(Context && "Empty decl context.");
+ SourceRange CalleeRange = Call->getCallee()->getSourceRange();
+ replaceQualifiedSymbolInDeclContext(
+ Result, Context->getDeclContext(), CalleeRange.getBegin(),
+ CalleeRange.getEnd(), llvm::cast<NamedDecl>(Func));
+ }
+}
+
+static SourceLocation getLocAfterNamespaceLBrace(const NamespaceDecl *NsDecl,
+ const SourceManager &SM,
+ const LangOptions &LangOpts) {
+ std::unique_ptr<Lexer> Lex =
+ getLexerStartingFromLoc(NsDecl->getBeginLoc(), SM, LangOpts);
+ assert(Lex.get() &&
+ "Failed to create lexer from the beginning of namespace.");
+ if (!Lex.get())
+ return SourceLocation();
+ Token Tok;
+ while (!Lex->LexFromRawLexer(Tok) && Tok.isNot(tok::TokenKind::l_brace)) {
+ }
+ return Tok.isNot(tok::TokenKind::l_brace)
+ ? SourceLocation()
+ : Tok.getEndLoc().getLocWithOffset(1);
+}
+
+// Stores information about a moved namespace in `MoveNamespaces` and leaves
+// the actual movement to `onEndOfTranslationUnit()`.
+void ChangeNamespaceTool::moveOldNamespace(
+ const ast_matchers::MatchFinder::MatchResult &Result,
+ const NamespaceDecl *NsDecl) {
+ // If the namespace is empty, do nothing.
+ if (Decl::castToDeclContext(NsDecl)->decls_empty())
+ return;
+
+ const SourceManager &SM = *Result.SourceManager;
+ // Get the range of the code in the old namespace.
+ SourceLocation Start =
+ getLocAfterNamespaceLBrace(NsDecl, SM, Result.Context->getLangOpts());
+ assert(Start.isValid() && "Can't find l_brace for namespace.");
+ MoveNamespace MoveNs;
+ MoveNs.Offset = SM.getFileOffset(Start);
+ // The range of the moved namespace is from the location just past the left
+ // brace to the location right before the right brace.
+ MoveNs.Length = SM.getFileOffset(NsDecl->getRBraceLoc()) - MoveNs.Offset;
+
+ // Insert the new namespace after `DiffOldNamespace`. For example, if
+ // `OldNamespace` is "a::b::c" and `NewNamespace` is `a::x::y`, then
+ // "x::y" will be inserted inside the existing namespace "a" and after "a::b".
+ // `OuterNs` is the first namespace in `DiffOldNamespace`, e.g. "namespace b"
+ // in the above example.
+ // If there is no outer namespace (i.e. DiffOldNamespace is empty), the new
+ // namespace will be a nested namespace in the old namespace.
+ const NamespaceDecl *OuterNs = getOuterNamespace(NsDecl, DiffOldNamespace);
+ SourceLocation InsertionLoc = Start;
+ if (OuterNs) {
+ SourceLocation LocAfterNs = getStartOfNextLine(
+ OuterNs->getRBraceLoc(), SM, Result.Context->getLangOpts());
+ assert(LocAfterNs.isValid() &&
+ "Failed to get location after DiffOldNamespace");
+ InsertionLoc = LocAfterNs;
+ }
+ MoveNs.InsertionOffset = SM.getFileOffset(SM.getSpellingLoc(InsertionLoc));
+ MoveNs.FID = SM.getFileID(Start);
+ MoveNs.SourceMgr = Result.SourceManager;
+ MoveNamespaces[SM.getFilename(Start)].push_back(MoveNs);
+}
+
+// Removes a class forward declaration from the code in the moved namespace and
+// creates an `InsertForwardDeclaration` to insert the forward declaration back
+// into the old namespace after moving code from the old namespace to the new
+// namespace.
+// For example, changing "a" to "x":
+// Old code:
+// namespace a {
+// class FWD;
+// class A { FWD *fwd; }
+// } // a
+// New code:
+// namespace a {
+// class FWD;
+// } // a
+// namespace x {
+// class A { a::FWD *fwd; }
+// } // x
+void ChangeNamespaceTool::moveClassForwardDeclaration(
+ const ast_matchers::MatchFinder::MatchResult &Result,
+ const NamedDecl *FwdDecl) {
+ SourceLocation Start = FwdDecl->getBeginLoc();
+ SourceLocation End = FwdDecl->getEndLoc();
+ const SourceManager &SM = *Result.SourceManager;
+ SourceLocation AfterSemi = Lexer::findLocationAfterToken(
+ End, tok::semi, SM, Result.Context->getLangOpts(),
+ /*SkipTrailingWhitespaceAndNewLine=*/true);
+ if (AfterSemi.isValid())
+ End = AfterSemi.getLocWithOffset(-1);
+ // Delete the forward declaration from the code to be moved.
+ addReplacementOrDie(Start, End, "", SM, &FileToReplacements);
+ llvm::StringRef Code = Lexer::getSourceText(
+ CharSourceRange::getTokenRange(SM.getSpellingLoc(Start),
+ SM.getSpellingLoc(End)),
+ SM, Result.Context->getLangOpts());
+ // Insert the forward declaration back into the old namespace after moving the
+ // code from old namespace to new namespace.
+ // Insertion information is stored in `InsertFwdDecls` and actual
+ // insertion will be performed in `onEndOfTranslationUnit`.
+ // Get the (old) namespace that contains the forward declaration.
+ const auto *NsDecl = Result.Nodes.getNodeAs<NamespaceDecl>("ns_decl");
+ // The namespace contains the forward declaration, so it must not be empty.
+ assert(!NsDecl->decls_empty());
+ const auto Insertion = createInsertion(
+ getLocAfterNamespaceLBrace(NsDecl, SM, Result.Context->getLangOpts()),
+ Code, SM);
+ InsertForwardDeclaration InsertFwd;
+ InsertFwd.InsertionOffset = Insertion.getOffset();
+ InsertFwd.ForwardDeclText = Insertion.getReplacementText().str();
+ InsertFwdDecls[Insertion.getFilePath()].push_back(InsertFwd);
+}
+
+// Replaces a qualified symbol (in \p DeclCtx) that refers to a declaration \p
+// FromDecl with the shortest qualified name possible when the reference is in
+// `NewNamespace`.
+void ChangeNamespaceTool::replaceQualifiedSymbolInDeclContext(
+ const ast_matchers::MatchFinder::MatchResult &Result,
+ const DeclContext *DeclCtx, SourceLocation Start, SourceLocation End,
+ const NamedDecl *FromDecl) {
+ const auto *NsDeclContext = DeclCtx->getEnclosingNamespaceContext();
+ if (llvm::isa<TranslationUnitDecl>(NsDeclContext)) {
+ // This should not happen in usual unless the TypeLoc is in function type
+ // parameters, e.g `std::function<void(T)>`. In this case, DeclContext of
+ // `T` will be the translation unit. We simply use fully-qualified name
+ // here.
+ // Note that `FromDecl` must not be defined in the old namespace (according
+ // to `DeclMatcher`), so its fully-qualified name will not change after
+ // changing the namespace.
+ addReplacementOrDie(Start, End, FromDecl->getQualifiedNameAsString(),
+ *Result.SourceManager, &FileToReplacements);
+ return;
+ }
+ const auto *NsDecl = llvm::cast<NamespaceDecl>(NsDeclContext);
+ // Calculate the name of the `NsDecl` after it is moved to new namespace.
+ std::string OldNs = NsDecl->getQualifiedNameAsString();
+ llvm::StringRef Postfix = OldNs;
+ bool Consumed = Postfix.consume_front(OldNamespace);
+ assert(Consumed && "Expect OldNS to start with OldNamespace.");
+ (void)Consumed;
+ const std::string NewNs = (NewNamespace + Postfix).str();
+
+ llvm::StringRef NestedName = Lexer::getSourceText(
+ CharSourceRange::getTokenRange(
+ Result.SourceManager->getSpellingLoc(Start),
+ Result.SourceManager->getSpellingLoc(End)),
+ *Result.SourceManager, Result.Context->getLangOpts());
+ std::string FromDeclName = FromDecl->getQualifiedNameAsString();
+ for (llvm::Regex &RE : WhiteListedSymbolRegexes)
+ if (RE.match(FromDeclName))
+ return;
+ std::string ReplaceName =
+ getShortestQualifiedNameInNamespace(FromDeclName, NewNs);
+ // Checks if there is any using namespace declarations that can shorten the
+ // qualified name.
+ for (const auto *UsingNamespace : UsingNamespaceDecls) {
+ if (!isDeclVisibleAtLocation(*Result.SourceManager, UsingNamespace, DeclCtx,
+ Start))
+ continue;
+ StringRef FromDeclNameRef = FromDeclName;
+ if (FromDeclNameRef.consume_front(UsingNamespace->getNominatedNamespace()
+ ->getQualifiedNameAsString())) {
+ FromDeclNameRef = FromDeclNameRef.drop_front(2);
+ if (FromDeclNameRef.size() < ReplaceName.size())
+ ReplaceName = FromDeclNameRef;
+ }
+ }
+ // Checks if there is any namespace alias declarations that can shorten the
+ // qualified name.
+ for (const auto *NamespaceAlias : NamespaceAliasDecls) {
+ if (!isDeclVisibleAtLocation(*Result.SourceManager, NamespaceAlias, DeclCtx,
+ Start))
+ continue;
+ StringRef FromDeclNameRef = FromDeclName;
+ if (FromDeclNameRef.consume_front(
+ NamespaceAlias->getNamespace()->getQualifiedNameAsString() +
+ "::")) {
+ std::string AliasName = NamespaceAlias->getNameAsString();
+ std::string AliasQualifiedName =
+ NamespaceAlias->getQualifiedNameAsString();
+ // We only consider namespace aliases define in the global namepspace or
+ // in namespaces that are directly visible from the reference, i.e.
+ // ancestor of the `OldNs`. Note that declarations in ancestor namespaces
+ // but not visible in the new namespace is filtered out by
+ // "IsVisibleInNewNs" matcher.
+ if (AliasQualifiedName != AliasName) {
+ // The alias is defined in some namespace.
+ assert(StringRef(AliasQualifiedName).endswith("::" + AliasName));
+ llvm::StringRef AliasNs =
+ StringRef(AliasQualifiedName).drop_back(AliasName.size() + 2);
+ if (!llvm::StringRef(OldNs).startswith(AliasNs))
+ continue;
+ }
+ std::string NameWithAliasNamespace =
+ (AliasName + "::" + FromDeclNameRef).str();
+ if (NameWithAliasNamespace.size() < ReplaceName.size())
+ ReplaceName = NameWithAliasNamespace;
+ }
+ }
+ // Checks if there is any using shadow declarations that can shorten the
+ // qualified name.
+ bool Matched = false;
+ for (const UsingDecl *Using : UsingDecls) {
+ if (Matched)
+ break;
+ if (isDeclVisibleAtLocation(*Result.SourceManager, Using, DeclCtx, Start)) {
+ for (const auto *UsingShadow : Using->shadows()) {
+ const auto *TargetDecl = UsingShadow->getTargetDecl();
+ if (TargetDecl->getQualifiedNameAsString() ==
+ FromDecl->getQualifiedNameAsString()) {
+ ReplaceName = FromDecl->getNameAsString();
+ Matched = true;
+ break;
+ }
+ }
+ }
+ }
+ bool Conflict = conflictInNamespace(DeclCtx->getParentASTContext(),
+ ReplaceName, NewNamespace);
+ // If the new nested name in the new namespace is the same as it was in the
+ // old namespace, we don't create replacement unless there can be ambiguity.
+ if ((NestedName == ReplaceName && !Conflict) ||
+ (NestedName.startswith("::") && NestedName.drop_front(2) == ReplaceName))
+ return;
+ // If the reference need to be fully-qualified, add a leading "::" unless
+ // NewNamespace is the global namespace.
+ if (ReplaceName == FromDeclName && !NewNamespace.empty() && Conflict)
+ ReplaceName = "::" + ReplaceName;
+ addReplacementOrDie(Start, End, ReplaceName, *Result.SourceManager,
+ &FileToReplacements);
+}
+
+// Replace the [Start, End] of `Type` with the shortest qualified name when the
+// `Type` is in `NewNamespace`.
+void ChangeNamespaceTool::fixTypeLoc(
+ const ast_matchers::MatchFinder::MatchResult &Result, SourceLocation Start,
+ SourceLocation End, TypeLoc Type) {
+ // FIXME: do not rename template parameter.
+ if (Start.isInvalid() || End.isInvalid())
+ return;
+ // Types of CXXCtorInitializers do not need to be fixed.
+ if (llvm::is_contained(BaseCtorInitializerTypeLocs, Type))
+ return;
+ if (isTemplateParameter(Type))
+ return;
+ // The declaration which this TypeLoc refers to.
+ const auto *FromDecl = Result.Nodes.getNodeAs<NamedDecl>("from_decl");
+ // `hasDeclaration` gives underlying declaration, but if the type is
+ // a typedef type, we need to use the typedef type instead.
+ auto IsInMovedNs = [&](const NamedDecl *D) {
+ if (!llvm::StringRef(D->getQualifiedNameAsString())
+ .startswith(OldNamespace + "::"))
+ return false;
+ auto ExpansionLoc = Result.SourceManager->getExpansionLoc(D->getBeginLoc());
+ if (ExpansionLoc.isInvalid())
+ return false;
+ llvm::StringRef Filename = Result.SourceManager->getFilename(ExpansionLoc);
+ return FilePatternRE.match(Filename);
+ };
+ // Make `FromDecl` the immediate declaration that `Type` refers to, i.e. if
+ // `Type` is an alias type, we make `FromDecl` the type alias declaration.
+ // Also, don't fix the \p Type if it refers to a type alias decl in the moved
+ // namespace since the alias decl will be moved along with the type reference.
+ if (auto *Typedef = Type.getType()->getAs<TypedefType>()) {
+ FromDecl = Typedef->getDecl();
+ if (IsInMovedNs(FromDecl))
+ return;
+ } else if (auto *TemplateType =
+ Type.getType()->getAs<TemplateSpecializationType>()) {
+ if (TemplateType->isTypeAlias()) {
+ FromDecl = TemplateType->getTemplateName().getAsTemplateDecl();
+ if (IsInMovedNs(FromDecl))
+ return;
+ }
+ }
+ const auto *DeclCtx = Result.Nodes.getNodeAs<Decl>("dc");
+ assert(DeclCtx && "Empty decl context.");
+ replaceQualifiedSymbolInDeclContext(Result, DeclCtx->getDeclContext(), Start,
+ End, FromDecl);
+}
+
+void ChangeNamespaceTool::fixUsingShadowDecl(
+ const ast_matchers::MatchFinder::MatchResult &Result,
+ const UsingDecl *UsingDeclaration) {
+ SourceLocation Start = UsingDeclaration->getBeginLoc();
+ SourceLocation End = UsingDeclaration->getEndLoc();
+ if (Start.isInvalid() || End.isInvalid())
+ return;
+
+ assert(UsingDeclaration->shadow_size() > 0);
+ // FIXME: it might not be always accurate to use the first using-decl.
+ const NamedDecl *TargetDecl =
+ UsingDeclaration->shadow_begin()->getTargetDecl();
+ std::string TargetDeclName = TargetDecl->getQualifiedNameAsString();
+ // FIXME: check if target_decl_name is in moved ns, which doesn't make much
+ // sense. If this happens, we need to use name with the new namespace.
+ // Use fully qualified name in UsingDecl for now.
+ addReplacementOrDie(Start, End, "using ::" + TargetDeclName,
+ *Result.SourceManager, &FileToReplacements);
+}
+
+void ChangeNamespaceTool::fixDeclRefExpr(
+ const ast_matchers::MatchFinder::MatchResult &Result,
+ const DeclContext *UseContext, const NamedDecl *From,
+ const DeclRefExpr *Ref) {
+ SourceRange RefRange = Ref->getSourceRange();
+ replaceQualifiedSymbolInDeclContext(Result, UseContext, RefRange.getBegin(),
+ RefRange.getEnd(), From);
+}
+
+void ChangeNamespaceTool::onEndOfTranslationUnit() {
+ // Move namespace blocks and insert forward declaration to old namespace.
+ for (const auto &FileAndNsMoves : MoveNamespaces) {
+ auto &NsMoves = FileAndNsMoves.second;
+ if (NsMoves.empty())
+ continue;
+ const std::string &FilePath = FileAndNsMoves.first;
+ auto &Replaces = FileToReplacements[FilePath];
+ auto &SM = *NsMoves.begin()->SourceMgr;
+ llvm::StringRef Code = SM.getBufferData(NsMoves.begin()->FID);
+ auto ChangedCode = tooling::applyAllReplacements(Code, Replaces);
+ if (!ChangedCode) {
+ llvm::errs() << llvm::toString(ChangedCode.takeError()) << "\n";
+ continue;
+ }
+ // Replacements on the changed code for moving namespaces and inserting
+ // forward declarations to old namespaces.
+ tooling::Replacements NewReplacements;
+ // Cut the changed code from the old namespace and paste the code in the new
+ // namespace.
+ for (const auto &NsMove : NsMoves) {
+ // Calculate the range of the old namespace block in the changed
+ // code.
+ const unsigned NewOffset = Replaces.getShiftedCodePosition(NsMove.Offset);
+ const unsigned NewLength =
+ Replaces.getShiftedCodePosition(NsMove.Offset + NsMove.Length) -
+ NewOffset;
+ tooling::Replacement Deletion(FilePath, NewOffset, NewLength, "");
+ std::string MovedCode = ChangedCode->substr(NewOffset, NewLength);
+ std::string MovedCodeWrappedInNewNs =
+ wrapCodeInNamespace(DiffNewNamespace, MovedCode);
+ // Calculate the new offset at which the code will be inserted in the
+ // changed code.
+ unsigned NewInsertionOffset =
+ Replaces.getShiftedCodePosition(NsMove.InsertionOffset);
+ tooling::Replacement Insertion(FilePath, NewInsertionOffset, 0,
+ MovedCodeWrappedInNewNs);
+ addOrMergeReplacement(Deletion, &NewReplacements);
+ addOrMergeReplacement(Insertion, &NewReplacements);
+ }
+ // After moving namespaces, insert forward declarations back to old
+ // namespaces.
+ const auto &FwdDeclInsertions = InsertFwdDecls[FilePath];
+ for (const auto &FwdDeclInsertion : FwdDeclInsertions) {
+ unsigned NewInsertionOffset =
+ Replaces.getShiftedCodePosition(FwdDeclInsertion.InsertionOffset);
+ tooling::Replacement Insertion(FilePath, NewInsertionOffset, 0,
+ FwdDeclInsertion.ForwardDeclText);
+ addOrMergeReplacement(Insertion, &NewReplacements);
+ }
+ // Add replacements referring to the changed code to existing replacements,
+ // which refers to the original code.
+ Replaces = Replaces.merge(NewReplacements);
+ auto Style =
+ format::getStyle(format::DefaultFormatStyle, FilePath, FallbackStyle);
+ if (!Style) {
+ llvm::errs() << llvm::toString(Style.takeError()) << "\n";
+ continue;
+ }
+ // Clean up old namespaces if there is nothing in it after moving.
+ auto CleanReplacements =
+ format::cleanupAroundReplacements(Code, Replaces, *Style);
+ if (!CleanReplacements) {
+ llvm::errs() << llvm::toString(CleanReplacements.takeError()) << "\n";
+ continue;
+ }
+ FileToReplacements[FilePath] = *CleanReplacements;
+ }
+
+ // Make sure we don't generate replacements for files that do not match
+ // FilePattern.
+ for (auto &Entry : FileToReplacements)
+ if (!FilePatternRE.match(Entry.first))
+ Entry.second.clear();
+}
+
+} // namespace change_namespace
+} // namespace clang