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
45namespace clang::clangd {
46namespace {
47struct 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};
66static std::vector<Inclusion> Includes;
67static std::vector<syntax::Token> SkippedFiles;
68struct 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};
86struct 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};
94llvm::StringLiteral CheckName = "replay-preamble-check";
95struct ReplayPreambleModule : public tidy::ClangTidyModule {
96 void
97 addCheckFactories(tidy::ClangTidyCheckFactories &CheckFactories) override {
98 CheckFactories.registerCheck<ReplayPreambleCheck>(CheckName);
99 }
100};
101static tidy::ClangTidyModuleRegistry::Add<ReplayPreambleModule>
102 X("replay-preamble-module", "");
103
104MATCHER_P(rangeIs, R, "") {
105 return arg.beginOffset() == R.Begin && arg.endOffset() == R.End;
106}
107
108TEST(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

source code of clang-tools-extra/clangd/unittests/ReplayPeambleTests.cpp