reference, declarationdefinition
definition → references, declarations, derived classes, virtual overrides
reference to multiple definitions → definitions
unreferenced
    1
    2
    3
    4
    5
    6
    7
    8
    9
   10
   11
   12
   13
   14
   15
   16
   17
   18
   19
   20
   21
   22
   23
   24
   25
   26
   27
   28
   29
   30
   31
   32
   33
   34
   35
   36
   37
   38
   39
   40
   41
   42
   43
   44
   45
   46
   47
   48
   49
   50
   51
   52
   53
   54
   55
   56
   57
   58
   59
   60
   61
   62
   63
   64
   65
   66
   67
   68
   69
   70
   71
   72
   73
   74
   75
   76
   77
   78
   79
   80
   81
   82
   83
   84
   85
   86
   87
   88
   89
   90
   91
   92
   93
   94
   95
   96
   97
   98
   99
  100
  101
  102
  103
  104
  105
  106
  107
  108
  109
  110
  111
  112
  113
//===--- ConcatNestedNamespacesCheck.cpp - clang-tidy----------------------===//
//
// 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 "ConcatNestedNamespacesCheck.h"
#include "clang/AST/ASTContext.h"
#include "clang/ASTMatchers/ASTMatchFinder.h"
#include <algorithm>
#include <iterator>

namespace clang {
namespace tidy {
namespace modernize {

static bool locationsInSameFile(const SourceManager &Sources,
                                SourceLocation Loc1, SourceLocation Loc2) {
  return Loc1.isFileID() && Loc2.isFileID() &&
         Sources.getFileID(Loc1) == Sources.getFileID(Loc2);
}

static bool anonymousOrInlineNamespace(const NamespaceDecl &ND) {
  return ND.isAnonymousNamespace() || ND.isInlineNamespace();
}

static bool singleNamedNamespaceChild(const NamespaceDecl &ND) {
  NamespaceDecl::decl_range Decls = ND.decls();
  if (std::distance(Decls.begin(), Decls.end()) != 1)
    return false;

  const auto *ChildNamespace = dyn_cast<const NamespaceDecl>(*Decls.begin());
  return ChildNamespace && !anonymousOrInlineNamespace(*ChildNamespace);
}

static bool alreadyConcatenated(std::size_t NumCandidates,
                                const SourceRange &ReplacementRange,
                                const SourceManager &Sources,
                                const LangOptions &LangOpts) {
  // FIXME: This logic breaks when there is a comment with ':'s in the middle.
  CharSourceRange TextRange =
      Lexer::getAsCharRange(ReplacementRange, Sources, LangOpts);
  StringRef CurrentNamespacesText =
      Lexer::getSourceText(TextRange, Sources, LangOpts);
  return CurrentNamespacesText.count(':') == (NumCandidates - 1) * 2;
}

ConcatNestedNamespacesCheck::NamespaceString
ConcatNestedNamespacesCheck::concatNamespaces() {
  NamespaceString Result("namespace ");
  Result.append(Namespaces.front()->getName());

  std::for_each(std::next(Namespaces.begin()), Namespaces.end(),
                [&Result](const NamespaceDecl *ND) {
                  Result.append("::");
                  Result.append(ND->getName());
                });

  return Result;
}

void ConcatNestedNamespacesCheck::registerMatchers(
    ast_matchers::MatchFinder *Finder) {
  if (!getLangOpts().CPlusPlus17)
    return;

  Finder->addMatcher(ast_matchers::namespaceDecl().bind("namespace"), this);
}

void ConcatNestedNamespacesCheck::reportDiagnostic(
    const SourceRange &FrontReplacement, const SourceRange &BackReplacement) {
  diag(Namespaces.front()->getBeginLoc(),
       "nested namespaces can be concatenated", DiagnosticIDs::Warning)
      << FixItHint::CreateReplacement(FrontReplacement, concatNamespaces())
      << FixItHint::CreateReplacement(BackReplacement, "}");
}

void ConcatNestedNamespacesCheck::check(
    const ast_matchers::MatchFinder::MatchResult &Result) {
  const NamespaceDecl &ND = *Result.Nodes.getNodeAs<NamespaceDecl>("namespace");
  const SourceManager &Sources = *Result.SourceManager;

  if (!locationsInSameFile(Sources, ND.getBeginLoc(), ND.getRBraceLoc()))
    return;

  if (!Sources.isInMainFile(ND.getBeginLoc()))
    return;

  if (anonymousOrInlineNamespace(ND))
    return;

  Namespaces.push_back(&ND);

  if (singleNamedNamespaceChild(ND))
    return;

  SourceRange FrontReplacement(Namespaces.front()->getBeginLoc(),
                               Namespaces.back()->getLocation());
  SourceRange BackReplacement(Namespaces.back()->getRBraceLoc(),
                              Namespaces.front()->getRBraceLoc());

  if (!alreadyConcatenated(Namespaces.size(), FrontReplacement, Sources,
                           getLangOpts()))
    reportDiagnostic(FrontReplacement, BackReplacement);

  Namespaces.clear();
}

} // namespace modernize
} // namespace tidy
} // namespace clang