| 1 | //===-- ReplayPreambleTests.cpp -------------------------------------------===// |
| 2 | // |
| 3 | // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. |
| 4 | // See https://llvm.org/LICENSE.txt for license information. |
| 5 | // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
| 6 | // |
| 7 | //===----------------------------------------------------------------------===// |
| 8 | // |
| 9 | // These tests cover clangd's logic to replay PP events from preamble to |
| 10 | // clang-tidy checks. |
| 11 | // |
| 12 | //===----------------------------------------------------------------------===// |
| 13 | |
| 14 | #include "../../clang-tidy/ClangTidyCheck.h" |
| 15 | #include "../../clang-tidy/ClangTidyModule.h" |
| 16 | #include "../../clang-tidy/ClangTidyModuleRegistry.h" |
| 17 | #include "AST.h" |
| 18 | #include "Config.h" |
| 19 | #include "Diagnostics.h" |
| 20 | #include "ParsedAST.h" |
| 21 | #include "SourceCode.h" |
| 22 | #include "TestTU.h" |
| 23 | #include "TidyProvider.h" |
| 24 | #include "support/Context.h" |
| 25 | #include "clang/AST/DeclTemplate.h" |
| 26 | #include "clang/Basic/FileEntry.h" |
| 27 | #include "clang/Basic/LLVM.h" |
| 28 | #include "clang/Basic/SourceLocation.h" |
| 29 | #include "clang/Basic/SourceManager.h" |
| 30 | #include "clang/Basic/TokenKinds.h" |
| 31 | #include "clang/Lex/PPCallbacks.h" |
| 32 | #include "clang/Lex/Token.h" |
| 33 | #include "clang/Tooling/Syntax/Tokens.h" |
| 34 | #include "llvm/ADT/StringRef.h" |
| 35 | #include "llvm/Support/Registry.h" |
| 36 | #include "llvm/Testing/Annotations/Annotations.h" |
| 37 | #include "gmock/gmock-matchers.h" |
| 38 | #include "gmock/gmock.h" |
| 39 | #include "gtest/gtest.h" |
| 40 | #include <cstddef> |
| 41 | #include <memory> |
| 42 | #include <vector> |
| 43 | |
| 44 | namespace clang { |
| 45 | |
| 46 | class Module; |
| 47 | |
| 48 | namespace clangd { |
| 49 | namespace { |
| 50 | struct Inclusion { |
| 51 | Inclusion(const SourceManager &SM, SourceLocation HashLoc, |
| 52 | const Token &IncludeTok, llvm::StringRef FileName, bool IsAngled, |
| 53 | CharSourceRange FilenameRange) |
| 54 | : HashOffset(SM.getDecomposedLoc(Loc: HashLoc).second), IncTok(IncludeTok), |
| 55 | IncDirective(IncludeTok.getIdentifierInfo()->getName()), |
| 56 | FileNameOffset(SM.getDecomposedLoc(Loc: FilenameRange.getBegin()).second), |
| 57 | FileName(FileName), IsAngled(IsAngled) { |
| 58 | EXPECT_EQ( |
| 59 | toSourceCode(SM, FilenameRange.getAsRange()).drop_back().drop_front(), |
| 60 | FileName); |
| 61 | } |
| 62 | size_t HashOffset; |
| 63 | syntax::Token IncTok; |
| 64 | llvm::StringRef IncDirective; |
| 65 | size_t FileNameOffset; |
| 66 | llvm::StringRef FileName; |
| 67 | bool IsAngled; |
| 68 | }; |
| 69 | static std::vector<Inclusion> Includes; |
| 70 | static std::vector<syntax::Token> SkippedFiles; |
| 71 | struct ReplayPreamblePPCallback : public PPCallbacks { |
| 72 | const SourceManager &SM; |
| 73 | explicit ReplayPreamblePPCallback(const SourceManager &SM) : SM(SM) {} |
| 74 | |
| 75 | void InclusionDirective(SourceLocation HashLoc, const Token &IncludeTok, |
| 76 | StringRef FileName, bool IsAngled, |
| 77 | CharSourceRange FilenameRange, OptionalFileEntryRef, |
| 78 | StringRef, StringRef, const clang::Module *, bool, |
| 79 | SrcMgr::CharacteristicKind) override { |
| 80 | Includes.emplace_back(args: SM, args&: HashLoc, args: IncludeTok, args&: FileName, args&: IsAngled, |
| 81 | args&: FilenameRange); |
| 82 | } |
| 83 | |
| 84 | void FileSkipped(const FileEntryRef &, const Token &FilenameTok, |
| 85 | SrcMgr::CharacteristicKind) override { |
| 86 | SkippedFiles.emplace_back(args: FilenameTok); |
| 87 | } |
| 88 | }; |
| 89 | struct ReplayPreambleCheck : public tidy::ClangTidyCheck { |
| 90 | ReplayPreambleCheck(StringRef Name, tidy::ClangTidyContext *Context) |
| 91 | : ClangTidyCheck(Name, Context) {} |
| 92 | void registerPPCallbacks(const SourceManager &SM, Preprocessor *PP, |
| 93 | Preprocessor *ModuleExpanderPP) override { |
| 94 | PP->addPPCallbacks(C: ::std::make_unique<ReplayPreamblePPCallback>(args: SM)); |
| 95 | } |
| 96 | }; |
| 97 | llvm::StringLiteral CheckName = "replay-preamble-check" ; |
| 98 | struct ReplayPreambleModule : public tidy::ClangTidyModule { |
| 99 | void |
| 100 | addCheckFactories(tidy::ClangTidyCheckFactories &CheckFactories) override { |
| 101 | CheckFactories.registerCheck<ReplayPreambleCheck>(CheckName); |
| 102 | } |
| 103 | }; |
| 104 | static tidy::ClangTidyModuleRegistry::Add<ReplayPreambleModule> |
| 105 | X("replay-preamble-module" , "" ); |
| 106 | |
| 107 | MATCHER_P(rangeIs, R, "" ) { |
| 108 | return arg.beginOffset() == R.Begin && arg.endOffset() == R.End; |
| 109 | } |
| 110 | |
| 111 | TEST(ReplayPreambleTest, IncludesAndSkippedFiles) { |
| 112 | TestTU TU; |
| 113 | // This check records inclusion directives replayed by clangd. |
| 114 | TU.ClangTidyProvider = addTidyChecks(Checks: CheckName); |
| 115 | llvm::Annotations Test(R"cpp( |
| 116 | $hash^#$include[[import]] $filebegin^"$filerange[[bar.h]]" |
| 117 | $hash^#$include[[include_next]] $filebegin^"$filerange[[baz.h]]" |
| 118 | $hash^#$include[[include]] $filebegin^<$filerange[[a.h]]>)cpp" ); |
| 119 | llvm::StringRef Code = Test.code(); |
| 120 | TU.Code = Code.str(); |
| 121 | TU.AdditionalFiles["bar.h" ] = "" ; |
| 122 | TU.AdditionalFiles["baz.h" ] = "" ; |
| 123 | TU.AdditionalFiles["a.h" ] = "" ; |
| 124 | // Since we are also testing #import directives, and they don't make much |
| 125 | // sense in c++ (also they actually break on windows), just set language to |
| 126 | // obj-c. |
| 127 | TU.ExtraArgs = {"-isystem." , "-xobjective-c" }; |
| 128 | |
| 129 | // Allow the check to run even though not marked as fast. |
| 130 | Config Cfg; |
| 131 | Cfg.Diagnostics.ClangTidy.FastCheckFilter = Config::FastCheckPolicy::Loose; |
| 132 | WithContextValue WithCfg(Config::Key, std::move(Cfg)); |
| 133 | |
| 134 | const auto &AST = TU.build(); |
| 135 | const auto &SM = AST.getSourceManager(); |
| 136 | |
| 137 | auto HashLocs = Test.points(Name: "hash" ); |
| 138 | ASSERT_EQ(HashLocs.size(), Includes.size()); |
| 139 | auto IncludeRanges = Test.ranges(Name: "include" ); |
| 140 | ASSERT_EQ(IncludeRanges.size(), Includes.size()); |
| 141 | auto FileBeginLocs = Test.points(Name: "filebegin" ); |
| 142 | ASSERT_EQ(FileBeginLocs.size(), Includes.size()); |
| 143 | auto FileRanges = Test.ranges(Name: "filerange" ); |
| 144 | ASSERT_EQ(FileRanges.size(), Includes.size()); |
| 145 | |
| 146 | ASSERT_EQ(SkippedFiles.size(), Includes.size()); |
| 147 | for (size_t I = 0; I < Includes.size(); ++I) { |
| 148 | const auto &Inc = Includes[I]; |
| 149 | |
| 150 | EXPECT_EQ(Inc.HashOffset, HashLocs[I]); |
| 151 | |
| 152 | auto IncRange = IncludeRanges[I]; |
| 153 | EXPECT_THAT(Inc.IncTok.range(SM), rangeIs(IncRange)); |
| 154 | EXPECT_EQ(Inc.IncTok.kind(), tok::identifier); |
| 155 | EXPECT_EQ(Inc.IncDirective, |
| 156 | Code.substr(IncRange.Begin, IncRange.End - IncRange.Begin)); |
| 157 | |
| 158 | EXPECT_EQ(Inc.FileNameOffset, FileBeginLocs[I]); |
| 159 | EXPECT_EQ(Inc.IsAngled, Code[FileBeginLocs[I]] == '<'); |
| 160 | |
| 161 | auto FileRange = FileRanges[I]; |
| 162 | EXPECT_EQ(Inc.FileName, |
| 163 | Code.substr(FileRange.Begin, FileRange.End - FileRange.Begin)); |
| 164 | |
| 165 | EXPECT_EQ(SM.getDecomposedLoc(SkippedFiles[I].location()).second, |
| 166 | Inc.FileNameOffset); |
| 167 | // This also contains quotes/angles so increment the range by one from both |
| 168 | // sides. |
| 169 | EXPECT_EQ( |
| 170 | SkippedFiles[I].text(SM), |
| 171 | Code.substr(FileRange.Begin - 1, FileRange.End - FileRange.Begin + 2)); |
| 172 | EXPECT_EQ(SkippedFiles[I].kind(), tok::header_name); |
| 173 | } |
| 174 | } |
| 175 | } // namespace |
| 176 | } // namespace clangd |
| 177 | } // namespace clang |
| 178 | |