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