namespace clang {
namespace clangd {
-class IncludeStructure::RecordHeaders : public PPCallbacks,
- public CommentHandler {
+class IncludeStructure::RecordHeaders : public PPCallbacks {
public:
RecordHeaders(const CompilerInstance &CI, IncludeStructure *Out)
: SM(CI.getSourceManager()),
SM.getLineNumber(SM.getFileID(HashLoc), Inc.HashOffset) - 1;
Inc.FileKind = FileKind;
Inc.Directive = IncludeTok.getIdentifierInfo()->getPPKeywordID();
- if (LastPragmaKeepInMainFileLine == Inc.HashLine)
- Inc.BehindPragmaKeep = true;
if (File) {
IncludeStructure::HeaderID HID = Out->getOrCreateID(*File);
Inc.HeaderID = static_cast<unsigned>(HID);
}
}
- bool HandleComment(Preprocessor &PP, SourceRange Range) override {
- auto Pragma =
- tooling::parseIWYUPragma(SM.getCharacterData(Range.getBegin()));
- if (!Pragma)
- return false;
-
- if (inMainFile()) {
- // Given:
- //
- // #include "foo.h"
- // #include "bar.h" // IWYU pragma: keep
- //
- // The order in which the callbacks will be triggered:
- //
- // 1. InclusionDirective("foo.h")
- // 2. handleCommentInMainFile("// IWYU pragma: keep")
- // 3. InclusionDirective("bar.h")
- //
- // This code stores the last location of "IWYU pragma: keep" (or export)
- // comment in the main file, so that when InclusionDirective is called, it
- // will know that the next inclusion is behind the IWYU pragma.
- // FIXME: Support "IWYU pragma: begin_exports" and "IWYU pragma:
- // end_exports".
- if (!Pragma->startswith("export") && !Pragma->startswith("keep"))
- return false;
- unsigned Offset = SM.getFileOffset(Range.getBegin());
- LastPragmaKeepInMainFileLine =
- SM.getLineNumber(SM.getMainFileID(), Offset) - 1;
- } else {
- // Memorize headers that have export pragmas in them. Include Cleaner
- // does not support them properly yet, so they will be not marked as
- // unused.
- // FIXME: Once IncludeCleaner supports export pragmas, remove this.
- if (!Pragma->startswith("export") && !Pragma->startswith("begin_exports"))
- return false;
- Out->HasIWYUExport.insert(
- *Out->getID(SM.getFileEntryForID(SM.getFileID(Range.getBegin()))));
- }
- return false;
- }
-
private:
// Keeps track of include depth for the current file. It's 1 for main file.
int Level = 0;
bool InBuiltinFile = false;
IncludeStructure *Out;
-
- // The last line "IWYU pragma: keep" was seen in the main file, 0-indexed.
- int LastPragmaKeepInMainFileLine = -1;
};
bool isLiteralInclude(llvm::StringRef Include) {
auto &SM = CI.getSourceManager();
MainFileEntry = SM.getFileEntryForID(SM.getMainFileID());
auto Collector = std::make_unique<RecordHeaders>(CI, this);
- CI.getPreprocessor().addCommentHandler(Collector.get());
CI.getPreprocessor().addPPCallbacks(std::move(Collector));
}
int HashLine = 0; // Line number containing the directive, 0-indexed.
SrcMgr::CharacteristicKind FileKind = SrcMgr::C_User;
std::optional<unsigned> HeaderID;
- bool BehindPragmaKeep = false; // Has IWYU pragma: keep right after.
};
llvm::raw_ostream &operator<<(llvm::raw_ostream &, const Inclusion &);
bool operator==(const Inclusion &LHS, const Inclusion &RHS);
return !NonSelfContained.contains(ID);
}
- bool hasIWYUExport(HeaderID ID) const { return HasIWYUExport.contains(ID); }
-
// Return all transitively reachable files.
llvm::ArrayRef<std::string> allHeaders() const { return RealPathNames; }
// Contains HeaderIDs of all non self-contained entries in the
// IncludeStructure.
llvm::DenseSet<HeaderID> NonSelfContained;
- // Contains a set of headers that have either "IWYU pragma: export" or "IWYU
- // pragma: begin_exports".
- llvm::DenseSet<HeaderID> HasIWYUExport;
// Maps written includes to indices in MainFileInclude for easier lookup by
// spelling.
#include "SourceCode.h"
#include "URI.h"
#include "clang-include-cleaner/Analysis.h"
+#include "clang-include-cleaner/Record.h"
#include "clang-include-cleaner/Types.h"
#include "support/Logger.h"
#include "support/Path.h"
}
static bool mayConsiderUnused(const Inclusion &Inc, ParsedAST &AST,
- const Config &Cfg) {
- if (Inc.BehindPragmaKeep)
+ const Config &Cfg,
+ const include_cleaner::PragmaIncludes *PI) {
+ if (PI && PI->shouldKeep(Inc.HashLine + 1))
return false;
-
// FIXME(kirillbobyrev): We currently do not support the umbrella headers.
// System headers are likely to be standard library headers.
// Until we have good support for umbrella headers, don't warn about them.
}
assert(Inc.HeaderID);
auto HID = static_cast<IncludeStructure::HeaderID>(*Inc.HeaderID);
- // FIXME: Ignore the headers with IWYU export pragmas for now, remove this
- // check when we have more precise tracking of exported headers.
- if (AST.getIncludeStructure().hasIWYUExport(HID))
- return false;
auto FE = AST.getSourceManager().getFileManager().getFileRef(
AST.getIncludeStructure().getRealPath(HID));
assert(FE);
continue;
auto IncludeID = static_cast<IncludeStructure::HeaderID>(*MFI.HeaderID);
bool Used = ReferencedFiles.contains(IncludeID);
- if (!Used && !mayConsiderUnused(MFI, AST, Cfg)) {
+ if (!Used && !mayConsiderUnused(MFI, AST, Cfg, AST.getPragmaIncludes())) {
dlog("{0} was not used, but is not eligible to be diagnosed as unused",
MFI.Written);
continue;
MATCHER_P(resolved, Name, "") { return arg.Resolved == Name; }
MATCHER_P(includeLine, N, "") { return arg.HashLine == N; }
MATCHER_P(directive, D, "") { return arg.Directive == D; }
-MATCHER_P(hasPragmaKeep, H, "") { return arg.BehindPragmaKeep == H; }
MATCHER_P2(Distance, File, D, "") {
if (arg.getFirst() != File)
directive(tok::pp_include_next)));
}
-TEST_F(HeadersTest, IWYUPragmaKeep) {
- FS.Files[MainFile] = R"cpp(
-#include "bar.h" // IWYU pragma: keep
-#include "foo.h"
-)cpp";
-
- EXPECT_THAT(
- collectIncludes().MainFileIncludes,
- UnorderedElementsAre(AllOf(written("\"foo.h\""), hasPragmaKeep(false)),
- AllOf(written("\"bar.h\""), hasPragmaKeep(true))));
-}
-
TEST_F(HeadersTest, InsertInclude) {
std::string Path = testPath("sub/bar.h");
FS.Files[Path] = "";
EXPECT_FALSE(Includes.isSelfContained(getID("pp_depend.h", Includes)));
}
-TEST_F(HeadersTest, HasIWYUPragmas) {
- FS.Files[MainFile] = R"cpp(
-#include "export.h"
-#include "begin_exports.h"
-#include "none.h"
-)cpp";
- FS.Files["export.h"] = R"cpp(
-#pragma once
-#include "none.h" // IWYU pragma: export
-)cpp";
- FS.Files["begin_exports.h"] = R"cpp(
-#pragma once
-// IWYU pragma: begin_exports
-#include "none.h"
-// IWYU pragma: end_exports
-)cpp";
- FS.Files["none.h"] = R"cpp(
-#pragma once
-// Not a pragma.
-)cpp";
-
- auto Includes = collectIncludes();
- EXPECT_TRUE(Includes.hasIWYUExport(getID("export.h", Includes)));
- EXPECT_TRUE(Includes.hasIWYUExport(getID("begin_exports.h", Includes)));
- EXPECT_FALSE(Includes.hasIWYUExport(getID("none.h", Includes)));
-}
-
} // namespace
} // namespace clangd
} // namespace clang
ParsedAST AST = TU.build();
EXPECT_THAT(AST.getDiagnostics(), llvm::ValueIs(IsEmpty()));
- // FIXME: This is not correct: foo.h is unused but is not diagnosed as such
- // because we ignore headers with IWYU export pragmas for now.
IncludeCleanerFindings Findings = computeIncludeCleanerFindings(AST);
- EXPECT_THAT(Findings.UnusedIncludes, IsEmpty());
+ EXPECT_THAT(Findings.UnusedIncludes,
+ ElementsAre(Pointee(writtenInclusion("\"foo.h\""))));
}
TEST(IncludeCleaner, NoDiagsForObjC) {
Field(&Inclusion::Written, "\"a.h\""),
Field(&Inclusion::Resolved, testPath("a.h")),
Field(&Inclusion::HeaderID, testing::Not(testing::Eq(std::nullopt))),
- Field(&Inclusion::BehindPragmaKeep, true),
Field(&Inclusion::FileKind, SrcMgr::CharacteristicKind::C_User))));
}