mirror of
https://github.com/capstone-engine/llvm-capstone.git
synced 2024-11-23 22:00:10 +00:00
[clang-tidy] Implement an include-cleaner check.
Differential Revision: https://reviews.llvm.org/D148793
This commit is contained in:
parent
f9753ef189
commit
c28506ba4b
@ -7,6 +7,7 @@ setup_host_tool(clang-tidy-confusable-chars-gen CLANG_TIDY_CONFUSABLE_CHARS_GEN
|
||||
|
||||
add_subdirectory(ConfusableTable)
|
||||
|
||||
include_directories(BEFORE "${CMAKE_CURRENT_SOURCE_DIR}/../../include-cleaner/include")
|
||||
|
||||
add_custom_command(
|
||||
OUTPUT Confusables.inc
|
||||
@ -19,6 +20,7 @@ add_clang_library(clangTidyMiscModule
|
||||
ConstCorrectnessCheck.cpp
|
||||
DefinitionsInHeadersCheck.cpp
|
||||
ConfusableIdentifierCheck.cpp
|
||||
IncludeCleanerCheck.cpp
|
||||
MiscTidyModule.cpp
|
||||
MisleadingBidirectional.cpp
|
||||
MisleadingIdentifier.cpp
|
||||
@ -53,6 +55,7 @@ clang_target_link_libraries(clangTidyMiscModule
|
||||
clangAST
|
||||
clangASTMatchers
|
||||
clangBasic
|
||||
clangIncludeCleaner
|
||||
clangLex
|
||||
clangSerialization
|
||||
clangTooling
|
||||
|
202
clang-tools-extra/clang-tidy/misc/IncludeCleanerCheck.cpp
Normal file
202
clang-tools-extra/clang-tidy/misc/IncludeCleanerCheck.cpp
Normal file
@ -0,0 +1,202 @@
|
||||
//===--- IncludeCleanerCheck.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 "IncludeCleanerCheck.h"
|
||||
#include "../ClangTidyCheck.h"
|
||||
#include "../ClangTidyDiagnosticConsumer.h"
|
||||
#include "../ClangTidyOptions.h"
|
||||
#include "../utils/OptionsUtils.h"
|
||||
#include "clang-include-cleaner/Analysis.h"
|
||||
#include "clang-include-cleaner/Record.h"
|
||||
#include "clang-include-cleaner/Types.h"
|
||||
#include "clang/AST/ASTContext.h"
|
||||
#include "clang/AST/Decl.h"
|
||||
#include "clang/AST/DeclBase.h"
|
||||
#include "clang/ASTMatchers/ASTMatchFinder.h"
|
||||
#include "clang/ASTMatchers/ASTMatchers.h"
|
||||
#include "clang/Basic/Diagnostic.h"
|
||||
#include "clang/Basic/FileEntry.h"
|
||||
#include "clang/Basic/LLVM.h"
|
||||
#include "clang/Basic/LangOptions.h"
|
||||
#include "clang/Basic/SourceLocation.h"
|
||||
#include "clang/Format/Format.h"
|
||||
#include "clang/Lex/Preprocessor.h"
|
||||
#include "clang/Tooling/Core/Replacement.h"
|
||||
#include "clang/Tooling/Inclusions/HeaderIncludes.h"
|
||||
#include "llvm/ADT/DenseSet.h"
|
||||
#include "llvm/ADT/STLExtras.h"
|
||||
#include "llvm/ADT/SmallVector.h"
|
||||
#include "llvm/ADT/StringRef.h"
|
||||
#include "llvm/Support/ErrorHandling.h"
|
||||
#include "llvm/Support/Regex.h"
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
using namespace clang::ast_matchers;
|
||||
|
||||
namespace clang::tidy::misc {
|
||||
|
||||
namespace {
|
||||
struct MissingIncludeInfo {
|
||||
SourceLocation SymRefLocation;
|
||||
include_cleaner::Header Missing;
|
||||
};
|
||||
} // namespace
|
||||
|
||||
IncludeCleanerCheck::IncludeCleanerCheck(StringRef Name,
|
||||
ClangTidyContext *Context)
|
||||
: ClangTidyCheck(Name, Context),
|
||||
IgnoreHeaders(utils::options::parseStringList(
|
||||
Options.getLocalOrGlobal("IgnoreHeaders", ""))) {
|
||||
for (const auto &Header : IgnoreHeaders) {
|
||||
if (!llvm::Regex{Header}.isValid())
|
||||
configurationDiag("Invalid ignore headers regex '%0'") << Header;
|
||||
std::string HeaderSuffix{Header.str()};
|
||||
if (!Header.ends_with("$"))
|
||||
HeaderSuffix += "$";
|
||||
IgnoreHeadersRegex.emplace_back(HeaderSuffix);
|
||||
}
|
||||
}
|
||||
|
||||
void IncludeCleanerCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) {
|
||||
Options.store(Opts, "IgnoreHeaders",
|
||||
utils::options::serializeStringList(IgnoreHeaders));
|
||||
}
|
||||
|
||||
bool IncludeCleanerCheck::isLanguageVersionSupported(
|
||||
const LangOptions &LangOpts) const {
|
||||
return !LangOpts.ObjC;
|
||||
}
|
||||
|
||||
void IncludeCleanerCheck::registerMatchers(MatchFinder *Finder) {
|
||||
Finder->addMatcher(translationUnitDecl().bind("top"), this);
|
||||
}
|
||||
|
||||
void IncludeCleanerCheck::registerPPCallbacks(const SourceManager &SM,
|
||||
Preprocessor *PP,
|
||||
Preprocessor *ModuleExpanderPP) {
|
||||
PP->addPPCallbacks(RecordedPreprocessor.record(*PP));
|
||||
HS = &PP->getHeaderSearchInfo();
|
||||
RecordedPI.record(*PP);
|
||||
}
|
||||
|
||||
bool IncludeCleanerCheck::shouldIgnore(const include_cleaner::Header &H) {
|
||||
return llvm::any_of(IgnoreHeadersRegex, [&H](const llvm::Regex &R) {
|
||||
switch (H.kind()) {
|
||||
case include_cleaner::Header::Standard:
|
||||
return R.match(H.standard().name());
|
||||
case include_cleaner::Header::Verbatim:
|
||||
return R.match(H.verbatim());
|
||||
case include_cleaner::Header::Physical:
|
||||
return R.match(H.physical()->tryGetRealPathName());
|
||||
}
|
||||
llvm_unreachable("Unknown Header kind.");
|
||||
});
|
||||
}
|
||||
|
||||
void IncludeCleanerCheck::check(const MatchFinder::MatchResult &Result) {
|
||||
const SourceManager *SM = Result.SourceManager;
|
||||
const FileEntry *MainFile = SM->getFileEntryForID(SM->getMainFileID());
|
||||
llvm::DenseSet<const include_cleaner::Include *> Used;
|
||||
std::vector<MissingIncludeInfo> Missing;
|
||||
llvm::SmallVector<Decl *> MainFileDecls;
|
||||
for (Decl *D : Result.Nodes.getNodeAs<TranslationUnitDecl>("top")->decls()) {
|
||||
if (!SM->isWrittenInMainFile(SM->getExpansionLoc(D->getLocation())))
|
||||
continue;
|
||||
// FIXME: Filter out implicit template specializations.
|
||||
MainFileDecls.push_back(D);
|
||||
}
|
||||
// FIXME: Find a way to have less code duplication between include-cleaner
|
||||
// analysis implementation and the below code.
|
||||
walkUsed(MainFileDecls, RecordedPreprocessor.MacroReferences, &RecordedPI,
|
||||
*SM,
|
||||
[&](const include_cleaner::SymbolReference &Ref,
|
||||
llvm::ArrayRef<include_cleaner::Header> Providers) {
|
||||
bool Satisfied = false;
|
||||
for (const include_cleaner::Header &H : Providers) {
|
||||
if (H.kind() == include_cleaner::Header::Physical &&
|
||||
H.physical() == MainFile)
|
||||
Satisfied = true;
|
||||
|
||||
for (const include_cleaner::Include *I :
|
||||
RecordedPreprocessor.Includes.match(H)) {
|
||||
Used.insert(I);
|
||||
Satisfied = true;
|
||||
}
|
||||
}
|
||||
if (!Satisfied && !Providers.empty() &&
|
||||
Ref.RT == include_cleaner::RefType::Explicit &&
|
||||
!shouldIgnore(Providers.front()))
|
||||
Missing.push_back({Ref.RefLocation, Providers.front()});
|
||||
});
|
||||
|
||||
std::vector<const include_cleaner::Include *> Unused;
|
||||
for (const include_cleaner::Include &I :
|
||||
RecordedPreprocessor.Includes.all()) {
|
||||
if (Used.contains(&I) || !I.Resolved)
|
||||
continue;
|
||||
if (RecordedPI.shouldKeep(I.Line))
|
||||
continue;
|
||||
// Check if main file is the public interface for a private header. If so
|
||||
// we shouldn't diagnose it as unused.
|
||||
if (auto PHeader = RecordedPI.getPublic(I.Resolved); !PHeader.empty()) {
|
||||
PHeader = PHeader.trim("<>\"");
|
||||
// Since most private -> public mappings happen in a verbatim way, we
|
||||
// check textually here. This might go wrong in presence of symlinks or
|
||||
// header mappings. But that's not different than rest of the places.
|
||||
if (getCurrentMainFile().endswith(PHeader))
|
||||
continue;
|
||||
}
|
||||
|
||||
if (llvm::none_of(IgnoreHeadersRegex,
|
||||
[Resolved = I.Resolved->tryGetRealPathName()](
|
||||
const llvm::Regex &R) { return R.match(Resolved); }))
|
||||
Unused.push_back(&I);
|
||||
}
|
||||
|
||||
llvm::StringRef Code = SM->getBufferData(SM->getMainFileID());
|
||||
auto FileStyle =
|
||||
format::getStyle(format::DefaultFormatStyle, getCurrentMainFile(),
|
||||
format::DefaultFallbackStyle, Code,
|
||||
&SM->getFileManager().getVirtualFileSystem());
|
||||
if (!FileStyle)
|
||||
FileStyle = format::getLLVMStyle();
|
||||
|
||||
for (const auto *Inc : Unused) {
|
||||
diag(Inc->HashLocation, "included header %0 is not used directly")
|
||||
<< Inc->quote()
|
||||
<< FixItHint::CreateRemoval(CharSourceRange::getCharRange(
|
||||
SM->translateLineCol(SM->getMainFileID(), Inc->Line, 1),
|
||||
SM->translateLineCol(SM->getMainFileID(), Inc->Line + 1, 1)));
|
||||
}
|
||||
|
||||
tooling::HeaderIncludes HeaderIncludes(getCurrentMainFile(), Code,
|
||||
FileStyle->IncludeStyle);
|
||||
for (const auto &Inc : Missing) {
|
||||
std::string Spelling =
|
||||
include_cleaner::spellHeader(Inc.Missing, *HS, MainFile);
|
||||
bool Angled = llvm::StringRef{Spelling}.starts_with("<");
|
||||
// We might suggest insertion of an existing include in edge cases, e.g.,
|
||||
// include is present in a PP-disabled region, or spelling of the header
|
||||
// turns out to be the same as one of the unresolved includes in the
|
||||
// main file.
|
||||
if (auto Replacement =
|
||||
HeaderIncludes.insert(llvm::StringRef{Spelling}.trim("\"<>"),
|
||||
Angled, tooling::IncludeDirective::Include))
|
||||
diag(SM->getSpellingLoc(Inc.SymRefLocation),
|
||||
"no header providing %0 is directly included")
|
||||
<< Spelling
|
||||
<< FixItHint::CreateInsertion(
|
||||
SM->getComposedLoc(SM->getMainFileID(),
|
||||
Replacement->getOffset()),
|
||||
Replacement->getReplacementText());
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace clang::tidy::misc
|
53
clang-tools-extra/clang-tidy/misc/IncludeCleanerCheck.h
Normal file
53
clang-tools-extra/clang-tidy/misc/IncludeCleanerCheck.h
Normal file
@ -0,0 +1,53 @@
|
||||
//===--- IncludeCleanerCheck.h - clang-tidy ---------------------*- 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_CLANG_TIDY_MISC_INCLUDECLEANER_H
|
||||
#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_INCLUDECLEANER_H
|
||||
|
||||
#include "../ClangTidyCheck.h"
|
||||
#include "../ClangTidyDiagnosticConsumer.h"
|
||||
#include "../ClangTidyOptions.h"
|
||||
#include "clang-include-cleaner/Record.h"
|
||||
#include "clang-include-cleaner/Types.h"
|
||||
#include "clang/ASTMatchers/ASTMatchFinder.h"
|
||||
#include "clang/Basic/LLVM.h"
|
||||
#include "clang/Basic/SourceLocation.h"
|
||||
#include "clang/Lex/HeaderSearch.h"
|
||||
#include "llvm/Support/Regex.h"
|
||||
#include <vector>
|
||||
|
||||
namespace clang::tidy::misc {
|
||||
|
||||
/// Checks for unused and missing includes. Generates findings only for
|
||||
/// the main file of a translation unit.
|
||||
/// Findings correspond to https://clangd.llvm.org/design/include-cleaner.
|
||||
///
|
||||
/// For the user-facing documentation see:
|
||||
/// http://clang.llvm.org/extra/clang-tidy/checks/misc/include-cleaner.html
|
||||
class IncludeCleanerCheck : public ClangTidyCheck {
|
||||
public:
|
||||
IncludeCleanerCheck(StringRef Name, ClangTidyContext *Context);
|
||||
void registerMatchers(ast_matchers::MatchFinder *Finder) override;
|
||||
void check(const ast_matchers::MatchFinder::MatchResult &Result) override;
|
||||
void registerPPCallbacks(const SourceManager &SM, Preprocessor *PP,
|
||||
Preprocessor *ModuleExpanderPP) override;
|
||||
void storeOptions(ClangTidyOptions::OptionMap &Opts) override;
|
||||
bool isLanguageVersionSupported(const LangOptions &LangOpts) const override;
|
||||
|
||||
private:
|
||||
include_cleaner::RecordedPP RecordedPreprocessor;
|
||||
include_cleaner::PragmaIncludes RecordedPI;
|
||||
HeaderSearch *HS;
|
||||
std::vector<StringRef> IgnoreHeaders;
|
||||
llvm::SmallVector<llvm::Regex> IgnoreHeadersRegex;
|
||||
bool shouldIgnore(const include_cleaner::Header &H);
|
||||
};
|
||||
|
||||
} // namespace clang::tidy::misc
|
||||
|
||||
#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_INCLUDECLEANER_H
|
@ -12,6 +12,7 @@
|
||||
#include "ConfusableIdentifierCheck.h"
|
||||
#include "ConstCorrectnessCheck.h"
|
||||
#include "DefinitionsInHeadersCheck.h"
|
||||
#include "IncludeCleanerCheck.h"
|
||||
#include "MisleadingBidirectional.h"
|
||||
#include "MisleadingIdentifier.h"
|
||||
#include "MisplacedConstCheck.h"
|
||||
@ -41,6 +42,7 @@ public:
|
||||
"misc-const-correctness");
|
||||
CheckFactories.registerCheck<DefinitionsInHeadersCheck>(
|
||||
"misc-definitions-in-headers");
|
||||
CheckFactories.registerCheck<IncludeCleanerCheck>("misc-include-cleaner");
|
||||
CheckFactories.registerCheck<MisleadingBidirectionalCheck>(
|
||||
"misc-misleading-bidirectional");
|
||||
CheckFactories.registerCheck<MisleadingIdentifierCheck>(
|
||||
|
@ -196,32 +196,35 @@ TidyProvider addTidyChecks(llvm::StringRef Checks,
|
||||
|
||||
TidyProvider disableUnusableChecks(llvm::ArrayRef<std::string> ExtraBadChecks) {
|
||||
constexpr llvm::StringLiteral Seperator(",");
|
||||
static const std::string BadChecks =
|
||||
llvm::join_items(Seperator,
|
||||
// We want this list to start with a seperator to
|
||||
// simplify appending in the lambda. So including an
|
||||
// empty string here will force that.
|
||||
"",
|
||||
// ----- False Positives -----
|
||||
static const std::string BadChecks = llvm::join_items(
|
||||
Seperator,
|
||||
// We want this list to start with a seperator to
|
||||
// simplify appending in the lambda. So including an
|
||||
// empty string here will force that.
|
||||
"",
|
||||
// include-cleaner is directly integrated in IncludeCleaner.cpp
|
||||
"-misc-include-cleaner",
|
||||
|
||||
// Check relies on seeing ifndef/define/endif directives,
|
||||
// clangd doesn't replay those when using a preamble.
|
||||
"-llvm-header-guard", "-modernize-macro-to-enum",
|
||||
// ----- False Positives -----
|
||||
|
||||
// ----- Crashing Checks -----
|
||||
// Check relies on seeing ifndef/define/endif directives,
|
||||
// clangd doesn't replay those when using a preamble.
|
||||
"-llvm-header-guard", "-modernize-macro-to-enum",
|
||||
|
||||
// Check can choke on invalid (intermediate) c++
|
||||
// code, which is often the case when clangd
|
||||
// tries to build an AST.
|
||||
"-bugprone-use-after-move",
|
||||
// Alias for bugprone-use-after-move.
|
||||
"-hicpp-invalid-access-moved",
|
||||
// ----- Crashing Checks -----
|
||||
|
||||
// ----- Performance problems -----
|
||||
// Check can choke on invalid (intermediate) c++
|
||||
// code, which is often the case when clangd
|
||||
// tries to build an AST.
|
||||
"-bugprone-use-after-move",
|
||||
// Alias for bugprone-use-after-move.
|
||||
"-hicpp-invalid-access-moved",
|
||||
|
||||
// This check runs expensive analysis for each variable.
|
||||
// It has been observed to increase reparse time by 10x.
|
||||
"-misc-const-correctness");
|
||||
// ----- Performance problems -----
|
||||
|
||||
// This check runs expensive analysis for each variable.
|
||||
// It has been observed to increase reparse time by 10x.
|
||||
"-misc-const-correctness");
|
||||
|
||||
size_t Size = BadChecks.size();
|
||||
for (const std::string &Str : ExtraBadChecks) {
|
||||
|
@ -165,6 +165,11 @@ New checks
|
||||
Checks that all implicit and explicit inline functions in header files are
|
||||
tagged with the ``LIBC_INLINE`` macro.
|
||||
|
||||
- New :doc:`misc-include-cleaner
|
||||
<clang-tidy/checks/misc/include-cleaner>` check.
|
||||
|
||||
Checks for unused and missing includes.
|
||||
|
||||
- New :doc:`modernize-type-traits
|
||||
<clang-tidy/checks/modernize/type-traits>` check.
|
||||
|
||||
|
@ -255,6 +255,7 @@ Clang-Tidy Checks
|
||||
`misc-confusable-identifiers <misc/confusable-identifiers.html>`_,
|
||||
`misc-const-correctness <misc/const-correctness.html>`_, "Yes"
|
||||
`misc-definitions-in-headers <misc/definitions-in-headers.html>`_, "Yes"
|
||||
`misc-include-cleaner <misc/include-cleaner.html>`_, "Yes"
|
||||
`misc-misleading-bidirectional <misc/misleading-bidirectional.html>`_,
|
||||
`misc-misleading-identifier <misc/misleading-identifier.html>`_,
|
||||
`misc-misplaced-const <misc/misplaced-const.html>`_,
|
||||
|
@ -0,0 +1,34 @@
|
||||
.. title:: clang-tidy - misc-include-cleaner
|
||||
|
||||
misc-include-cleaner
|
||||
====================
|
||||
|
||||
Checks for unused and missing includes. Generates findings only for
|
||||
the main file of a translation unit.
|
||||
Findings correspond to https://clangd.llvm.org/design/include-cleaner.
|
||||
|
||||
Example:
|
||||
|
||||
.. code-block:: c++
|
||||
// foo.h
|
||||
class Foo{};
|
||||
// bar.h
|
||||
#include "baz.h"
|
||||
class Bar{};
|
||||
// baz.h
|
||||
class Baz{};
|
||||
// main.cc
|
||||
#include "bar.h" // OK: uses class Bar from bar.h
|
||||
#include "foo.h" // warning: unused include "foo.h"
|
||||
Bar bar;
|
||||
Baz baz; // warning: missing include "baz.h"
|
||||
|
||||
Options
|
||||
-------
|
||||
|
||||
.. option:: IgnoreHeaders
|
||||
|
||||
A semicolon-separated list of regexes to disable insertion/removal of header
|
||||
files that match this regex as a suffix. E.g., `foo/.*` disables
|
||||
insertion/removal for all headers under the directory `foo`. By default, no
|
||||
headers will be ignored.
|
@ -18,6 +18,7 @@
|
||||
#define CLANG_INCLUDE_CLEANER_RECORD_H
|
||||
|
||||
#include "clang-include-cleaner/Types.h"
|
||||
#include "clang/Basic/SourceLocation.h"
|
||||
#include "llvm/ADT/DenseMap.h"
|
||||
#include "llvm/ADT/DenseSet.h"
|
||||
#include "llvm/ADT/SmallVector.h"
|
||||
@ -52,6 +53,10 @@ public:
|
||||
/// to the structure.
|
||||
void record(const CompilerInstance &CI);
|
||||
|
||||
/// Installs an analysing PPCallback and CommentHandler and populates results
|
||||
/// to the structure.
|
||||
void record(Preprocessor &P);
|
||||
|
||||
/// Returns true if the given #include of the main-file should never be
|
||||
/// removed.
|
||||
bool shouldKeep(unsigned HashLineNumber) const {
|
||||
|
@ -19,6 +19,8 @@
|
||||
#include "clang/Lex/Preprocessor.h"
|
||||
#include "clang/Tooling/Inclusions/HeaderAnalysis.h"
|
||||
#include "clang/Tooling/Inclusions/StandardLibrary.h"
|
||||
#include <memory>
|
||||
#include <utility>
|
||||
|
||||
namespace clang::include_cleaner {
|
||||
namespace {
|
||||
@ -148,8 +150,9 @@ private:
|
||||
class PragmaIncludes::RecordPragma : public PPCallbacks, public CommentHandler {
|
||||
public:
|
||||
RecordPragma(const CompilerInstance &CI, PragmaIncludes *Out)
|
||||
: SM(CI.getSourceManager()),
|
||||
HeaderInfo(CI.getPreprocessor().getHeaderSearchInfo()), Out(Out),
|
||||
: RecordPragma(CI.getPreprocessor(), Out) {}
|
||||
RecordPragma(const Preprocessor &P, PragmaIncludes *Out)
|
||||
: SM(P.getSourceManager()), HeaderInfo(P.getHeaderSearchInfo()), Out(Out),
|
||||
UniqueStrings(Arena) {}
|
||||
|
||||
void FileChanged(SourceLocation Loc, FileChangeReason Reason,
|
||||
@ -342,6 +345,12 @@ void PragmaIncludes::record(const CompilerInstance &CI) {
|
||||
CI.getPreprocessor().addPPCallbacks(std::move(Record));
|
||||
}
|
||||
|
||||
void PragmaIncludes::record(Preprocessor &P) {
|
||||
auto Record = std::make_unique<RecordPragma>(P, this);
|
||||
P.addCommentHandler(Record.get());
|
||||
P.addPPCallbacks(std::move(Record));
|
||||
}
|
||||
|
||||
llvm::StringRef PragmaIncludes::getPublic(const FileEntry *F) const {
|
||||
auto It = IWYUPublic.find(F->getUniqueID());
|
||||
if (It == IWYUPublic.end())
|
||||
@ -350,8 +359,8 @@ llvm::StringRef PragmaIncludes::getPublic(const FileEntry *F) const {
|
||||
}
|
||||
|
||||
static llvm::SmallVector<const FileEntry *>
|
||||
toFileEntries(llvm::ArrayRef<StringRef> FileNames, FileManager& FM) {
|
||||
llvm::SmallVector<const FileEntry *> Results;
|
||||
toFileEntries(llvm::ArrayRef<StringRef> FileNames, FileManager &FM) {
|
||||
llvm::SmallVector<const FileEntry *> Results;
|
||||
|
||||
for (auto FName : FileNames) {
|
||||
// FIMXE: log the failing cases?
|
||||
|
@ -0,0 +1,4 @@
|
||||
#pragma once
|
||||
#include "baz.h"
|
||||
#include "private.h"
|
||||
int bar();
|
@ -0,0 +1,2 @@
|
||||
#pragma once
|
||||
int baz();
|
@ -0,0 +1,2 @@
|
||||
#pragma once
|
||||
void foo();
|
@ -0,0 +1,2 @@
|
||||
// IWYU pragma: private, include "public.h"
|
||||
int foobar();
|
@ -0,0 +1,17 @@
|
||||
// RUN: %check_clang_tidy %s misc-include-cleaner %t -- -- -I%S/Inputs -isystem%S/system
|
||||
#include "bar.h"
|
||||
// CHECK-FIXES: {{^}}#include "baz.h"{{$}}
|
||||
#include "foo.h"
|
||||
// CHECK-MESSAGES: :[[@LINE-1]]:1: warning: included header "foo.h" is not used directly [misc-include-cleaner]
|
||||
// CHECK-FIXES: {{^}}
|
||||
// CHECK-FIXES: {{^}}#include <string>{{$}}
|
||||
#include <vector.h>
|
||||
// CHECK-MESSAGES: :[[@LINE-1]]:1: warning: included header <vector.h> is not used directly [misc-include-cleaner]
|
||||
// CHECK-FIXES: {{^}}
|
||||
int BarResult = bar();
|
||||
int BazResult = baz();
|
||||
// CHECK-MESSAGES: :[[@LINE-1]]:17: warning: no header providing "baz.h" is directly included [misc-include-cleaner]
|
||||
std::string HelloString;
|
||||
// CHECK-MESSAGES: :[[@LINE-1]]:6: warning: no header providing <string> is directly included [misc-include-cleaner]
|
||||
int FooBarResult = foobar();
|
||||
// CHECK-MESSAGES: :[[@LINE-1]]:20: warning: no header providing "public.h" is directly included [misc-include-cleaner]
|
@ -0,0 +1,2 @@
|
||||
#pragma once
|
||||
namespace std { class string {}; }
|
@ -0,0 +1,4 @@
|
||||
#pragma once
|
||||
#include <string.h>
|
||||
|
||||
namespace std { class vector {}; }
|
@ -15,12 +15,14 @@ endif()
|
||||
get_filename_component(CLANG_LINT_SOURCE_DIR
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/../../clang-tidy REALPATH)
|
||||
include_directories(${CLANG_LINT_SOURCE_DIR})
|
||||
include_directories(BEFORE "${CMAKE_CURRENT_SOURCE_DIR}/../../include-cleaner/include")
|
||||
|
||||
add_extra_unittest(ClangTidyTests
|
||||
AddConstTest.cpp
|
||||
ClangTidyDiagnosticConsumerTest.cpp
|
||||
ClangTidyOptionsTest.cpp
|
||||
DeclRefExprUtilsTest.cpp
|
||||
IncludeCleanerTest.cpp
|
||||
IncludeInserterTest.cpp
|
||||
GlobListTest.cpp
|
||||
GoogleModuleTest.cpp
|
||||
@ -46,12 +48,14 @@ clang_target_link_libraries(ClangTidyTests
|
||||
clangTooling
|
||||
clangToolingCore
|
||||
clangTransformer
|
||||
clangIncludeCleaner
|
||||
)
|
||||
target_link_libraries(ClangTidyTests
|
||||
PRIVATE
|
||||
clangTidy
|
||||
clangTidyAndroidModule
|
||||
clangTidyGoogleModule
|
||||
clangTidyMiscModule
|
||||
clangTidyLLVMModule
|
||||
clangTidyModernizeModule
|
||||
clangTidyObjCModule
|
||||
|
236
clang-tools-extra/unittests/clang-tidy/IncludeCleanerTest.cpp
Normal file
236
clang-tools-extra/unittests/clang-tidy/IncludeCleanerTest.cpp
Normal file
@ -0,0 +1,236 @@
|
||||
//===--- IncludeCleanerTest.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 "ClangTidyDiagnosticConsumer.h"
|
||||
#include "ClangTidyOptions.h"
|
||||
#include "ClangTidyTest.h"
|
||||
#include "misc/IncludeCleanerCheck.h"
|
||||
#include "llvm/ADT/SmallString.h"
|
||||
#include "llvm/ADT/StringRef.h"
|
||||
#include "llvm/Support/FormatVariadic.h"
|
||||
#include "llvm/Support/Path.h"
|
||||
#include "llvm/Support/Regex.h"
|
||||
#include "gtest/gtest.h"
|
||||
#include <initializer_list>
|
||||
|
||||
#include <optional>
|
||||
#include <vector>
|
||||
|
||||
using namespace clang::tidy::misc;
|
||||
|
||||
namespace clang {
|
||||
namespace tidy {
|
||||
namespace test {
|
||||
namespace {
|
||||
|
||||
std::string
|
||||
appendPathFileSystemIndependent(std::initializer_list<std::string> Segments) {
|
||||
llvm::SmallString<32> Result;
|
||||
for (const auto &Segment : Segments)
|
||||
llvm::sys::path::append(Result, llvm::sys::path::Style::native, Segment);
|
||||
return std::string(Result.str());
|
||||
}
|
||||
|
||||
TEST(IncludeCleanerCheckTest, BasicUnusedIncludes) {
|
||||
const char *PreCode = R"(
|
||||
#include "bar.h"
|
||||
#include <vector>
|
||||
#include "bar.h"
|
||||
)";
|
||||
const char *PostCode = "\n";
|
||||
|
||||
std::vector<ClangTidyError> Errors;
|
||||
EXPECT_EQ(PostCode, runCheckOnCode<IncludeCleanerCheck>(
|
||||
PreCode, &Errors, "file.cpp", std::nullopt,
|
||||
ClangTidyOptions(), {{"bar.h", ""}, {"vector", ""}}));
|
||||
}
|
||||
|
||||
TEST(IncludeCleanerCheckTest, SuppressUnusedIncludes) {
|
||||
const char *PreCode = R"(
|
||||
#include "bar.h"
|
||||
#include "foo/qux.h"
|
||||
#include "baz/qux/qux.h"
|
||||
#include <vector>
|
||||
)";
|
||||
|
||||
const char *PostCode = R"(
|
||||
#include "bar.h"
|
||||
#include "foo/qux.h"
|
||||
#include <vector>
|
||||
)";
|
||||
|
||||
std::vector<ClangTidyError> Errors;
|
||||
ClangTidyOptions Opts;
|
||||
Opts.CheckOptions["IgnoreHeaders"] = llvm::StringRef{llvm::formatv(
|
||||
"bar.h;{0};{1};vector",
|
||||
llvm::Regex::escape(appendPathFileSystemIndependent({"foo", "qux.h"})),
|
||||
llvm::Regex::escape(appendPathFileSystemIndependent({"baz", "qux"})))};
|
||||
EXPECT_EQ(
|
||||
PostCode,
|
||||
runCheckOnCode<IncludeCleanerCheck>(
|
||||
PreCode, &Errors, "file.cpp", std::nullopt, Opts,
|
||||
{{"bar.h", ""},
|
||||
{"vector", ""},
|
||||
{appendPathFileSystemIndependent({"foo", "qux.h"}), ""},
|
||||
{appendPathFileSystemIndependent({"baz", "qux", "qux.h"}), ""}}));
|
||||
}
|
||||
|
||||
TEST(IncludeCleanerCheckTest, BasicMissingIncludes) {
|
||||
const char *PreCode = R"(
|
||||
#include "bar.h"
|
||||
|
||||
int BarResult = bar();
|
||||
int BazResult = baz();
|
||||
)";
|
||||
const char *PostCode = R"(
|
||||
#include "bar.h"
|
||||
#include "baz.h"
|
||||
|
||||
int BarResult = bar();
|
||||
int BazResult = baz();
|
||||
)";
|
||||
|
||||
std::vector<ClangTidyError> Errors;
|
||||
EXPECT_EQ(PostCode,
|
||||
runCheckOnCode<IncludeCleanerCheck>(
|
||||
PreCode, &Errors, "file.cpp", std::nullopt, ClangTidyOptions(),
|
||||
{{"bar.h", R"(#pragma once
|
||||
#include "baz.h"
|
||||
int bar();
|
||||
)"},
|
||||
{"baz.h", R"(#pragma once
|
||||
int baz();
|
||||
)"}}));
|
||||
}
|
||||
|
||||
TEST(IncludeCleanerCheckTest, SuppressMissingIncludes) {
|
||||
const char *PreCode = R"(
|
||||
#include "bar.h"
|
||||
|
||||
int BarResult = bar();
|
||||
int BazResult = baz();
|
||||
int QuxResult = qux();
|
||||
)";
|
||||
|
||||
ClangTidyOptions Opts;
|
||||
Opts.CheckOptions["IgnoreHeaders"] = llvm::StringRef{
|
||||
"baz.h;" +
|
||||
llvm::Regex::escape(appendPathFileSystemIndependent({"foo", "qux.h"}))};
|
||||
std::vector<ClangTidyError> Errors;
|
||||
EXPECT_EQ(PreCode, runCheckOnCode<IncludeCleanerCheck>(
|
||||
PreCode, &Errors, "file.cpp", std::nullopt, Opts,
|
||||
{{"bar.h", R"(#pragma once
|
||||
#include "baz.h"
|
||||
#include "foo/qux.h"
|
||||
int bar();
|
||||
)"},
|
||||
{"baz.h", R"(#pragma once
|
||||
int baz();
|
||||
)"},
|
||||
{appendPathFileSystemIndependent({"foo", "qux.h"}),
|
||||
R"(#pragma once
|
||||
int qux();
|
||||
)"}}));
|
||||
}
|
||||
|
||||
TEST(IncludeCleanerCheckTest, SystemMissingIncludes) {
|
||||
const char *PreCode = R"(
|
||||
#include <vector>
|
||||
|
||||
std::string HelloString;
|
||||
std::vector Vec;
|
||||
)";
|
||||
const char *PostCode = R"(
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
std::string HelloString;
|
||||
std::vector Vec;
|
||||
)";
|
||||
|
||||
std::vector<ClangTidyError> Errors;
|
||||
EXPECT_EQ(PostCode,
|
||||
runCheckOnCode<IncludeCleanerCheck>(
|
||||
PreCode, &Errors, "file.cpp", std::nullopt, ClangTidyOptions(),
|
||||
{{"string", R"(#pragma once
|
||||
namespace std { class string {}; }
|
||||
)"},
|
||||
{"vector", R"(#pragma once
|
||||
#include <string>
|
||||
namespace std { class vector {}; }
|
||||
)"}}));
|
||||
}
|
||||
|
||||
TEST(IncludeCleanerCheckTest, PragmaMissingIncludes) {
|
||||
const char *PreCode = R"(
|
||||
#include "bar.h"
|
||||
|
||||
int BarResult = bar();
|
||||
int FooBarResult = foobar();
|
||||
)";
|
||||
const char *PostCode = R"(
|
||||
#include "bar.h"
|
||||
#include "public.h"
|
||||
|
||||
int BarResult = bar();
|
||||
int FooBarResult = foobar();
|
||||
)";
|
||||
|
||||
std::vector<ClangTidyError> Errors;
|
||||
EXPECT_EQ(PostCode,
|
||||
runCheckOnCode<IncludeCleanerCheck>(
|
||||
PreCode, &Errors, "file.cpp", std::nullopt, ClangTidyOptions(),
|
||||
{{"bar.h", R"(#pragma once
|
||||
#include "private.h"
|
||||
int bar();
|
||||
)"},
|
||||
{"private.h", R"(#pragma once
|
||||
// IWYU pragma: private, include "public.h"
|
||||
int foobar();
|
||||
)"}}));
|
||||
}
|
||||
|
||||
TEST(IncludeCleanerCheckTest, DeclFromMacroExpansion) {
|
||||
const char *PreCode = R"(
|
||||
#include "foo.h"
|
||||
|
||||
DECLARE(myfunc) {
|
||||
int a;
|
||||
}
|
||||
)";
|
||||
|
||||
std::vector<ClangTidyError> Errors;
|
||||
EXPECT_EQ(PreCode,
|
||||
runCheckOnCode<IncludeCleanerCheck>(
|
||||
PreCode, &Errors, "file.cpp", std::nullopt, ClangTidyOptions(),
|
||||
{{"foo.h",
|
||||
R"(#pragma once
|
||||
#define DECLARE(X) void X()
|
||||
)"}}));
|
||||
|
||||
PreCode = R"(
|
||||
#include "foo.h"
|
||||
|
||||
DECLARE {
|
||||
int a;
|
||||
}
|
||||
)";
|
||||
|
||||
EXPECT_EQ(PreCode,
|
||||
runCheckOnCode<IncludeCleanerCheck>(
|
||||
PreCode, &Errors, "file.cpp", std::nullopt, ClangTidyOptions(),
|
||||
{{"foo.h",
|
||||
R"(#pragma once
|
||||
#define DECLARE void myfunc()
|
||||
)"}}));
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace test
|
||||
} // namespace tidy
|
||||
} // namespace clang
|
Loading…
Reference in New Issue
Block a user