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, BufferName: "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: YAML.code(), BufferName: "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: YAML.code(), BufferName: "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, BufferName: "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: YAML.code(), BufferName: "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: YAML.code(), BufferName: "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: YAML.code(), BufferName: "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: YAML.code(), BufferName: "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: YAML.code(), BufferName: "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: YAML.code(), BufferName: "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: YAML.code(), BufferName: "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: YAML.code(), BufferName: "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, Style) { |
282 | CapturedDiags Diags; |
283 | Annotations YAML(R"yaml( |
284 | Style: |
285 | FullyQualifiedNamespaces: [foo, bar])yaml" ); |
286 | auto Results = |
287 | Fragment::parseYAML(YAML: YAML.code(), BufferName: "config.yaml" , Diags.callback()); |
288 | ASSERT_THAT(Diags.Diagnostics, IsEmpty()); |
289 | ASSERT_EQ(Results.size(), 1u); |
290 | EXPECT_THAT(Results[0].Style.FullyQualifiedNamespaces, |
291 | ElementsAre(val("foo" ), val("bar" ))); |
292 | } |
293 | } // namespace |
294 | } // namespace config |
295 | } // namespace clangd |
296 | } // namespace clang |
297 | |