| //===-- ReplayPreambleTests.cpp -------------------------------------------===// |
| // |
| // 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 |
| // |
| //===----------------------------------------------------------------------===// |
| // |
| // These tests cover clangd's logic to replay PP events from preamble to |
| // clang-tidy checks. |
| // |
| //===----------------------------------------------------------------------===// |
| |
| #include "../../clang-tidy/ClangTidyCheck.h" |
| #include "../../clang-tidy/ClangTidyModule.h" |
| #include "../../clang-tidy/ClangTidyModuleRegistry.h" |
| #include "AST.h" |
| #include "Config.h" |
| #include "Diagnostics.h" |
| #include "ParsedAST.h" |
| #include "SourceCode.h" |
| #include "TestTU.h" |
| #include "TidyProvider.h" |
| #include "support/Context.h" |
| #include "clang/AST/DeclTemplate.h" |
| #include "clang/Basic/FileEntry.h" |
| #include "clang/Basic/LLVM.h" |
| #include "clang/Basic/SourceLocation.h" |
| #include "clang/Basic/SourceManager.h" |
| #include "clang/Basic/TokenKinds.h" |
| #include "clang/Lex/PPCallbacks.h" |
| #include "clang/Lex/Token.h" |
| #include "clang/Tooling/Syntax/Tokens.h" |
| #include "llvm/ADT/StringRef.h" |
| #include "llvm/Support/Registry.h" |
| #include "llvm/Testing/Annotations/Annotations.h" |
| #include "gmock/gmock-matchers.h" |
| #include "gmock/gmock.h" |
| #include "gtest/gtest.h" |
| #include <cstddef> |
| #include <memory> |
| #include <vector> |
| |
| namespace clang { |
| |
| class Module; |
| |
| namespace clangd { |
| namespace { |
| struct Inclusion { |
| Inclusion(const SourceManager &SM, SourceLocation HashLoc, |
| const Token &IncludeTok, llvm::StringRef FileName, bool IsAngled, |
| CharSourceRange FilenameRange) |
| : HashOffset(SM.getDecomposedLoc(HashLoc).second), IncTok(IncludeTok), |
| IncDirective(IncludeTok.getIdentifierInfo()->getName()), |
| FileNameOffset(SM.getDecomposedLoc(FilenameRange.getBegin()).second), |
| FileName(FileName), IsAngled(IsAngled) { |
| EXPECT_EQ( |
| toSourceCode(SM, FilenameRange.getAsRange()).drop_back().drop_front(), |
| FileName); |
| } |
| size_t HashOffset; |
| syntax::Token IncTok; |
| llvm::StringRef IncDirective; |
| size_t FileNameOffset; |
| llvm::StringRef FileName; |
| bool IsAngled; |
| }; |
| static std::vector<Inclusion> Includes; |
| static std::vector<syntax::Token> SkippedFiles; |
| struct ReplayPreamblePPCallback : public PPCallbacks { |
| const SourceManager &SM; |
| explicit ReplayPreamblePPCallback(const SourceManager &SM) : SM(SM) {} |
| |
| void InclusionDirective(SourceLocation HashLoc, const Token &IncludeTok, |
| StringRef FileName, bool IsAngled, |
| CharSourceRange FilenameRange, OptionalFileEntryRef, |
| StringRef, StringRef, const clang::Module *, bool, |
| SrcMgr::CharacteristicKind) override { |
| Includes.emplace_back(SM, HashLoc, IncludeTok, FileName, IsAngled, |
| FilenameRange); |
| } |
| |
| void FileSkipped(const FileEntryRef &, const Token &FilenameTok, |
| SrcMgr::CharacteristicKind) override { |
| SkippedFiles.emplace_back(FilenameTok); |
| } |
| }; |
| struct ReplayPreambleCheck : public tidy::ClangTidyCheck { |
| ReplayPreambleCheck(StringRef Name, tidy::ClangTidyContext *Context) |
| : ClangTidyCheck(Name, Context) {} |
| void registerPPCallbacks(const SourceManager &SM, Preprocessor *PP, |
| Preprocessor *ModuleExpanderPP) override { |
| PP->addPPCallbacks(::std::make_unique<ReplayPreamblePPCallback>(SM)); |
| } |
| }; |
| llvm::StringLiteral CheckName = "replay-preamble-check"; |
| struct ReplayPreambleModule : public tidy::ClangTidyModule { |
| void |
| addCheckFactories(tidy::ClangTidyCheckFactories &CheckFactories) override { |
| CheckFactories.registerCheck<ReplayPreambleCheck>(CheckName); |
| } |
| }; |
| static tidy::ClangTidyModuleRegistry::Add<ReplayPreambleModule> |
| X("replay-preamble-module", ""); |
| |
| MATCHER_P(rangeIs, R, "") { |
| return arg.beginOffset() == R.Begin && arg.endOffset() == R.End; |
| } |
| |
| TEST(ReplayPreambleTest, IncludesAndSkippedFiles) { |
| TestTU TU; |
| // This check records inclusion directives replayed by clangd. |
| TU.ClangTidyProvider = addTidyChecks(CheckName); |
| llvm::Annotations Test(R"cpp( |
| $hash^#$include[[import]] $filebegin^"$filerange[[bar.h]]" |
| $hash^#$include[[include_next]] $filebegin^"$filerange[[baz.h]]" |
| $hash^#$include[[include]] $filebegin^<$filerange[[a.h]]>)cpp"); |
| llvm::StringRef Code = Test.code(); |
| TU.Code = Code.str(); |
| TU.AdditionalFiles["bar.h"] = ""; |
| TU.AdditionalFiles["baz.h"] = ""; |
| TU.AdditionalFiles["a.h"] = ""; |
| // Since we are also testing #import directives, and they don't make much |
| // sense in c++ (also they actually break on windows), just set language to |
| // obj-c. |
| TU.ExtraArgs = {"-isystem.", "-xobjective-c"}; |
| |
| // Allow the check to run even though not marked as fast. |
| Config Cfg; |
| Cfg.Diagnostics.ClangTidy.FastCheckFilter = Config::FastCheckPolicy::Loose; |
| WithContextValue WithCfg(Config::Key, std::move(Cfg)); |
| |
| const auto &AST = TU.build(); |
| const auto &SM = AST.getSourceManager(); |
| |
| auto HashLocs = Test.points("hash"); |
| ASSERT_EQ(HashLocs.size(), Includes.size()); |
| auto IncludeRanges = Test.ranges("include"); |
| ASSERT_EQ(IncludeRanges.size(), Includes.size()); |
| auto FileBeginLocs = Test.points("filebegin"); |
| ASSERT_EQ(FileBeginLocs.size(), Includes.size()); |
| auto FileRanges = Test.ranges("filerange"); |
| ASSERT_EQ(FileRanges.size(), Includes.size()); |
| |
| ASSERT_EQ(SkippedFiles.size(), Includes.size()); |
| for (size_t I = 0; I < Includes.size(); ++I) { |
| const auto &Inc = Includes[I]; |
| |
| EXPECT_EQ(Inc.HashOffset, HashLocs[I]); |
| |
| auto IncRange = IncludeRanges[I]; |
| EXPECT_THAT(Inc.IncTok.range(SM), rangeIs(IncRange)); |
| EXPECT_EQ(Inc.IncTok.kind(), tok::identifier); |
| EXPECT_EQ(Inc.IncDirective, |
| Code.substr(IncRange.Begin, IncRange.End - IncRange.Begin)); |
| |
| EXPECT_EQ(Inc.FileNameOffset, FileBeginLocs[I]); |
| EXPECT_EQ(Inc.IsAngled, Code[FileBeginLocs[I]] == '<'); |
| |
| auto FileRange = FileRanges[I]; |
| EXPECT_EQ(Inc.FileName, |
| Code.substr(FileRange.Begin, FileRange.End - FileRange.Begin)); |
| |
| EXPECT_EQ(SM.getDecomposedLoc(SkippedFiles[I].location()).second, |
| Inc.FileNameOffset); |
| // This also contains quotes/angles so increment the range by one from both |
| // sides. |
| EXPECT_EQ( |
| SkippedFiles[I].text(SM), |
| Code.substr(FileRange.Begin - 1, FileRange.End - FileRange.Begin + 2)); |
| EXPECT_EQ(SkippedFiles[I].kind(), tok::header_name); |
| } |
| } |
| } // namespace |
| } // namespace clangd |
| } // namespace clang |