Index: clang-tools-extra/trunk/clang-tidy/utils/ASTUtils.h =================================================================== --- clang-tools-extra/trunk/clang-tidy/utils/ASTUtils.h +++ clang-tools-extra/trunk/clang-tidy/utils/ASTUtils.h @@ -0,0 +1,25 @@ +//===---------- ASTUtils.h - clang-tidy ----------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_ASTUTILS_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_ASTUTILS_H + +#include "clang/AST/AST.h" + +namespace clang { +namespace tidy { +namespace utils { +// Returns the (closest) Function declaration surrounding |Statement| or NULL. +const FunctionDecl *getSurroundingFunction(ASTContext &Context, + const Stmt &Statement); +} // namespace utils +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_ASTUTILS_H Index: clang-tools-extra/trunk/clang-tidy/utils/ASTUtils.cpp =================================================================== --- clang-tools-extra/trunk/clang-tidy/utils/ASTUtils.cpp +++ clang-tools-extra/trunk/clang-tidy/utils/ASTUtils.cpp @@ -0,0 +1,28 @@ +//===---------- ASTUtils.cpp - clang-tidy ----------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "ASTUtils.h" + +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/ASTMatchers/ASTMatchers.h" + +namespace clang { +namespace tidy { +namespace utils { +using namespace ast_matchers; + +const FunctionDecl *getSurroundingFunction(ASTContext &Context, + const Stmt &Statement) { + return selectFirst( + "function", match(stmt(hasAncestor(functionDecl().bind("function"))), + Statement, Context)); +} +} // namespace utils +} // namespace tidy +} // namespace clang Index: clang-tools-extra/trunk/clang-tidy/utils/NamespaceAliaser.h =================================================================== --- clang-tools-extra/trunk/clang-tidy/utils/NamespaceAliaser.h +++ clang-tools-extra/trunk/clang-tidy/utils/NamespaceAliaser.h @@ -0,0 +1,52 @@ +//===---------- NamespaceAliaser.h - clang-tidy ---------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_NAMESPACEALIASER_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_NAMESPACEALIASER_H + +#include "clang/AST/ASTContext.h" +#include "clang/AST/Stmt.h" +#include "clang/Basic/Diagnostic.h" +#include "clang/Basic/SourceManager.h" +#include "llvm/ADT/DenseMap.h" +#include "llvm/ADT/StringMap.h" +#include + +namespace clang { +namespace tidy { +namespace utils { + +// This class creates function-level namespace aliases. +class NamespaceAliaser { +public: + explicit NamespaceAliaser(const SourceManager &SourceMgr); + // Adds a namespace alias for \p Namespace valid near \p + // Statement. Picks the first available name from \p Abbreviations. + // Returns ``llvm::None`` if an alias already exists or there is an error. + llvm::Optional + createAlias(ASTContext &Context, const Stmt &Statement, + llvm::StringRef Namespace, + const std::vector &Abbreviations); + + // Get an alias name for \p Namespace valid at \p Statement. Returns \p + // Namespace if there is no alias. + std::string getNamespaceName(ASTContext &Context, const Stmt &Statement, + llvm::StringRef Namespace) const; + +private: + const SourceManager &SourceMgr; + llvm::DenseMap> + AddedAliases; +}; + +} // namespace utils +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_NAMESPACEALIASER_H Index: clang-tools-extra/trunk/clang-tidy/utils/NamespaceAliaser.cpp =================================================================== --- clang-tools-extra/trunk/clang-tidy/utils/NamespaceAliaser.cpp +++ clang-tools-extra/trunk/clang-tidy/utils/NamespaceAliaser.cpp @@ -0,0 +1,99 @@ +//===---------- NamespaceAliaser.cpp - clang-tidy -------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "NamespaceAliaser.h" + +#include "ASTUtils.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/ASTMatchers/ASTMatchers.h" +#include "clang/Lex/Lexer.h" +namespace clang { +namespace tidy { +namespace utils { + +using namespace ast_matchers; + +NamespaceAliaser::NamespaceAliaser(const SourceManager &SourceMgr) + : SourceMgr(SourceMgr) {} + +AST_MATCHER_P(NamespaceAliasDecl, hasTargetNamespace, + ast_matchers::internal::Matcher, innerMatcher) { + return innerMatcher.matches(*Node.getNamespace(), Finder, Builder); +} + +Optional +NamespaceAliaser::createAlias(ASTContext &Context, const Stmt &Statement, + StringRef Namespace, + const std::vector &Abbreviations) { + const FunctionDecl *Function = getSurroundingFunction(Context, Statement); + if (!Function || !Function->hasBody()) + return None; + + + if (AddedAliases[Function].count(Namespace.str()) != 0) + return None; + + + // FIXME: Doesn't consider the order of declarations. + // If we accidentially pick an alias defined later in the function, + // the output won't compile. + // FIXME: Also doesn't consider file or class-scope aliases. + + const auto *ExistingAlias = selectFirst( + "alias", + match(functionDecl(hasBody(compoundStmt(has(declStmt( + has(namespaceAliasDecl(hasTargetNamespace(hasName(Namespace))) + .bind("alias"))))))), + *Function, Context)); + + if (ExistingAlias != nullptr) { + AddedAliases[Function][Namespace.str()] = ExistingAlias->getName().str(); + return None; + } + + for (const auto &Abbreviation : Abbreviations) { + DeclarationMatcher ConflictMatcher = namedDecl(hasName(Abbreviation)); + const auto HasConflictingChildren = + !match(findAll(ConflictMatcher), *Function, Context).empty(); + const auto HasConflictingAncestors = + !match(functionDecl(hasAncestor(decl(has(ConflictMatcher)))), *Function, + Context) + .empty(); + if (HasConflictingAncestors || HasConflictingChildren) + continue; + + std::string Declaration = + (llvm::Twine("\nnamespace ") + Abbreviation + " = " + Namespace + ";") + .str(); + SourceLocation Loc = + Lexer::getLocForEndOfToken(Function->getBody()->getLocStart(), 0, + SourceMgr, Context.getLangOpts()); + AddedAliases[Function][Namespace.str()] = Abbreviation; + return FixItHint::CreateInsertion(Loc, Declaration); + } + + return None; +} + +std::string NamespaceAliaser::getNamespaceName(ASTContext &Context, + const Stmt &Statement, + StringRef Namespace) const { + const auto *Function = getSurroundingFunction(Context, Statement); + auto FunctionAliases = AddedAliases.find(Function); + if (FunctionAliases != AddedAliases.end()) { + if (FunctionAliases->second.count(Namespace) != 0) { + return FunctionAliases->second.find(Namespace)->getValue(); + } + } + return Namespace.str(); +} + +} // namespace utils +} // namespace tidy +} // namespace clang Index: clang-tools-extra/trunk/clang-tidy/utils/UsingInserter.h =================================================================== --- clang-tools-extra/trunk/clang-tidy/utils/UsingInserter.h +++ clang-tools-extra/trunk/clang-tidy/utils/UsingInserter.h @@ -0,0 +1,50 @@ +//===---------- UsingInserter.h - clang-tidy ----------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_USINGINSERTER_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_USINGINSERTER_H + +#include "clang/AST/Decl.h" +#include "clang/AST/Stmt.h" +#include "clang/Basic/Diagnostic.h" +#include "clang/Basic/SourceManager.h" +#include + +namespace clang { +namespace tidy { +namespace utils { + +// UsingInserter adds using declarations for |QualifiedName| to the surrounding +// function. +// This allows using a shorter name without clobbering other scopes. +class UsingInserter { +public: + UsingInserter(const SourceManager &SourceMgr); + + // Creates a \p using declaration fixit. Returns ``llvm::None`` on error + // or if the using declaration already exists. + llvm::Optional + createUsingDeclaration(ASTContext &Context, const Stmt &Statement, + llvm::StringRef QualifiedName); + + // Returns the unqualified version of the name if there is an + // appropriate using declaration and the qualified name otherwise. + llvm::StringRef getShortName(ASTContext &Context, const Stmt &Statement, + llvm::StringRef QualifiedName); + +private: + typedef NameInFunction std::pair; + const SourceManager &SourceMgr; + std::set AddedUsing; +}; + +} // namespace utils +} // namespace tidy +} // namespace clang +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_USINGINSERTER_H Index: clang-tools-extra/trunk/clang-tidy/utils/UsingInserter.cpp =================================================================== --- clang-tools-extra/trunk/clang-tidy/utils/UsingInserter.cpp +++ clang-tools-extra/trunk/clang-tidy/utils/UsingInserter.cpp @@ -0,0 +1,88 @@ +//===---------- UsingInserter.cpp - clang-tidy ----------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "UsingInserter.h" + +#include "ASTUtils.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/ASTMatchers/ASTMatchers.h" +#include "clang/Lex/Lexer.h" + +namespace clang { +namespace tidy { +namespace utils { + +using namespace ast_matchers; + +static StringRef getUnqualifiedName(StringRef QualifiedName) { + size_t LastSeparatorPos = QualifiedName.rfind("::"); + if (LastSeparatorPos == StringRef::npos) + return QualifiedName; + return QualifiedName.drop_front(LastSeparatorPos + 2); +} + +UsingInserter::UsingInserter(const SourceManager &SourceMgr) + : SourceMgr(SourceMgr) {} + +Optional UsingInserter::createUsingDeclaration( + ASTContext &Context, const Stmt &Statement, StringRef QualifiedName) { + StringRef UnqualifiedName = getUnqualifiedName(QualifiedName); + const FunctionDecl *Function = getSurroundingFunction(Context, Statement); + if (!Function) + return None; + + if (AddedUsing.count(std::make_pair(Function, QualifiedName.str())) != 0) + return None; + + SourceLocation InsertLoc = Lexer::getLocForEndOfToken( + Function->getBody()->getLocStart(), 0, SourceMgr, Context.getLangOpts()); + + // Only use using declarations in the main file, not in includes. + if (SourceMgr.getFileID(InsertLoc) != SourceMgr.getMainFileID()) + return None; + + // FIXME: This declaration could be masked. Investigate if + // there is a way to avoid using Sema. + bool AlreadyHasUsingDecl = + !match(stmt(hasAncestor(decl(has(usingDecl(hasAnyUsingShadowDecl( + hasTargetDecl(hasName(QualifiedName.str())))))))), + Statement, Context) + .empty(); + if (AlreadyHasUsingDecl) { + AddedUsing.emplace(NameInFunction(Function, QualifiedName.str())); + return None; + } + // Find conflicting declarations and references. + auto ConflictingDecl = namedDecl(hasName(UnqualifiedName)); + bool HasConflictingDeclaration = + !match(findAll(ConflictingDecl), *Function, Context).empty(); + bool HasConflictingDeclRef = + !match(findAll(declRefExpr(to(ConflictingDecl))), *Function, Context) + .empty(); + if (HasConflictingDeclaration || HasConflictingDeclRef) + return None; + + std::string Declaration = (llvm::Twine("\nusing ") + QualifiedName + ";").str(); + + AddedUsing.emplace(std::make_pair(Function, QualifiedName.str())); + return FixItHint::CreateInsertion(InsertLoc, Declaration); +} + +StringRef UsingInserter::getShortName(ASTContext &Context, + const Stmt &Statement, + StringRef QualifiedName) { + const FunctionDecl *Function = getSurroundingFunction(Context, Statement); + if (AddedUsing.count(NameInFunction(Function, QualifiedName.str())) != 0) + return getUnqualifiedName(QualifiedName); + return QualifiedName; +} + +} // namespace utils +} // namespace tidy +} // namespace clang Index: clang-tools-extra/trunk/unittests/clang-tidy/NamespaceAliaserTest.cpp =================================================================== --- clang-tools-extra/trunk/unittests/clang-tidy/NamespaceAliaserTest.cpp +++ clang-tools-extra/trunk/unittests/clang-tidy/NamespaceAliaserTest.cpp @@ -0,0 +1,124 @@ +//===---- NamespaceAliaserTest.cpp - clang-tidy +//----------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "../clang-tidy/utils/NamespaceAliaser.h" + +#include "ClangTidyTest.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/ASTMatchers/ASTMatchers.h" +#include "gtest/gtest.h" + +namespace clang { +namespace tidy { +namespace utils { +// This checker is for testing only. It can only run on one test case +// (e.g. with one SourceManager). +class InsertAliasCheck : public ClangTidyCheck { +public: + using ClangTidyCheck::ClangTidyCheck; + void registerMatchers(ast_matchers::MatchFinder *Finder) override { + Finder->addMatcher(ast_matchers::callExpr().bind("foo"), this); + } + void + check(const ast_matchers::MatchFinder::MatchResult &Result) override { + if (!Aliaser) + Aliaser.reset(new NamespaceAliaser(*Result.SourceManager)); + + const CallExpr *Call = + Result.Nodes.getNodeAs("foo"); + assert(Call != nullptr && "Did not find node \"foo\""); + auto Hint = Aliaser->createAlias(*Result.Context, *Call, "::foo::bar", + {"b", "some_alias"}); + if (Hint.hasValue()) + diag(Call->getLocStart(), "Fix for testing") << Hint.getValue(); + + diag(Call->getLocStart(), "insert call") + << FixItHint::CreateInsertion( + Call->getLocStart(), + Aliaser->getNamespaceName(*Result.Context, *Call, "::foo::bar") + + "::"); + } + +private: + std::unique_ptr Aliaser; +}; + +template +std::string runChecker(StringRef Code, int ExpectedWarningCount) { + std::map AdditionalFileContents = {{"foo.h", + "namespace foo {\n" + "namespace bar {\n" + "}\n" + "void func() { }\n" + "}"}}; + std::vector errors; + + std::string result = + test::runCheckOnCode(Code, &errors, "foo.cc", None, + ClangTidyOptions(), AdditionalFileContents); + + EXPECT_EQ(ExpectedWarningCount, errors.size()); + return result; +} + +TEST(NamespaceAliaserTest, AddNewAlias) { + EXPECT_EQ("#include \"foo.h\"\n" + "void f() {\n" + "namespace b = ::foo::bar;" + " b::f(); }", + runChecker("#include \"foo.h\"\n" + "void f() { f(); }", + 2)); +} + +TEST(NamespaceAliaserTest, ReuseAlias) { + EXPECT_EQ( + "#include \"foo.h\"\n" + "void f() { namespace x = foo::bar; x::f(); }", + runChecker("#include \"foo.h\"\n" + "void f() { namespace x = foo::bar; f(); }", + 1)); +} + +TEST(NamespaceAliaserTest, AddsOnlyOneAlias) { + EXPECT_EQ("#include \"foo.h\"\n" + "void f() {\n" + "namespace b = ::foo::bar;" + " b::f(); b::f(); }", + runChecker("#include \"foo.h\"\n" + "void f() { f(); f(); }", + 3)); +} + +TEST(NamespaceAliaserTest, LocalConflict) { + EXPECT_EQ("#include \"foo.h\"\n" + "void f() {\n" + "namespace some_alias = ::foo::bar;" + " namespace b = foo; some_alias::f(); }", + runChecker("#include \"foo.h\"\n" + "void f() { namespace b = foo; f(); }", + 2)); +} + +TEST(NamespaceAliaserTest, GlobalConflict) { + EXPECT_EQ("#include \"foo.h\"\n" + "namespace b = foo;\n" + "void f() {\n" + "namespace some_alias = ::foo::bar;" + " some_alias::f(); }", + runChecker("#include \"foo.h\"\n" + "namespace b = foo;\n" + "void f() { f(); }", + 2)); +} + +} // namespace utils +} // namespace tidy +} // namespace clang Index: clang-tools-extra/trunk/unittests/clang-tidy/UsingInserterTest.cpp =================================================================== --- clang-tools-extra/trunk/unittests/clang-tidy/UsingInserterTest.cpp +++ clang-tools-extra/trunk/unittests/clang-tidy/UsingInserterTest.cpp @@ -0,0 +1,115 @@ +//===---- UsingInserterTest.cpp - clang-tidy ----------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "../clang-tidy/utils/UsingInserter.h" + +#include "ClangTidyTest.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/ASTMatchers/ASTMatchers.h" +#include "gtest/gtest.h" + +namespace clang { +namespace tidy { +namespace utils { + +// Replace all function calls with calls to foo::func. Inserts using +// declarations as necessary. This checker is for testing only. It +// can only run on one test case (e.g. wih one SourceManager). +class InsertUsingCheck : public clang::tidy::ClangTidyCheck { +public: + using clang::tidy::ClangTidyCheck::ClangTidyCheck; + void registerMatchers(clang::ast_matchers::MatchFinder *Finder) override { + Finder->addMatcher(clang::ast_matchers::callExpr().bind("foo"), this); + } + void + check(const clang::ast_matchers::MatchFinder::MatchResult &Result) override { + if (!Inserter) + Inserter.reset(new UsingInserter(*Result.SourceManager, + Result.Context->getLangOpts())); + + const clang::CallExpr *Call = + Result.Nodes.getNodeAs("foo"); + assert(Call != nullptr && "Did not find node \"foo\""); + auto Hint = + Inserter->createUsingDeclaration(*Result.Context, *Call, "::foo::func"); + + if (Hint.hasValue()) + diag(Call->getLocStart(), "Fix for testing") << Hint.getValue(); + + diag(Call->getLocStart(), "insert call") + << clang::FixItHint::CreateReplacement( + Call->getCallee()->getSourceRange(), + Inserter->getShortName(*Result.Context, *Call, "::foo::func")); + } + +private: + std::unique_ptr Inserter; +}; + +template +std::string runChecker(StringRef Code, int ExpectedWarningCount) { + std::map AdditionalFileContents = {{"foo.h", + "namespace foo {\n" + "namespace bar {\n" + "}\n" + "void func() { }\n" + "}"}}; + std::vector errors; + + std::string result = + test::runCheckOnCode(Code, &errors, "foo.cc", None, + ClangTidyOptions(), AdditionalFileContents); + + EXPECT_EQ(ExpectedWarningCount, errors.size()); + return result; +} + +TEST(UsingInserterTest, ReusesExisting) { + EXPECT_EQ("#include \"foo.h\"\n" + "namespace {" + "using ::foo::func;\n" + "void f() { func(); }" + "}", + runChecker("#include \"foo.h\"\n" + "namespace {" + "using ::foo::func;\n" + "void f() { f(); }" + "}", + 1)); +} + +TEST(UsingInserterTest, ReusesExistingGlobal) { + EXPECT_EQ("#include \"foo.h\"\n" + "using ::foo::func;\n" + "namespace {" + "void f() { func(); }" + "}", + runChecker("#include \"foo.h\"\n" + "using ::foo::func;\n" + "namespace {" + "void f() { f(); }" + "}", + 1)); +} + +TEST(UsingInserterTest, AvoidsConflict) { + EXPECT_EQ("#include \"foo.h\"\n" + "namespace {" + "void f() { int func; ::foo::func(); }" + "}", + runChecker("#include \"foo.h\"\n" + "namespace {" + "void f() { int func; f(); }" + "}", + 1)); +} + +} // namespace utils +} // namespace tidy +} // namespace clang