forked from OSchip/llvm-project
111 lines
3.9 KiB
C++
111 lines
3.9 KiB
C++
//===--- 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 "clang/Lex/Lexer.h"
|
|
#include <algorithm>
|
|
|
|
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) {
|
|
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
|