| 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, CodePatterns) { |
| 221 | CapturedDiags Diags; |
| 222 | Annotations YAML(R"yaml( |
| 223 | Completion: |
| 224 | CodePatterns: None |
| 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].Completion.CodePatterns, llvm::ValueIs(val("None" ))); |
| 231 | } |
| 232 | |
| 233 | TEST(ParseYAML, ShowAKA) { |
| 234 | CapturedDiags Diags; |
| 235 | Annotations YAML(R"yaml( |
| 236 | Hover: |
| 237 | ShowAKA: True |
| 238 | )yaml" ); |
| 239 | auto Results = |
| 240 | Fragment::parseYAML(YAML: YAML.code(), BufferName: "config.yaml" , Diags.callback()); |
| 241 | ASSERT_THAT(Diags.Diagnostics, IsEmpty()); |
| 242 | ASSERT_EQ(Results.size(), 1u); |
| 243 | EXPECT_THAT(Results[0].Hover.ShowAKA, llvm::ValueIs(val(true))); |
| 244 | } |
| 245 | |
| 246 | TEST(ParseYAML, InlayHints) { |
| 247 | CapturedDiags Diags; |
| 248 | Annotations YAML(R"yaml( |
| 249 | InlayHints: |
| 250 | Enabled: No |
| 251 | ParameterNames: Yes |
| 252 | )yaml" ); |
| 253 | auto Results = |
| 254 | Fragment::parseYAML(YAML: YAML.code(), BufferName: "config.yaml" , Diags.callback()); |
| 255 | ASSERT_THAT(Diags.Diagnostics, IsEmpty()); |
| 256 | ASSERT_EQ(Results.size(), 1u); |
| 257 | EXPECT_THAT(Results[0].InlayHints.Enabled, llvm::ValueIs(val(false))); |
| 258 | EXPECT_THAT(Results[0].InlayHints.ParameterNames, llvm::ValueIs(val(true))); |
| 259 | EXPECT_EQ(Results[0].InlayHints.DeducedTypes, std::nullopt); |
| 260 | } |
| 261 | |
| 262 | TEST(ParseYAML, SemanticTokens) { |
| 263 | CapturedDiags Diags; |
| 264 | Annotations YAML(R"yaml( |
| 265 | SemanticTokens: |
| 266 | DisabledKinds: [ Operator, InactiveCode] |
| 267 | DisabledModifiers: Readonly |
| 268 | )yaml" ); |
| 269 | auto Results = |
| 270 | Fragment::parseYAML(YAML: YAML.code(), BufferName: "config.yaml" , Diags.callback()); |
| 271 | ASSERT_THAT(Diags.Diagnostics, IsEmpty()); |
| 272 | ASSERT_EQ(Results.size(), 1u); |
| 273 | EXPECT_THAT(Results[0].SemanticTokens.DisabledKinds, |
| 274 | ElementsAre(val("Operator" ), val("InactiveCode" ))); |
| 275 | EXPECT_THAT(Results[0].SemanticTokens.DisabledModifiers, |
| 276 | ElementsAre(val("Readonly" ))); |
| 277 | } |
| 278 | |
| 279 | TEST(ParseYAML, IncludesIgnoreHeader) { |
| 280 | CapturedDiags Diags; |
| 281 | Annotations YAML(R"yaml( |
| 282 | Diagnostics: |
| 283 | Includes: |
| 284 | IgnoreHeader: [foo, bar] |
| 285 | )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].Diagnostics.Includes.IgnoreHeader, |
| 291 | ElementsAre(val("foo" ), val("bar" ))); |
| 292 | } |
| 293 | |
| 294 | TEST(ParseYAML, IncludesAnalyzeAngledIncludes) { |
| 295 | CapturedDiags Diags; |
| 296 | Annotations YAML(R"yaml( |
| 297 | Diagnostics: |
| 298 | Includes: |
| 299 | AnalyzeAngledIncludes: true |
| 300 | )yaml" ); |
| 301 | auto Results = |
| 302 | Fragment::parseYAML(YAML: YAML.code(), BufferName: "config.yaml" , Diags.callback()); |
| 303 | ASSERT_THAT(Diags.Diagnostics, IsEmpty()); |
| 304 | ASSERT_EQ(Results.size(), 1u); |
| 305 | EXPECT_THAT(Results[0].Diagnostics.Includes.AnalyzeAngledIncludes, |
| 306 | llvm::ValueIs(val(true))); |
| 307 | } |
| 308 | |
| 309 | TEST(ParseYAML, Style) { |
| 310 | CapturedDiags Diags; |
| 311 | Annotations YAML(R"yaml( |
| 312 | Style: |
| 313 | FullyQualifiedNamespaces: [foo, bar] |
| 314 | AngledHeaders: ["foo", "bar"] |
| 315 | QuotedHeaders: ["baz", "baar"])yaml" ); |
| 316 | auto Results = |
| 317 | Fragment::parseYAML(YAML: YAML.code(), BufferName: "config.yaml" , Diags.callback()); |
| 318 | ASSERT_THAT(Diags.Diagnostics, IsEmpty()); |
| 319 | ASSERT_EQ(Results.size(), 1u); |
| 320 | EXPECT_THAT(Results[0].Style.FullyQualifiedNamespaces, |
| 321 | ElementsAre(val("foo" ), val("bar" ))); |
| 322 | EXPECT_THAT(Results[0].Style.AngledHeaders, |
| 323 | ElementsAre(val("foo" ), val("bar" ))); |
| 324 | EXPECT_THAT(Results[0].Style.QuotedHeaders, |
| 325 | ElementsAre(val("baz" ), val("baar" ))); |
| 326 | } |
| 327 | } // namespace |
| 328 | } // namespace config |
| 329 | } // namespace clangd |
| 330 | } // namespace clang |
| 331 | |