//===-- ChangeNamespace.h -- Change namespace  ------------------*- C++ -*-===//
//
// 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
//
//===----------------------------------------------------------------------===//

#ifndef LLVM_CLANG_TOOLS_EXTRA_CHANGE_NAMESPACE_CHANGENAMESPACE_H
#define LLVM_CLANG_TOOLS_EXTRA_CHANGE_NAMESPACE_CHANGENAMESPACE_H

#include "clang/ASTMatchers/ASTMatchFinder.h"
#include "clang/Format/Format.h"
#include "clang/Tooling/Core/Replacement.h"
#include "llvm/Support/Regex.h"
#include <string>

namespace clang {
namespace change_namespace {

// This tool can be used to change the surrounding namespaces of class/function
// definitions. Classes/functions in the moved namespace will have new
// namespaces while references to symbols (e.g. types, functions) which are not
// defined in the changed namespace will be correctly qualified by prepending
// namespace specifiers before them.
// This will try to add shortest namespace specifiers possible. When a symbol
// reference needs to be fully-qualified, this adds a "::" prefix to the
// namespace specifiers unless the new namespace is the global namespace.
// For classes, only classes that are declared/defined in the given namespace in
// specified files will be moved: forward declarations will remain in the old
// 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
// FIXME: support moving typedef, enums across namespaces.
class ChangeNamespaceTool : public ast_matchers::MatchFinder::MatchCallback {
public:
  // Moves code in the old namespace `OldNs` to the new namespace `NewNs` in
  // files matching `FilePattern`.
  ChangeNamespaceTool(
      llvm::StringRef OldNs, llvm::StringRef NewNs, llvm::StringRef FilePattern,
      llvm::ArrayRef<std::string> AllowedSymbolPatterns,
      std::map<std::string, tooling::Replacements> *FileToReplacements,
      llvm::StringRef FallbackStyle = "LLVM");

  void registerMatchers(ast_matchers::MatchFinder *Finder);

  void run(const ast_matchers::MatchFinder::MatchResult &Result) override;

  // Moves the changed code in old namespaces but leaves class forward
  // declarations behind.
  void onEndOfTranslationUnit() override;

private:
  void moveOldNamespace(const ast_matchers::MatchFinder::MatchResult &Result,
                        const NamespaceDecl *NsDecl);

  void moveClassForwardDeclaration(
      const ast_matchers::MatchFinder::MatchResult &Result,
      const NamedDecl *FwdDecl);

  void replaceQualifiedSymbolInDeclContext(
      const ast_matchers::MatchFinder::MatchResult &Result,
      const DeclContext *DeclContext, SourceLocation Start, SourceLocation End,
      const NamedDecl *FromDecl);

  void fixTypeLoc(const ast_matchers::MatchFinder::MatchResult &Result,
                  SourceLocation Start, SourceLocation End, TypeLoc Type);

  void fixUsingShadowDecl(const ast_matchers::MatchFinder::MatchResult &Result,
                          const UsingDecl *UsingDeclaration);

  void fixDeclRefExpr(const ast_matchers::MatchFinder::MatchResult &Result,
                      const DeclContext *UseContext, const NamedDecl *From,
                      const DeclRefExpr *Ref);

  // Information about moving an old namespace.
  struct MoveNamespace {
    // The start offset of the namespace block being moved in the original
    // code.
    unsigned Offset;
    // The length of the namespace block in the original code.
    unsigned Length;
    // The offset at which the new namespace block will be inserted in the
    // original code.
    unsigned InsertionOffset;
    // The file in which the namespace is declared.
    FileID FID;
    SourceManager *SourceMgr;
  };

  // Information about inserting a class forward declaration.
  struct InsertForwardDeclaration {
    // The offset at while the forward declaration will be inserted in the
    // original code.
    unsigned InsertionOffset;
    // The code to be inserted.
    std::string ForwardDeclText;
  };

  std::string FallbackStyle;
  // In match callbacks, this contains replacements for replacing `typeLoc`s in
  // and deleting forward declarations in the moved namespace blocks.
  // In `onEndOfTranslationUnit` callback, the previous added replacements are
  // applied (on the moved namespace blocks), and then changed code in old
  // namespaces re moved to new namespaces, and previously deleted forward
  // declarations are inserted back to old namespaces, from which they are
  // deleted.
  std::map<std::string, tooling::Replacements> &FileToReplacements;
  // A fully qualified name of the old namespace without "::" prefix, e.g.
  // "a::b::c".
  std::string OldNamespace;
  // A fully qualified name of the new namespace without "::" prefix, e.g.
  // "x::y::z".
  std::string NewNamespace;
  // The longest suffix in the old namespace that does not overlap the new
  // namespace.
  // For example, if `OldNamespace` is "a::b::c" and `NewNamespace` is
  // "a::x::y", then `DiffOldNamespace` will be "b::c".
  std::string DiffOldNamespace;
  // The longest suffix in the new namespace that does not overlap the old
  // namespace.
  // For example, if `OldNamespace` is "a::b::c" and `NewNamespace` is
  // "a::x::y", then `DiffNewNamespace` will be "x::y".
  std::string DiffNewNamespace;
  // A regex pattern that matches files to be processed.
  std::string FilePattern;
  llvm::Regex FilePatternRE;
  // Information about moved namespaces grouped by file.
  // Since we are modifying code in old namespaces (e.g. add namespace
  // specifiers) as well as moving them, we store information about namespaces
  // to be moved and only move them after all modifications are finished (i.e.
  // in `onEndOfTranslationUnit`).
  std::map<std::string, std::vector<MoveNamespace>> MoveNamespaces;
  // Information about forward declaration insertions grouped by files.
  // A class forward declaration is not moved, so it will be deleted from the
  // moved code block and inserted back into the old namespace. The insertion
  // will be done after removing the code from the old namespace and before
  // inserting it to the new namespace.
  std::map<std::string, std::vector<InsertForwardDeclaration>> InsertFwdDecls;
  // Records all using declarations, which can be used to shorten namespace
  // specifiers.
  llvm::SmallPtrSet<const UsingDecl *, 8> UsingDecls;
  // Records all using namespace declarations, which can be used to shorten
  // namespace specifiers.
  llvm::SmallPtrSet<const UsingDirectiveDecl *, 8> UsingNamespaceDecls;
  // Records all namespace alias declarations, which can be used to shorten
  // namespace specifiers.
  llvm::SmallPtrSet<const NamespaceAliasDecl *, 8> NamespaceAliasDecls;
  // TypeLocs of CXXCtorInitializer. Types of CXXCtorInitializers do not need to
  // be fixed.
  llvm::SmallVector<TypeLoc, 8> BaseCtorInitializerTypeLocs;
  // Since a DeclRefExpr for a function call can be matched twice (one as
  // CallExpr and one as DeclRefExpr), we record all DeclRefExpr's that have
  // been processed so that we don't handle them twice.
  llvm::SmallPtrSet<const clang::DeclRefExpr*, 16> ProcessedFuncRefs;
  // Patterns of symbol names whose references are not expected to be updated
  // when changing namespaces around them.
  std::vector<llvm::Regex> AllowedSymbolRegexes;
};

} // namespace change_namespace
} // namespace clang

#endif // LLVM_CLANG_TOOLS_EXTRA_CHANGE_NAMESPACE_CHANGENAMESPACE_H
