[clang-tdiy] Add header file extension configuration support.

Summary: * Add a `HeaderFileExtensions` check option in misc-definitions-in-headers, google-build-namespaces and google-global-names-in-headers.

Reviewers: aaron.ballman, alexfh

Subscribers: cfe-commits

Differential Revision: http://reviews.llvm.org/D16113

llvm-svn: 259879
This commit is contained in:
Haojian Wu 2016-02-05 11:23:59 +00:00
parent 5990cd5a0c
commit c2d7577cc5
11 changed files with 252 additions and 41 deletions

View File

@ -344,13 +344,25 @@ OptionsView::OptionsView(StringRef CheckName,
const ClangTidyOptions::OptionMap &CheckOptions)
: NamePrefix(CheckName.str() + "."), CheckOptions(CheckOptions) {}
std::string OptionsView::get(StringRef LocalName, std::string Default) const {
std::string OptionsView::get(StringRef LocalName, StringRef Default) const {
const auto &Iter = CheckOptions.find(NamePrefix + LocalName.str());
if (Iter != CheckOptions.end())
return Iter->second;
return Default;
}
std::string OptionsView::getLocalOrGlobal(StringRef LocalName,
StringRef Default) const {
auto Iter = CheckOptions.find(NamePrefix + LocalName.str());
if (Iter != CheckOptions.end())
return Iter->second;
// Fallback to global setting, if present.
Iter = CheckOptions.find(LocalName.str());
if (Iter != CheckOptions.end())
return Iter->second;
return Default;
}
void OptionsView::store(ClangTidyOptions::OptionMap &Options,
StringRef LocalName, StringRef Value) const {
Options[NamePrefix + LocalName.str()] = Value;

View File

@ -47,7 +47,15 @@ public:
/// Reads the option with the check-local name \p LocalName from the
/// \c CheckOptions. If the corresponding key is not present, returns
/// \p Default.
std::string get(StringRef LocalName, std::string Default) const;
std::string get(StringRef LocalName, StringRef Default) const;
/// \brief Read a named option from the \c Context.
///
/// Reads the option with the check-local name \p LocalName from local or
/// global \c CheckOptions. Gets local option first. If local is not
/// present, falls back to get global option. If global option is not present
/// either, returns Default.
std::string getLocalOrGlobal(StringRef LocalName, StringRef Default) const;
/// \brief Read a named option from the \c Context and parse it as an integral
/// type \c T.

View File

@ -20,6 +20,24 @@ namespace tidy {
namespace google {
namespace readability {
GlobalNamesInHeadersCheck::GlobalNamesInHeadersCheck(StringRef Name,
ClangTidyContext *Context)
: ClangTidyCheck(Name, Context),
RawStringHeaderFileExtensions(
Options.getLocalOrGlobal("HeaderFileExtensions", "h")) {
if (!utils::parseHeaderFileExtensions(RawStringHeaderFileExtensions,
HeaderFileExtensions,
',')) {
llvm::errs() << "Invalid header file extension: "
<< RawStringHeaderFileExtensions << "\n";
}
}
void GlobalNamesInHeadersCheck::storeOptions(
ClangTidyOptions::OptionMap &Opts) {
Options.store(Opts, "HeaderFileExtensions", RawStringHeaderFileExtensions);
}
void
GlobalNamesInHeadersCheck::registerMatchers(ast_matchers::MatchFinder *Finder) {
Finder->addMatcher(
@ -38,10 +56,8 @@ void GlobalNamesInHeadersCheck::check(const MatchFinder::MatchResult &Result) {
if (Result.SourceManager->isInMainFile(
Result.SourceManager->getExpansionLoc(D->getLocStart()))) {
// unless that file is a header.
StringRef Filename = Result.SourceManager->getFilename(
Result.SourceManager->getSpellingLoc(D->getLocStart()));
if (!Filename.endswith(".h"))
if (!utils::isSpellingLocInHeaderFile(
D->getLocStart(), *Result.SourceManager, HeaderFileExtensions))
return;
}

View File

@ -11,20 +11,32 @@
#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_GOOGLE_GLOBALNAMESINHEADERSCHECK_H
#include "../ClangTidy.h"
#include "../utils/HeaderFileExtensionsUtils.h"
namespace clang {
namespace tidy {
namespace google {
namespace readability {
// Flag global namespace pollution in header files.
// Right now it only triggers on using declarations and directives.
/// Flag global namespace pollution in header files.
/// Right now it only triggers on using declarations and directives.
///
/// The check supports these options:
/// - `HeaderFileExtensions`: a comma-separated list of filename extensions
/// of header files (The filename extensions should not contain "." prefix).
/// "h" by default.
/// For extension-less header files, using an empty string or leaving an
/// empty string between "," if there are other filename extensions.
class GlobalNamesInHeadersCheck : public ClangTidyCheck {
public:
GlobalNamesInHeadersCheck(StringRef Name, ClangTidyContext *Context)
: ClangTidyCheck(Name, Context) {}
GlobalNamesInHeadersCheck(StringRef Name, ClangTidyContext *Context);
void storeOptions(ClangTidyOptions::OptionMap &Opts) override;
void registerMatchers(ast_matchers::MatchFinder *Finder) override;
void check(const ast_matchers::MatchFinder::MatchResult &Result) override;
private:
const std::string RawStringHeaderFileExtensions;
utils::HeaderFileExtensionsSet HeaderFileExtensions;
};
} // namespace readability

View File

@ -19,6 +19,24 @@ namespace tidy {
namespace google {
namespace build {
UnnamedNamespaceInHeaderCheck::UnnamedNamespaceInHeaderCheck(
StringRef Name, ClangTidyContext *Context)
: ClangTidyCheck(Name, Context),
RawStringHeaderFileExtensions(
Options.getLocalOrGlobal("HeaderFileExtensions", "h,hh,hpp,hxx")) {
if (!utils::parseHeaderFileExtensions(RawStringHeaderFileExtensions,
HeaderFileExtensions,
',')) {
llvm::errs() << "Invalid header file extension: "
<< RawStringHeaderFileExtensions << "\n";
}
}
void UnnamedNamespaceInHeaderCheck::storeOptions(
ClangTidyOptions::OptionMap &Opts) {
Options.store(Opts, "HeaderFileExtensions", RawStringHeaderFileExtensions);
}
void UnnamedNamespaceInHeaderCheck::registerMatchers(
ast_matchers::MatchFinder *Finder) {
// Only register the matchers for C++; the functionality currently does not
@ -30,17 +48,13 @@ void UnnamedNamespaceInHeaderCheck::registerMatchers(
void
UnnamedNamespaceInHeaderCheck::check(const MatchFinder::MatchResult &Result) {
SourceManager *SM = Result.SourceManager;
const auto *N = Result.Nodes.getNodeAs<NamespaceDecl>("anonymousNamespace");
SourceLocation Loc = N->getLocStart();
if (!Loc.isValid())
return;
// Look if we're inside a header, check for common suffixes only.
// TODO: Allow configuring the set of file extensions.
StringRef FileName = SM->getPresumedLoc(Loc).getFilename();
if (FileName.endswith(".h") || FileName.endswith(".hh") ||
FileName.endswith(".hpp") || FileName.endswith(".hxx"))
if (utils::isPresumedLocInHeaderFile(Loc, *Result.SourceManager,
HeaderFileExtensions))
diag(Loc, "do not use unnamed namespaces in header files");
}

View File

@ -11,6 +11,7 @@
#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_GOOGLE_UNNAMEDNAMESPACEINHEADERCHECK_H
#include "../ClangTidy.h"
#include "../utils/HeaderFileExtensionsUtils.h"
namespace clang {
namespace tidy {
@ -19,15 +20,26 @@ namespace build {
/// Finds anonymous namespaces in headers.
///
/// The check supports these options:
/// - `HeaderFileExtensions`: a comma-separated list of filename extensions of
/// header files (The filename extensions should not contain "." prefix).
/// "h,hh,hpp,hxx" by default.
/// For extension-less header files, using an empty string or leaving an
/// empty string between "," if there are other filename extensions.
///
/// http://google-styleguide.googlecode.com/svn/trunk/cppguide.xml?showone=Namespaces#Namespaces
///
/// Corresponding cpplint.py check name: 'build/namespaces'.
class UnnamedNamespaceInHeaderCheck : public ClangTidyCheck {
public:
UnnamedNamespaceInHeaderCheck(StringRef Name, ClangTidyContext *Context)
: ClangTidyCheck(Name, Context) {}
UnnamedNamespaceInHeaderCheck(StringRef Name, ClangTidyContext *Context);
void storeOptions(ClangTidyOptions::OptionMap &Opts) override;
void registerMatchers(ast_matchers::MatchFinder *Finder) override;
void check(const ast_matchers::MatchFinder::MatchResult &Result) override;
private:
const std::string RawStringHeaderFileExtensions;
utils::HeaderFileExtensionsSet HeaderFileExtensions;
};
} // namespace build

View File

@ -19,38 +19,50 @@ namespace misc {
namespace {
AST_MATCHER(NamedDecl, isHeaderFileExtension) {
SourceManager& SM = Finder->getASTContext().getSourceManager();
SourceLocation ExpansionLoc = SM.getExpansionLoc(Node.getLocStart());
StringRef Filename = SM.getFilename(ExpansionLoc);
return Filename.endswith(".h") || Filename.endswith(".hh") ||
Filename.endswith(".hpp") || Filename.endswith(".hxx") ||
llvm::sys::path::extension(Filename).empty();
AST_MATCHER_P(NamedDecl, usesHeaderFileExtension,
utils::HeaderFileExtensionsSet, HeaderFileExtensions) {
return utils::isExpansionLocInHeaderFile(
Node.getLocStart(), Finder->getASTContext().getSourceManager(),
HeaderFileExtensions);
}
} // namespace
DefinitionsInHeadersCheck::DefinitionsInHeadersCheck(
StringRef Name, ClangTidyContext *Context)
: ClangTidyCheck(Name, Context),
UseHeaderFileExtension(Options.get("UseHeaderFileExtension", true)) {}
DefinitionsInHeadersCheck::DefinitionsInHeadersCheck(StringRef Name,
ClangTidyContext *Context)
: ClangTidyCheck(Name, Context),
UseHeaderFileExtension(Options.get("UseHeaderFileExtension", true)),
RawStringHeaderFileExtensions(
Options.getLocalOrGlobal("HeaderFileExtensions", ",h,hh,hpp,hxx")) {
if (!utils::parseHeaderFileExtensions(RawStringHeaderFileExtensions,
HeaderFileExtensions,
',')) {
// FIXME: Find a more suitable way to handle invalid configuration
// options.
llvm::errs() << "Invalid header file extension: "
<< RawStringHeaderFileExtensions << "\n";
}
}
void DefinitionsInHeadersCheck::storeOptions(
ClangTidyOptions::OptionMap &Opts) {
Options.store(Opts, "UseHeaderFileExtension", UseHeaderFileExtension);
Options.store(Opts, "HeaderFileExtensions", RawStringHeaderFileExtensions);
}
void DefinitionsInHeadersCheck::registerMatchers(MatchFinder *Finder) {
if (UseHeaderFileExtension) {
Finder->addMatcher(
namedDecl(anyOf(functionDecl(isDefinition()), varDecl(isDefinition())),
isHeaderFileExtension()).bind("name-decl"),
usesHeaderFileExtension(HeaderFileExtensions))
.bind("name-decl"),
this);
} else {
Finder->addMatcher(
namedDecl(anyOf(functionDecl(isDefinition()), varDecl(isDefinition())),
anyOf(isHeaderFileExtension(),
unless(isExpansionInMainFile()))).bind("name-decl"),
anyOf(usesHeaderFileExtension(HeaderFileExtensions),
unless(isExpansionInMainFile())))
.bind("name-decl"),
this);
}
}

View File

@ -11,20 +11,26 @@
#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_DEFINITIONS_IN_HEADERS_H
#include "../ClangTidy.h"
#include "../utils/HeaderFileExtensionsUtils.h"
namespace clang {
namespace tidy {
namespace misc {
// Finds non-extern non-inline function and variable definitions in header
// files, which can lead to potential ODR violations.
//
// There is one option:
// - `UseHeaderFileExtension`: Whether to use file extension (h, hh, hpp, hxx)
// to distinguish header files. True by default.
//
// For the user-facing documentation see:
// http://clang.llvm.org/extra/clang-tidy/checks/misc-definitions-in-headers.html
/// Finds non-extern non-inline function and variable definitions in header
/// files, which can lead to potential ODR violations.
///
/// The check supports these options:
/// - `UseHeaderFileExtension`: Whether to use file extension to distinguish
/// header files. True by default.
/// - `HeaderFileExtensions`: a comma-separated list of filename extensions of
/// header files (The filename extension should not contain "." prefix).
/// ",h,hh,hpp,hxx" by default.
/// For extension-less header files, using an empty string or leaving an
/// empty string between "," if there are other filename extensions.
///
/// For the user-facing documentation see:
/// http://clang.llvm.org/extra/clang-tidy/checks/misc-definitions-in-headers.html
class DefinitionsInHeadersCheck : public ClangTidyCheck {
public:
DefinitionsInHeadersCheck(StringRef Name, ClangTidyContext *Context);
@ -34,6 +40,8 @@ public:
private:
const bool UseHeaderFileExtension;
const std::string RawStringHeaderFileExtensions;
utils::HeaderFileExtensionsSet HeaderFileExtensions;
};
} // namespace misc

View File

@ -2,6 +2,7 @@ set(LLVM_LINK_COMPONENTS support)
add_clang_library(clangTidyUtils
HeaderGuard.cpp
HeaderFileExtensionsUtils.cpp
IncludeInserter.cpp
IncludeSorter.cpp
LexerUtils.cpp

View File

@ -0,0 +1,65 @@
//===--- HeaderFileExtensionsUtils.cpp - clang-tidy--------------*- C++ -*-===//
//
// The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
#include "HeaderFileExtensionsUtils.h"
#include "clang/Basic/CharInfo.h"
namespace clang {
namespace tidy {
namespace utils {
bool isExpansionLocInHeaderFile(
SourceLocation Loc, const SourceManager &SM,
const HeaderFileExtensionsSet &HeaderFileExtensions) {
SourceLocation ExpansionLoc = SM.getExpansionLoc(Loc);
StringRef FileExtension =
llvm::sys::path::extension(SM.getFilename(ExpansionLoc));
return HeaderFileExtensions.count(FileExtension.substr(1)) > 0;
}
bool isPresumedLocInHeaderFile(
SourceLocation Loc, SourceManager &SM,
const HeaderFileExtensionsSet &HeaderFileExtensions) {
PresumedLoc PresumedLocation = SM.getPresumedLoc(Loc);
StringRef FileExtension =
llvm::sys::path::extension(PresumedLocation.getFilename());
return HeaderFileExtensions.count(FileExtension.substr(1)) > 0;
}
bool isSpellingLocInHeaderFile(
SourceLocation Loc, SourceManager &SM,
const HeaderFileExtensionsSet &HeaderFileExtensions) {
SourceLocation SpellingLoc = SM.getSpellingLoc(Loc);
StringRef FileExtension =
llvm::sys::path::extension(SM.getFilename(SpellingLoc));
return HeaderFileExtensions.count(FileExtension.substr(1)) > 0;
}
bool parseHeaderFileExtensions(StringRef AllHeaderFileExtensions,
HeaderFileExtensionsSet &HeaderFileExtensions,
char delimiter) {
SmallVector<StringRef, 5> Suffixes;
AllHeaderFileExtensions.split(Suffixes, delimiter);
HeaderFileExtensions.clear();
for (StringRef Suffix : Suffixes) {
StringRef Extension = Suffix.trim();
for (StringRef::const_iterator it = Extension.begin();
it != Extension.end(); ++it) {
if (!isAlphanumeric(*it))
return false;
}
HeaderFileExtensions.insert(Extension);
}
return true;
}
} // namespace utils
} // namespace tidy
} // namespace clang

View File

@ -0,0 +1,51 @@
//===--- HeaderFileExtensionsUtils.h - clang-tidy----------------*- C++ -*-===//
//
// 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_UTILS_HEADER_FILE_EXTENSIONS_UTILS_H
#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_UTILS_HEADER_FILE_EXTENSIONS_UTILS_H
#include <string>
#include "clang/Basic/SourceLocation.h"
#include "clang/Basic/SourceManager.h"
#include "llvm/ADT/StringRef.h"
#include "llvm/ADT/SmallSet.h"
#include "llvm/Support/Path.h"
namespace clang {
namespace tidy {
namespace utils {
typedef llvm::SmallSet<llvm::StringRef, 5> HeaderFileExtensionsSet;
/// \brief Checks whether expansion location of Loc is in header file.
bool isExpansionLocInHeaderFile(
SourceLocation Loc, const SourceManager &SM,
const HeaderFileExtensionsSet &HeaderFileExtensions);
/// \brief Checks whether presumed location of Loc is in header file.
bool isPresumedLocInHeaderFile(
SourceLocation Loc, SourceManager &SM,
const HeaderFileExtensionsSet &HeaderFileExtensions);
/// \brief Checks whether spelling location of Loc is in header file.
bool isSpellingLocInHeaderFile(
SourceLocation Loc, SourceManager &SM,
const HeaderFileExtensionsSet &HeaderFileExtensions);
/// \brief Parses header file extensions from a semicolon-separated list.
bool parseHeaderFileExtensions(StringRef AllHeaderFileExtensions,
HeaderFileExtensionsSet &HeaderFileExtensions,
char delimiter);
} // namespace utils
} // namespace tidy
} // namespace clang
#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_UTILS_HEADER_FILE_EXTENSIONS_UTILS_H