xref: /llvm-project/clang-tools-extra/clangd/unittests/ReplayPeambleTests.cpp (revision aaa4ff88d6a2ef69053211e7bbee623f24723b51)
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 {
Inclusionclang::clangd::__anonc0cd58060111::Inclusion51   Inclusion(const SourceManager &SM, SourceLocation HashLoc,
52             const Token &IncludeTok, llvm::StringRef FileName, bool IsAngled,
53             CharSourceRange FilenameRange)
54       : HashOffset(SM.getDecomposedLoc(HashLoc).second), IncTok(IncludeTok),
55         IncDirective(IncludeTok.getIdentifierInfo()->getName()),
56         FileNameOffset(SM.getDecomposedLoc(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;
ReplayPreamblePPCallbackclang::clangd::__anonc0cd58060111::ReplayPreamblePPCallback73   explicit ReplayPreamblePPCallback(const SourceManager &SM) : SM(SM) {}
74 
InclusionDirectiveclang::clangd::__anonc0cd58060111::ReplayPreamblePPCallback75   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(SM, HashLoc, IncludeTok, FileName, IsAngled,
81                           FilenameRange);
82   }
83 
FileSkippedclang::clangd::__anonc0cd58060111::ReplayPreamblePPCallback84   void FileSkipped(const FileEntryRef &, const Token &FilenameTok,
85                    SrcMgr::CharacteristicKind) override {
86     SkippedFiles.emplace_back(FilenameTok);
87   }
88 };
89 struct ReplayPreambleCheck : public tidy::ClangTidyCheck {
ReplayPreambleCheckclang::clangd::__anonc0cd58060111::ReplayPreambleCheck90   ReplayPreambleCheck(StringRef Name, tidy::ClangTidyContext *Context)
91       : ClangTidyCheck(Name, Context) {}
registerPPCallbacksclang::clangd::__anonc0cd58060111::ReplayPreambleCheck92   void registerPPCallbacks(const SourceManager &SM, Preprocessor *PP,
93                            Preprocessor *ModuleExpanderPP) override {
94     PP->addPPCallbacks(::std::make_unique<ReplayPreamblePPCallback>(SM));
95   }
96 };
97 llvm::StringLiteral CheckName = "replay-preamble-check";
98 struct ReplayPreambleModule : public tidy::ClangTidyModule {
99   void
addCheckFactoriesclang::clangd::__anonc0cd58060111::ReplayPreambleModule100   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 
TEST(ReplayPreambleTest,IncludesAndSkippedFiles)111 TEST(ReplayPreambleTest, IncludesAndSkippedFiles) {
112   TestTU TU;
113   // This check records inclusion directives replayed by clangd.
114   TU.ClangTidyProvider = addTidyChecks(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("hash");
138   ASSERT_EQ(HashLocs.size(), Includes.size());
139   auto IncludeRanges = Test.ranges("include");
140   ASSERT_EQ(IncludeRanges.size(), Includes.size());
141   auto FileBeginLocs = Test.points("filebegin");
142   ASSERT_EQ(FileBeginLocs.size(), Includes.size());
143   auto FileRanges = Test.ranges("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