xref: /llvm-project/clang-tools-extra/clangd/unittests/ConfigYAMLTests.cpp (revision 1f90797f6a9d91d61e0f66b465b0467e4c66d0e0)
1 //===-- ConfigYAMLTests.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 #include "Annotations.h"
10 #include "ConfigFragment.h"
11 #include "ConfigTesting.h"
12 #include "Protocol.h"
13 #include "llvm/ADT/StringRef.h"
14 #include "llvm/Support/SMLoc.h"
15 #include "llvm/Support/SourceMgr.h"
16 #include "llvm/Testing/Support/SupportHelpers.h"
17 #include "gmock/gmock.h"
18 #include "gtest/gtest.h"
19 
20 namespace clang {
21 namespace clangd {
22 namespace config {
23 
24 // PrintTo is a magic identifier of GTest
25 // NOLINTNEXTLINE (readability-identifier-naming)
26 template <typename T> void PrintTo(const Located<T> &V, std::ostream *OS) {
27   *OS << ::testing::PrintToString(*V);
28 }
29 
30 namespace {
31 using ::testing::AllOf;
32 using ::testing::ElementsAre;
33 using ::testing::IsEmpty;
34 
35 MATCHER_P(val, Value, "") {
36   if (*arg == Value)
37     return true;
38   *result_listener << "value is " << *arg;
39   return false;
40 }
41 
42 MATCHER_P2(PairVal, Value1, Value2, "") {
43   if (*arg.first == Value1 && *arg.second == Value2)
44     return true;
45   *result_listener << "values are [" << *arg.first << ", " << *arg.second
46                    << "]";
47   return false;
48 }
49 
50 TEST(ParseYAML, SyntacticForms) {
51   CapturedDiags Diags;
52   const char *YAML = R"yaml(
53 If:
54   PathMatch:
55     - 'abc'
56 CompileFlags: { Add: [foo, bar] }
57 ---
58 CompileFlags:
59   Add: |
60     b
61     az
62 ---
63 Index:
64   Background: Skip
65 ---
66 Diagnostics:
67   ClangTidy:
68     CheckOptions:
69       IgnoreMacros: true
70       example-check.ExampleOption: 0
71   UnusedIncludes: Strict
72   )yaml";
73   auto Results = Fragment::parseYAML(YAML, "config.yaml", Diags.callback());
74   EXPECT_THAT(Diags.Diagnostics, IsEmpty());
75   EXPECT_THAT(Diags.Files, ElementsAre("config.yaml"));
76   ASSERT_EQ(Results.size(), 4u);
77   EXPECT_FALSE(Results[0].If.HasUnrecognizedCondition);
78   EXPECT_THAT(Results[0].If.PathMatch, ElementsAre(val("abc")));
79   EXPECT_THAT(Results[0].CompileFlags.Add, ElementsAre(val("foo"), val("bar")));
80 
81   EXPECT_THAT(Results[1].CompileFlags.Add, ElementsAre(val("b\naz\n")));
82 
83   ASSERT_TRUE(Results[2].Index.Background);
84   EXPECT_EQ("Skip", **Results[2].Index.Background);
85   EXPECT_THAT(Results[3].Diagnostics.ClangTidy.CheckOptions,
86               ElementsAre(PairVal("IgnoreMacros", "true"),
87                           PairVal("example-check.ExampleOption", "0")));
88   EXPECT_TRUE(Results[3].Diagnostics.UnusedIncludes);
89   EXPECT_EQ("Strict", **Results[3].Diagnostics.UnusedIncludes);
90 }
91 
92 TEST(ParseYAML, Locations) {
93   CapturedDiags Diags;
94   Annotations YAML(R"yaml(
95 If:
96   PathMatch: [['???bad***regex(((']]
97   )yaml");
98   auto Results =
99       Fragment::parseYAML(YAML.code(), "config.yaml", Diags.callback());
100   EXPECT_THAT(Diags.Diagnostics, IsEmpty());
101   ASSERT_EQ(Results.size(), 1u);
102   ASSERT_NE(Results.front().Source.Manager, nullptr);
103   EXPECT_EQ(toRange(Results.front().If.PathMatch.front().Range,
104                     *Results.front().Source.Manager),
105             YAML.range());
106 }
107 
108 TEST(ParseYAML, ConfigDiagnostics) {
109   CapturedDiags Diags;
110   Annotations YAML(R"yaml(
111 If:
112   $unknown[[UnknownCondition]]: "foo"
113 CompileFlags:
114   Add: 'first'
115 ---
116 CompileFlags: {$unexpected^
117 )yaml");
118   auto Results =
119       Fragment::parseYAML(YAML.code(), "config.yaml", Diags.callback());
120 
121   ASSERT_THAT(
122       Diags.Diagnostics,
123       ElementsAre(AllOf(diagMessage("Unknown If key 'UnknownCondition'"),
124                         diagKind(llvm::SourceMgr::DK_Warning),
125                         diagPos(YAML.range("unknown").start),
126                         diagRange(YAML.range("unknown"))),
127                   AllOf(diagMessage("Unexpected token. Expected Key, Flow "
128                                     "Entry, or Flow Mapping End."),
129                         diagKind(llvm::SourceMgr::DK_Error),
130                         diagPos(YAML.point("unexpected")),
131                         diagRange(std::nullopt))));
132 
133   ASSERT_EQ(Results.size(), 1u); // invalid fragment discarded.
134   EXPECT_THAT(Results.front().CompileFlags.Add, ElementsAre(val("first")));
135   EXPECT_TRUE(Results.front().If.HasUnrecognizedCondition);
136 }
137 
138 TEST(ParseYAML, Invalid) {
139   CapturedDiags Diags;
140   const char *YAML = R"yaml(
141 If:
142 
143 horrible
144 ---
145 - 1
146   )yaml";
147   auto Results = Fragment::parseYAML(YAML, "config.yaml", Diags.callback());
148   EXPECT_THAT(Diags.Diagnostics,
149               ElementsAre(diagMessage("If should be a dictionary"),
150                           diagMessage("Config should be a dictionary")));
151   ASSERT_THAT(Results, IsEmpty());
152 }
153 
154 TEST(ParseYAML, ExternalBlockNone) {
155   CapturedDiags Diags;
156   Annotations YAML(R"yaml(
157 Index:
158   External: None
159   )yaml");
160   auto Results =
161       Fragment::parseYAML(YAML.code(), "config.yaml", Diags.callback());
162   ASSERT_THAT(Diags.Diagnostics, IsEmpty());
163   ASSERT_EQ(Results.size(), 1u);
164   ASSERT_TRUE(Results[0].Index.External);
165   EXPECT_FALSE((*Results[0].Index.External)->File.has_value());
166   EXPECT_FALSE((*Results[0].Index.External)->MountPoint.has_value());
167   EXPECT_FALSE((*Results[0].Index.External)->Server.has_value());
168   EXPECT_THAT(*(*Results[0].Index.External)->IsNone, testing::Eq(true));
169 }
170 
171 TEST(ParseYAML, ExternalBlock) {
172   CapturedDiags Diags;
173   Annotations YAML(R"yaml(
174 Index:
175   External:
176     File: "foo"
177     Server: ^"bar"
178     MountPoint: "baz"
179   )yaml");
180   auto Results =
181       Fragment::parseYAML(YAML.code(), "config.yaml", Diags.callback());
182   ASSERT_EQ(Results.size(), 1u);
183   ASSERT_TRUE(Results[0].Index.External);
184   EXPECT_THAT(*(*Results[0].Index.External)->File, val("foo"));
185   EXPECT_THAT(*(*Results[0].Index.External)->MountPoint, val("baz"));
186   ASSERT_THAT(Diags.Diagnostics, IsEmpty());
187   EXPECT_THAT(*(*Results[0].Index.External)->Server, val("bar"));
188 }
189 
190 TEST(ParseYAML, AllScopes) {
191   CapturedDiags Diags;
192   Annotations YAML(R"yaml(
193 Completion:
194   AllScopes: True
195   )yaml");
196   auto Results =
197       Fragment::parseYAML(YAML.code(), "config.yaml", Diags.callback());
198   ASSERT_THAT(Diags.Diagnostics, IsEmpty());
199   ASSERT_EQ(Results.size(), 1u);
200   EXPECT_THAT(Results[0].Completion.AllScopes, llvm::ValueIs(val(true)));
201 }
202 
203 TEST(ParseYAML, AllScopesWarn) {
204   CapturedDiags Diags;
205   Annotations YAML(R"yaml(
206 Completion:
207   AllScopes: $diagrange[[Truex]]
208   )yaml");
209   auto Results =
210       Fragment::parseYAML(YAML.code(), "config.yaml", Diags.callback());
211   EXPECT_THAT(Diags.Diagnostics,
212               ElementsAre(AllOf(diagMessage("AllScopes should be a boolean"),
213                                 diagKind(llvm::SourceMgr::DK_Warning),
214                                 diagPos(YAML.range("diagrange").start),
215                                 diagRange(YAML.range("diagrange")))));
216   ASSERT_EQ(Results.size(), 1u);
217   EXPECT_THAT(Results[0].Completion.AllScopes, testing::Eq(std::nullopt));
218 }
219 
220 TEST(ParseYAML, ShowAKA) {
221   CapturedDiags Diags;
222   Annotations YAML(R"yaml(
223 Hover:
224   ShowAKA: True
225   )yaml");
226   auto Results =
227       Fragment::parseYAML(YAML.code(), "config.yaml", Diags.callback());
228   ASSERT_THAT(Diags.Diagnostics, IsEmpty());
229   ASSERT_EQ(Results.size(), 1u);
230   EXPECT_THAT(Results[0].Hover.ShowAKA, llvm::ValueIs(val(true)));
231 }
232 
233 TEST(ParseYAML, InlayHints) {
234   CapturedDiags Diags;
235   Annotations YAML(R"yaml(
236 InlayHints:
237   Enabled: No
238   ParameterNames: Yes
239   )yaml");
240   auto Results =
241       Fragment::parseYAML(YAML.code(), "config.yaml", Diags.callback());
242   ASSERT_THAT(Diags.Diagnostics, IsEmpty());
243   ASSERT_EQ(Results.size(), 1u);
244   EXPECT_THAT(Results[0].InlayHints.Enabled, llvm::ValueIs(val(false)));
245   EXPECT_THAT(Results[0].InlayHints.ParameterNames, llvm::ValueIs(val(true)));
246   EXPECT_EQ(Results[0].InlayHints.DeducedTypes, std::nullopt);
247 }
248 
249 TEST(ParseYAML, SemanticTokens) {
250   CapturedDiags Diags;
251   Annotations YAML(R"yaml(
252 SemanticTokens:
253   DisabledKinds: [ Operator, InactiveCode]
254   DisabledModifiers: Readonly
255   )yaml");
256   auto Results =
257       Fragment::parseYAML(YAML.code(), "config.yaml", Diags.callback());
258   ASSERT_THAT(Diags.Diagnostics, IsEmpty());
259   ASSERT_EQ(Results.size(), 1u);
260   EXPECT_THAT(Results[0].SemanticTokens.DisabledKinds,
261               ElementsAre(val("Operator"), val("InactiveCode")));
262   EXPECT_THAT(Results[0].SemanticTokens.DisabledModifiers,
263               ElementsAre(val("Readonly")));
264 }
265 
266 TEST(ParseYAML, IncludesIgnoreHeader) {
267   CapturedDiags Diags;
268   Annotations YAML(R"yaml(
269 Diagnostics:
270   Includes:
271     IgnoreHeader: [foo, bar]
272   )yaml");
273   auto Results =
274       Fragment::parseYAML(YAML.code(), "config.yaml", Diags.callback());
275   ASSERT_THAT(Diags.Diagnostics, IsEmpty());
276   ASSERT_EQ(Results.size(), 1u);
277   EXPECT_THAT(Results[0].Diagnostics.Includes.IgnoreHeader,
278               ElementsAre(val("foo"), val("bar")));
279 }
280 
281 TEST(ParseYAML, IncludesAnalyzeAngledIncludes) {
282   CapturedDiags Diags;
283   Annotations YAML(R"yaml(
284 Diagnostics:
285   Includes:
286     AnalyzeAngledIncludes: true
287   )yaml");
288   auto Results =
289       Fragment::parseYAML(YAML.code(), "config.yaml", Diags.callback());
290   ASSERT_THAT(Diags.Diagnostics, IsEmpty());
291   ASSERT_EQ(Results.size(), 1u);
292   EXPECT_THAT(Results[0].Diagnostics.Includes.AnalyzeAngledIncludes,
293               llvm::ValueIs(val(true)));
294 }
295 
296 TEST(ParseYAML, Style) {
297   CapturedDiags Diags;
298   Annotations YAML(R"yaml(
299 Style:
300   FullyQualifiedNamespaces: [foo, bar]
301   AngledHeaders: ["foo", "bar"]
302   QuotedHeaders: ["baz", "baar"])yaml");
303   auto Results =
304       Fragment::parseYAML(YAML.code(), "config.yaml", Diags.callback());
305   ASSERT_THAT(Diags.Diagnostics, IsEmpty());
306   ASSERT_EQ(Results.size(), 1u);
307   EXPECT_THAT(Results[0].Style.FullyQualifiedNamespaces,
308               ElementsAre(val("foo"), val("bar")));
309   EXPECT_THAT(Results[0].Style.AngledHeaders,
310               ElementsAre(val("foo"), val("bar")));
311   EXPECT_THAT(Results[0].Style.QuotedHeaders,
312               ElementsAre(val("baz"), val("baar")));
313 }
314 } // namespace
315 } // namespace config
316 } // namespace clangd
317 } // namespace clang
318