diff --git a/clang-tools-extra/clangd/Headers.h b/clang-tools-extra/clangd/Headers.h --- a/clang-tools-extra/clangd/Headers.h +++ b/clang-tools-extra/clangd/Headers.h @@ -35,12 +35,6 @@ /// Returns true if \p Include is literal include like "path" or . bool isLiteralInclude(llvm::StringRef Include); -/// If Text begins an Include-What-You-Use directive, returns it. -/// Given "// IWYU pragma: keep", returns "keep". -/// Input is a null-terminated char* as provided by SM.getCharacterData(). -/// (This should not be StringRef as we do *not* want to scan for its length). -llvm::Optional parseIWYUPragma(const char *Text); - /// Represents a header file to be #include'd. struct HeaderFile { std::string File; diff --git a/clang-tools-extra/clangd/Headers.cpp b/clang-tools-extra/clangd/Headers.cpp --- a/clang-tools-extra/clangd/Headers.cpp +++ b/clang-tools-extra/clangd/Headers.cpp @@ -23,18 +23,6 @@ namespace clang { namespace clangd { -llvm::Optional parseIWYUPragma(const char *Text) { - // This gets called for every comment seen in the preamble, so it's quite hot. - constexpr llvm::StringLiteral IWYUPragma = "// IWYU pragma: "; - if (strncmp(Text, IWYUPragma.data(), IWYUPragma.size())) - return llvm::None; - Text += IWYUPragma.size(); - const char *End = Text; - while (*End != 0 && *End != '\n') - ++End; - return StringRef(Text, End - Text); -} - class IncludeStructure::RecordHeaders : public PPCallbacks, public CommentHandler { public: @@ -136,7 +124,8 @@ } bool HandleComment(Preprocessor &PP, SourceRange Range) override { - auto Pragma = parseIWYUPragma(SM.getCharacterData(Range.getBegin())); + auto Pragma = + tooling::parseIWYUPragma(SM.getCharacterData(Range.getBegin())); if (!Pragma) return false; diff --git a/clang-tools-extra/clangd/unittests/HeadersTests.cpp b/clang-tools-extra/clangd/unittests/HeadersTests.cpp --- a/clang-tools-extra/clangd/unittests/HeadersTests.cpp +++ b/clang-tools-extra/clangd/unittests/HeadersTests.cpp @@ -447,18 +447,6 @@ EXPECT_FALSE(Includes.hasIWYUExport(getID("none.h", Includes))); } -TEST(Headers, ParseIWYUPragma) { - EXPECT_THAT(parseIWYUPragma("// IWYU pragma: keep"), HasValue(Eq("keep"))); - EXPECT_THAT(parseIWYUPragma("// IWYU pragma: keep\netc"), - HasValue(Eq("keep"))); - EXPECT_EQ(parseIWYUPragma("/* IWYU pragma: keep"), llvm::None) - << "Only // comments supported!"; - EXPECT_EQ(parseIWYUPragma("// IWYU pragma: keep"), llvm::None) - << "Sensitive to whitespace"; - EXPECT_EQ(parseIWYUPragma("// IWYU pragma:keep"), llvm::None) - << "Sensitive to whitespace"; -} - } // namespace } // namespace clangd } // namespace clang diff --git a/clang-tools-extra/include-cleaner/lib/Record.cpp b/clang-tools-extra/include-cleaner/lib/Record.cpp --- a/clang-tools-extra/include-cleaner/lib/Record.cpp +++ b/clang-tools-extra/include-cleaner/lib/Record.cpp @@ -141,21 +141,6 @@ } // namespace -// FIXME: this is a mirror of clang::clangd::parseIWYUPragma, move to libTooling -// to share the code? -static llvm::Optional parseIWYUPragma(const char *Text) { - assert(strncmp(Text, "//", 2) || strncmp(Text, "/*", 2)); - constexpr llvm::StringLiteral IWYUPragma = " IWYU pragma: "; - Text += 2; // Skip the comment start, // or /*. - if (strncmp(Text, IWYUPragma.data(), IWYUPragma.size())) - return llvm::None; - Text += IWYUPragma.size(); - const char *End = Text; - while (*End != 0 && *End != '\n') - ++End; - return StringRef(Text, End - Text); -} - class PragmaIncludes::RecordPragma : public PPCallbacks, public CommentHandler { public: RecordPragma(const CompilerInstance &CI, PragmaIncludes *Out) @@ -229,7 +214,8 @@ bool HandleComment(Preprocessor &PP, SourceRange Range) override { auto &SM = PP.getSourceManager(); - auto Pragma = parseIWYUPragma(SM.getCharacterData(Range.getBegin())); + auto Pragma = + tooling::parseIWYUPragma(SM.getCharacterData(Range.getBegin())); if (!Pragma) return false; diff --git a/clang/include/clang/Tooling/Inclusions/HeaderAnalysis.h b/clang/include/clang/Tooling/Inclusions/HeaderAnalysis.h --- a/clang/include/clang/Tooling/Inclusions/HeaderAnalysis.h +++ b/clang/include/clang/Tooling/Inclusions/HeaderAnalysis.h @@ -9,6 +9,8 @@ #ifndef LLVM_CLANG_TOOLING_INCLUSIONS_HEADER_ANALYSIS_H #define LLVM_CLANG_TOOLING_INCLUSIONS_HEADER_ANALYSIS_H +#include "llvm/ADT/Optional.h" +#include "llvm/ADT/StringRef.h" namespace clang { class FileEntry; class SourceManager; @@ -27,6 +29,13 @@ bool isSelfContainedHeader(const FileEntry *FE, const SourceManager &SM, HeaderSearch &HeaderInfo); +/// If Text begins an Include-What-You-Use directive, returns it. +/// Given "// IWYU pragma: keep", returns "keep". +/// Input is a null-terminated char* as provided by SM.getCharacterData(). +/// (This should not be StringRef as we do *not* want to scan for its length). +/// For multi-line comments, we return only the first line. +llvm::Optional parseIWYUPragma(const char *Text); + } // namespace tooling } // namespace clang diff --git a/clang/lib/Tooling/Inclusions/HeaderAnalysis.cpp b/clang/lib/Tooling/Inclusions/HeaderAnalysis.cpp --- a/clang/lib/Tooling/Inclusions/HeaderAnalysis.cpp +++ b/clang/lib/Tooling/Inclusions/HeaderAnalysis.cpp @@ -64,4 +64,28 @@ const_cast(SM).getMemoryBufferForFileOrNone(FE).value_or( llvm::MemoryBufferRef())); } + +llvm::Optional parseIWYUPragma(const char *Text) { + // Skip the comment start, // or /*. + if (Text[0] != '/' || (Text[1] != '/' && Text[1] != '*')) + return llvm::None; + bool BlockComment = Text[1] == '*'; + Text += 2; + + // Per spec, direcitves are whitespace- and case-sensitive. + constexpr llvm::StringLiteral IWYUPragma = " IWYU pragma: "; + if (strncmp(Text, IWYUPragma.data(), IWYUPragma.size())) + return llvm::None; + Text += IWYUPragma.size(); + const char *End = Text; + while (*End != 0 && *End != '\n') + ++End; + StringRef Rest(Text, End - Text); + // Strip off whitespace and comment markers to avoid confusion. This isn't + // fully-compatible with IWYU, which splits into whitespace-delimited tokens. + if (BlockComment) + Rest.consume_back("*/"); + return Rest.trim(); +} + } // namespace clang::tooling diff --git a/clang/unittests/Tooling/HeaderAnalysisTest.cpp b/clang/unittests/Tooling/HeaderAnalysisTest.cpp --- a/clang/unittests/Tooling/HeaderAnalysisTest.cpp +++ b/clang/unittests/Tooling/HeaderAnalysisTest.cpp @@ -9,11 +9,14 @@ #include "clang/Tooling/Inclusions/HeaderAnalysis.h" #include "clang/Lex/Preprocessor.h" #include "clang/Testing/TestAST.h" +#include "llvm/Testing/Support/SupportHelpers.h" #include "gtest/gtest.h" namespace clang { namespace tooling { namespace { +using llvm::ValueIs; +using testing::Eq; TEST(HeaderAnalysisTest, IsSelfContained) { TestInputs Inputs; @@ -58,6 +61,19 @@ EXPECT_FALSE(isSelfContainedHeader(FM.getFile("bad.h").get(), SM, HI)); } +TEST(HeaderAnalysisTest, ParseIWYUPragma) { + EXPECT_THAT(parseIWYUPragma("// IWYU pragma: keep"), ValueIs(Eq("keep"))); + EXPECT_THAT(parseIWYUPragma("// IWYU pragma: keep me\netc"), + ValueIs(Eq("keep me"))); + EXPECT_THAT(parseIWYUPragma("/* IWYU pragma: keep */"), ValueIs(Eq("keep"))); + EXPECT_EQ(parseIWYUPragma("// IWYU pragma: keep"), llvm::None) + << "Prefix is sensitive to whitespace"; + EXPECT_EQ(parseIWYUPragma("// IWYU pragma:keep"), llvm::None) + << "Prefix is sensitive to whitespace"; + EXPECT_EQ(parseIWYUPragma("/\n* IWYU pragma: keep */"), llvm::None) + << "Must start with /* or //"; +} + } // namespace } // namespace tooling } // namespace clang