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 | |