Sam McCall | fad12f8 | 2017-12-01 17:08:02 +0000 | [diff] [blame] | 1 | //===-- FuzzyMatchTests.cpp - String fuzzy matcher tests --------*- C++ -*-===// |
| 2 | // |
| 3 | // The LLVM Compiler Infrastructure |
| 4 | // |
| 5 | // This file is distributed under the University of Illinois Open Source |
| 6 | // License. See LICENSE.TXT for details. |
| 7 | // |
| 8 | //===----------------------------------------------------------------------===// |
| 9 | |
| 10 | #include "FuzzyMatch.h" |
| 11 | |
| 12 | #include "llvm/ADT/StringExtras.h" |
| 13 | #include "gmock/gmock.h" |
| 14 | #include "gtest/gtest.h" |
| 15 | |
| 16 | namespace clang { |
| 17 | namespace clangd { |
| 18 | namespace { |
| 19 | using namespace llvm; |
| 20 | using testing::Not; |
| 21 | |
| 22 | struct ExpectedMatch { |
Sam McCall | eff7cf9 | 2018-03-05 17:34:33 +0000 | [diff] [blame] | 23 | // Annotations are optional, and will not be asserted if absent. |
| 24 | ExpectedMatch(StringRef Match) : Word(Match), Annotated(Match) { |
Sam McCall | fad12f8 | 2017-12-01 17:08:02 +0000 | [diff] [blame] | 25 | for (char C : "[]") |
| 26 | Word.erase(std::remove(Word.begin(), Word.end(), C), Word.end()); |
Sam McCall | eff7cf9 | 2018-03-05 17:34:33 +0000 | [diff] [blame] | 27 | if (Word.size() == Annotated->size()) |
| 28 | Annotated = None; |
| 29 | } |
| 30 | bool accepts(StringRef ActualAnnotated) const { |
| 31 | return !Annotated || ActualAnnotated == *Annotated; |
Sam McCall | fad12f8 | 2017-12-01 17:08:02 +0000 | [diff] [blame] | 32 | } |
Sam McCall | eff7cf9 | 2018-03-05 17:34:33 +0000 | [diff] [blame] | 33 | |
| 34 | friend raw_ostream &operator<<(raw_ostream &OS, const ExpectedMatch &M) { |
Sam McCall | 89f02bf | 2018-03-06 14:30:07 +0000 | [diff] [blame] | 35 | OS << "'" << M.Word; |
Sam McCall | eff7cf9 | 2018-03-05 17:34:33 +0000 | [diff] [blame] | 36 | if (M.Annotated) |
| 37 | OS << "' as " << *M.Annotated; |
Sam McCall | 89f02bf | 2018-03-06 14:30:07 +0000 | [diff] [blame] | 38 | return OS; |
Sam McCall | eff7cf9 | 2018-03-05 17:34:33 +0000 | [diff] [blame] | 39 | } |
| 40 | |
Sam McCall | 89f02bf | 2018-03-06 14:30:07 +0000 | [diff] [blame] | 41 | std::string Word; |
| 42 | |
Sam McCall | eff7cf9 | 2018-03-05 17:34:33 +0000 | [diff] [blame] | 43 | private: |
| 44 | Optional<StringRef> Annotated; |
Sam McCall | fad12f8 | 2017-12-01 17:08:02 +0000 | [diff] [blame] | 45 | }; |
Sam McCall | fad12f8 | 2017-12-01 17:08:02 +0000 | [diff] [blame] | 46 | |
| 47 | struct MatchesMatcher : public testing::MatcherInterface<StringRef> { |
| 48 | ExpectedMatch Candidate; |
Sam McCall | a34ac6d | 2018-06-06 12:38:37 +0000 | [diff] [blame] | 49 | Optional<float> Score; |
| 50 | MatchesMatcher(ExpectedMatch Candidate, Optional<float> Score) |
| 51 | : Candidate(std::move(Candidate)), Score(Score) {} |
Sam McCall | fad12f8 | 2017-12-01 17:08:02 +0000 | [diff] [blame] | 52 | |
| 53 | void DescribeTo(::std::ostream *OS) const override { |
| 54 | raw_os_ostream(*OS) << "Matches " << Candidate; |
Sam McCall | a34ac6d | 2018-06-06 12:38:37 +0000 | [diff] [blame] | 55 | if (Score) |
| 56 | *OS << " with score " << *Score; |
Sam McCall | fad12f8 | 2017-12-01 17:08:02 +0000 | [diff] [blame] | 57 | } |
| 58 | |
| 59 | bool MatchAndExplain(StringRef Pattern, |
| 60 | testing::MatchResultListener *L) const override { |
| 61 | std::unique_ptr<raw_ostream> OS( |
| 62 | L->stream() ? (raw_ostream *)(new raw_os_ostream(*L->stream())) |
| 63 | : new raw_null_ostream()); |
| 64 | FuzzyMatcher Matcher(Pattern); |
| 65 | auto Result = Matcher.match(Candidate.Word); |
| 66 | auto AnnotatedMatch = Matcher.dumpLast(*OS << "\n"); |
Sam McCall | a34ac6d | 2018-06-06 12:38:37 +0000 | [diff] [blame] | 67 | return Result && Candidate.accepts(AnnotatedMatch) && |
| 68 | (!Score || testing::Value(*Result, testing::FloatEq(*Score))); |
Sam McCall | fad12f8 | 2017-12-01 17:08:02 +0000 | [diff] [blame] | 69 | } |
| 70 | }; |
| 71 | |
Sam McCall | a34ac6d | 2018-06-06 12:38:37 +0000 | [diff] [blame] | 72 | // Accepts patterns that match a given word, optionally requiring a score. |
Sam McCall | fad12f8 | 2017-12-01 17:08:02 +0000 | [diff] [blame] | 73 | // Dumps the debug tables on match failure. |
Sam McCall | a34ac6d | 2018-06-06 12:38:37 +0000 | [diff] [blame] | 74 | testing::Matcher<StringRef> matches(StringRef M, Optional<float> Score = {}) { |
| 75 | return testing::MakeMatcher<StringRef>(new MatchesMatcher(M, Score)); |
Sam McCall | fad12f8 | 2017-12-01 17:08:02 +0000 | [diff] [blame] | 76 | } |
| 77 | |
| 78 | TEST(FuzzyMatch, Matches) { |
Sam McCall | 7be1b56 | 2018-01-13 16:46:26 +0000 | [diff] [blame] | 79 | EXPECT_THAT("", matches("unique_ptr")); |
Sam McCall | fad12f8 | 2017-12-01 17:08:02 +0000 | [diff] [blame] | 80 | EXPECT_THAT("u_p", matches("[u]nique[_p]tr")); |
| 81 | EXPECT_THAT("up", matches("[u]nique_[p]tr")); |
Sam McCall | 1503451 | 2018-06-14 13:50:30 +0000 | [diff] [blame] | 82 | EXPECT_THAT("uq", Not(matches("unique_ptr"))); |
Sam McCall | fad12f8 | 2017-12-01 17:08:02 +0000 | [diff] [blame] | 83 | EXPECT_THAT("qp", Not(matches("unique_ptr"))); |
| 84 | EXPECT_THAT("log", Not(matches("SVGFEMorphologyElement"))); |
| 85 | |
| 86 | EXPECT_THAT("tit", matches("win.[tit]")); |
| 87 | EXPECT_THAT("title", matches("win.[title]")); |
| 88 | EXPECT_THAT("WordCla", matches("[Word]Character[Cla]ssifier")); |
| 89 | EXPECT_THAT("WordCCla", matches("[WordC]haracter[Cla]ssifier")); |
| 90 | |
| 91 | EXPECT_THAT("dete", Not(matches("editor.quickSuggestionsDelay"))); |
| 92 | |
| 93 | EXPECT_THAT("highlight", matches("editorHover[Highlight]")); |
| 94 | EXPECT_THAT("hhighlight", matches("editor[H]over[Highlight]")); |
| 95 | EXPECT_THAT("dhhighlight", Not(matches("editorHoverHighlight"))); |
| 96 | |
| 97 | EXPECT_THAT("-moz", matches("[-moz]-foo")); |
| 98 | EXPECT_THAT("moz", matches("-[moz]-foo")); |
| 99 | EXPECT_THAT("moza", matches("-[moz]-[a]nimation")); |
| 100 | |
| 101 | EXPECT_THAT("ab", matches("[ab]A")); |
Sam McCall | 1503451 | 2018-06-14 13:50:30 +0000 | [diff] [blame] | 102 | EXPECT_THAT("ccm", Not(matches("cacmelCase"))); |
Sam McCall | fad12f8 | 2017-12-01 17:08:02 +0000 | [diff] [blame] | 103 | EXPECT_THAT("bti", Not(matches("the_black_knight"))); |
| 104 | EXPECT_THAT("ccm", Not(matches("camelCase"))); |
| 105 | EXPECT_THAT("cmcm", Not(matches("camelCase"))); |
| 106 | EXPECT_THAT("BK", matches("the_[b]lack_[k]night")); |
| 107 | EXPECT_THAT("KeyboardLayout=", Not(matches("KeyboardLayout"))); |
| 108 | EXPECT_THAT("LLL", matches("SVisual[L]ogger[L]ogs[L]ist")); |
| 109 | EXPECT_THAT("LLLL", Not(matches("SVilLoLosLi"))); |
| 110 | EXPECT_THAT("LLLL", Not(matches("SVisualLoggerLogsList"))); |
| 111 | EXPECT_THAT("TEdit", matches("[T]ext[Edit]")); |
| 112 | EXPECT_THAT("TEdit", matches("[T]ext[Edit]or")); |
Sam McCall | 1503451 | 2018-06-14 13:50:30 +0000 | [diff] [blame] | 113 | EXPECT_THAT("TEdit", Not(matches("[T]ext[edit]"))); |
Sam McCall | fad12f8 | 2017-12-01 17:08:02 +0000 | [diff] [blame] | 114 | EXPECT_THAT("TEdit", matches("[t]ext_[edit]")); |
Sam McCall | 1503451 | 2018-06-14 13:50:30 +0000 | [diff] [blame] | 115 | EXPECT_THAT("TEditDt", matches("[T]ext[Edit]or[D]ecoration[T]ype")); |
Sam McCall | fad12f8 | 2017-12-01 17:08:02 +0000 | [diff] [blame] | 116 | EXPECT_THAT("TEdit", matches("[T]ext[Edit]orDecorationType")); |
| 117 | EXPECT_THAT("Tedit", matches("[T]ext[Edit]")); |
| 118 | EXPECT_THAT("ba", Not(matches("?AB?"))); |
| 119 | EXPECT_THAT("bkn", matches("the_[b]lack_[kn]ight")); |
Sam McCall | 1503451 | 2018-06-14 13:50:30 +0000 | [diff] [blame] | 120 | EXPECT_THAT("bt", Not(matches("the_[b]lack_knigh[t]"))); |
| 121 | EXPECT_THAT("ccm", Not(matches("[c]amelCase[cm]"))); |
| 122 | EXPECT_THAT("fdm", Not(matches("[f]in[dM]odel"))); |
| 123 | EXPECT_THAT("fob", Not(matches("[fo]o[b]ar"))); |
Sam McCall | fad12f8 | 2017-12-01 17:08:02 +0000 | [diff] [blame] | 124 | EXPECT_THAT("fobz", Not(matches("foobar"))); |
| 125 | EXPECT_THAT("foobar", matches("[foobar]")); |
| 126 | EXPECT_THAT("form", matches("editor.[form]atOnSave")); |
| 127 | EXPECT_THAT("g p", matches("[G]it:[ P]ull")); |
| 128 | EXPECT_THAT("g p", matches("[G]it:[ P]ull")); |
| 129 | EXPECT_THAT("gip", matches("[Gi]t: [P]ull")); |
| 130 | EXPECT_THAT("gip", matches("[Gi]t: [P]ull")); |
| 131 | EXPECT_THAT("gp", matches("[G]it: [P]ull")); |
| 132 | EXPECT_THAT("gp", matches("[G]it_Git_[P]ull")); |
| 133 | EXPECT_THAT("is", matches("[I]mport[S]tatement")); |
| 134 | EXPECT_THAT("is", matches("[is]Valid")); |
Sam McCall | 1503451 | 2018-06-14 13:50:30 +0000 | [diff] [blame] | 135 | EXPECT_THAT("lowrd", Not(matches("[low]Wo[rd]"))); |
| 136 | EXPECT_THAT("myvable", Not(matches("[myva]ria[ble]"))); |
Sam McCall | fad12f8 | 2017-12-01 17:08:02 +0000 | [diff] [blame] | 137 | EXPECT_THAT("no", Not(matches(""))); |
| 138 | EXPECT_THAT("no", Not(matches("match"))); |
| 139 | EXPECT_THAT("ob", Not(matches("foobar"))); |
| 140 | EXPECT_THAT("sl", matches("[S]Visual[L]oggerLogsList")); |
Sam McCall | 1503451 | 2018-06-14 13:50:30 +0000 | [diff] [blame] | 141 | EXPECT_THAT("sllll", matches("[S]Visua[L]ogger[Ll]ama[L]ist")); |
| 142 | EXPECT_THAT("THRE", matches("H[T]ML[HRE]lement")); |
Sam McCall | eff7cf9 | 2018-03-05 17:34:33 +0000 | [diff] [blame] | 143 | EXPECT_THAT("b", Not(matches("NDEBUG"))); |
Sam McCall | fad12f8 | 2017-12-01 17:08:02 +0000 | [diff] [blame] | 144 | EXPECT_THAT("Three", matches("[Three]")); |
| 145 | EXPECT_THAT("fo", Not(matches("barfoo"))); |
| 146 | EXPECT_THAT("fo", matches("bar_[fo]o")); |
| 147 | EXPECT_THAT("fo", matches("bar_[Fo]o")); |
| 148 | EXPECT_THAT("fo", matches("bar [fo]o")); |
| 149 | EXPECT_THAT("fo", matches("bar.[fo]o")); |
| 150 | EXPECT_THAT("fo", matches("bar/[fo]o")); |
| 151 | EXPECT_THAT("fo", matches("bar\\[fo]o")); |
| 152 | |
| 153 | EXPECT_THAT( |
| 154 | "aaaaaa", |
| 155 | matches("[aaaaaa]aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" |
| 156 | "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa")); |
| 157 | EXPECT_THAT("baba", Not(matches("ababababab"))); |
| 158 | EXPECT_THAT("fsfsfs", Not(matches("dsafdsafdsafdsafdsafdsafdsafasdfdsa"))); |
| 159 | EXPECT_THAT("fsfsfsfsfsfsfsf", |
| 160 | Not(matches("dsafdsafdsafdsafdsafdsafdsafasdfdsafdsafdsafdsafdsfd" |
| 161 | "safdsfdfdfasdnfdsajfndsjnafjndsajlknfdsa"))); |
| 162 | |
| 163 | EXPECT_THAT(" g", matches("[ g]roup")); |
| 164 | EXPECT_THAT("g", matches(" [g]roup")); |
| 165 | EXPECT_THAT("g g", Not(matches(" groupGroup"))); |
| 166 | EXPECT_THAT("g g", matches(" [g]roup[ G]roup")); |
| 167 | EXPECT_THAT(" g g", matches("[ ] [g]roup[ G]roup")); |
| 168 | EXPECT_THAT("zz", matches("[zz]Group")); |
| 169 | EXPECT_THAT("zzg", matches("[zzG]roup")); |
| 170 | EXPECT_THAT("g", matches("zz[G]roup")); |
| 171 | |
| 172 | EXPECT_THAT("aaaa", matches("_a_[aaaa]")); // Prefer consecutive. |
Sam McCall | eff7cf9 | 2018-03-05 17:34:33 +0000 | [diff] [blame] | 173 | // These would ideally match, but would need special segmentation rules. |
| 174 | EXPECT_THAT("printf", Not(matches("s[printf]"))); |
| 175 | EXPECT_THAT("str", Not(matches("o[str]eam"))); |
Sam McCall | 1503451 | 2018-06-14 13:50:30 +0000 | [diff] [blame] | 176 | EXPECT_THAT("strcpy", Not(matches("strncpy"))); |
| 177 | EXPECT_THAT("std", Not(matches("PTHREAD_MUTEX_STALLED"))); |
| 178 | EXPECT_THAT("std", Not(matches("pthread_condattr_setpshared"))); |
Sam McCall | fad12f8 | 2017-12-01 17:08:02 +0000 | [diff] [blame] | 179 | } |
| 180 | |
| 181 | struct RankMatcher : public testing::MatcherInterface<StringRef> { |
| 182 | std::vector<ExpectedMatch> RankedStrings; |
| 183 | RankMatcher(std::initializer_list<ExpectedMatch> RankedStrings) |
| 184 | : RankedStrings(RankedStrings) {} |
| 185 | |
| 186 | void DescribeTo(::std::ostream *OS) const override { |
| 187 | raw_os_ostream O(*OS); |
| 188 | O << "Ranks strings in order: ["; |
| 189 | for (const auto &Str : RankedStrings) |
| 190 | O << "\n\t" << Str; |
| 191 | O << "\n]"; |
| 192 | } |
| 193 | |
| 194 | bool MatchAndExplain(StringRef Pattern, |
| 195 | testing::MatchResultListener *L) const override { |
| 196 | std::unique_ptr<raw_ostream> OS( |
| 197 | L->stream() ? (raw_ostream *)(new raw_os_ostream(*L->stream())) |
| 198 | : new raw_null_ostream()); |
| 199 | FuzzyMatcher Matcher(Pattern); |
| 200 | const ExpectedMatch *LastMatch; |
| 201 | Optional<float> LastScore; |
| 202 | bool Ok = true; |
| 203 | for (const auto &Str : RankedStrings) { |
| 204 | auto Score = Matcher.match(Str.Word); |
| 205 | if (!Score) { |
| 206 | *OS << "\nDoesn't match '" << Str.Word << "'"; |
| 207 | Matcher.dumpLast(*OS << "\n"); |
| 208 | Ok = false; |
| 209 | } else { |
| 210 | std::string Buf; |
| 211 | llvm::raw_string_ostream Info(Buf); |
| 212 | auto AnnotatedMatch = Matcher.dumpLast(Info); |
| 213 | |
Sam McCall | eff7cf9 | 2018-03-05 17:34:33 +0000 | [diff] [blame] | 214 | if (!Str.accepts(AnnotatedMatch)) { |
| 215 | *OS << "\nDoesn't match " << Str << ", but " << AnnotatedMatch << "\n" |
Sam McCall | fad12f8 | 2017-12-01 17:08:02 +0000 | [diff] [blame] | 216 | << Info.str(); |
| 217 | Ok = false; |
| 218 | } else if (LastScore && *LastScore < *Score) { |
| 219 | *OS << "\nRanks '" << Str.Word << "'=" << *Score << " above '" |
| 220 | << LastMatch->Word << "'=" << *LastScore << "\n" |
| 221 | << Info.str(); |
| 222 | Matcher.match(LastMatch->Word); |
| 223 | Matcher.dumpLast(*OS << "\n"); |
| 224 | Ok = false; |
| 225 | } |
| 226 | } |
| 227 | LastMatch = &Str; |
| 228 | LastScore = Score; |
| 229 | } |
| 230 | return Ok; |
| 231 | } |
| 232 | }; |
| 233 | |
| 234 | // Accepts patterns that match all the strings and rank them in the given order. |
| 235 | // Dumps the debug tables on match failure. |
| 236 | template <typename... T> testing::Matcher<StringRef> ranks(T... RankedStrings) { |
| 237 | return testing::MakeMatcher<StringRef>( |
| 238 | new RankMatcher{ExpectedMatch(RankedStrings)...}); |
| 239 | } |
| 240 | |
| 241 | TEST(FuzzyMatch, Ranking) { |
Sam McCall | fad12f8 | 2017-12-01 17:08:02 +0000 | [diff] [blame] | 242 | EXPECT_THAT("cons", |
| 243 | ranks("[cons]ole", "[Cons]ole", "ArrayBuffer[Cons]tructor")); |
| 244 | EXPECT_THAT("foo", ranks("[foo]", "[Foo]")); |
Sam McCall | 1503451 | 2018-06-14 13:50:30 +0000 | [diff] [blame] | 245 | EXPECT_THAT("onMes", |
| 246 | ranks("[onMes]sage", "[onmes]sage", "[on]This[M]ega[Es]capes")); |
Sam McCall | fad12f8 | 2017-12-01 17:08:02 +0000 | [diff] [blame] | 247 | EXPECT_THAT("CC", ranks("[C]amel[C]ase", "[c]amel[C]ase")); |
| 248 | EXPECT_THAT("cC", ranks("[c]amel[C]ase", "[C]amel[C]ase")); |
Sam McCall | a34ac6d | 2018-06-06 12:38:37 +0000 | [diff] [blame] | 249 | EXPECT_THAT("p", ranks("[p]", "[p]arse", "[p]osix", "[p]afdsa", "[p]ath")); |
Sam McCall | fad12f8 | 2017-12-01 17:08:02 +0000 | [diff] [blame] | 250 | EXPECT_THAT("pa", ranks("[pa]rse", "[pa]th", "[pa]fdsa")); |
| 251 | EXPECT_THAT("log", ranks("[log]", "Scroll[Log]icalPosition")); |
| 252 | EXPECT_THAT("e", ranks("[e]lse", "Abstract[E]lement")); |
| 253 | EXPECT_THAT("workbench.sideb", |
| 254 | ranks("[workbench.sideB]ar.location", |
| 255 | "[workbench.]editor.default[SideB]ySideLayout")); |
| 256 | EXPECT_THAT("editor.r", ranks("[editor.r]enderControlCharacter", |
| 257 | "[editor.]overview[R]ulerlanes", |
| 258 | "diff[Editor.r]enderSideBySide")); |
| 259 | EXPECT_THAT("-mo", ranks("[-mo]z-columns", "[-]ms-ime-[mo]de")); |
| 260 | EXPECT_THAT("convertModelPosition", |
| 261 | ranks("[convertModelPosition]ToViewPosition", |
| 262 | "[convert]ViewTo[ModelPosition]")); |
| 263 | EXPECT_THAT("is", ranks("[is]ValidViewletId", "[i]mport [s]tatement")); |
Sam McCall | 1503451 | 2018-06-14 13:50:30 +0000 | [diff] [blame] | 264 | EXPECT_THAT("strcpy", ranks("[strcpy]", "[strcpy]_s")); |
Sam McCall | fad12f8 | 2017-12-01 17:08:02 +0000 | [diff] [blame] | 265 | } |
| 266 | |
Sam McCall | a34ac6d | 2018-06-06 12:38:37 +0000 | [diff] [blame] | 267 | // Verify some bounds so we know scores fall in the right range. |
| 268 | // Testing exact scores is fragile, so we prefer Ranking tests. |
| 269 | TEST(FuzzyMatch, Scoring) { |
Sam McCall | 1503451 | 2018-06-14 13:50:30 +0000 | [diff] [blame] | 270 | EXPECT_THAT("abs", matches("[a]w[B]xYz[S]", 0.f)); |
Sam McCall | a34ac6d | 2018-06-06 12:38:37 +0000 | [diff] [blame] | 271 | EXPECT_THAT("abs", matches("[abs]l", 1.f)); |
| 272 | EXPECT_THAT("abs", matches("[abs]", 2.f)); |
| 273 | EXPECT_THAT("Abs", matches("[abs]", 2.f)); |
| 274 | } |
| 275 | |
Sam McCall | b45d073 | 2018-07-20 08:01:37 +0000 | [diff] [blame] | 276 | // Returns pretty-printed segmentation of Text. |
| 277 | // e.g. std::basic_string --> +-- +---- +----- |
| 278 | std::string segment(StringRef Text) { |
| 279 | std::vector<CharRole> Roles(Text.size()); |
| 280 | calculateRoles(Text, Roles); |
| 281 | std::string Printed; |
| 282 | for (unsigned I = 0; I < Text.size(); ++I) |
| 283 | Printed.push_back("?-+ "[static_cast<unsigned>(Roles[I])]); |
| 284 | return Printed; |
| 285 | } |
| 286 | |
| 287 | // this is a no-op hack so clang-format will vertically align our testcases. |
| 288 | StringRef returns(StringRef Text) { return Text; } |
| 289 | |
| 290 | TEST(FuzzyMatch, Segmentation) { |
| 291 | EXPECT_THAT(segment("std::basic_string"), // |
| 292 | returns("+-- +---- +-----")); |
| 293 | EXPECT_THAT(segment("XMLHttpRequest"), // |
| 294 | returns("+--+---+------")); |
| 295 | EXPECT_THAT(segment("t3h PeNgU1N oF d00m!!!!!!!!"), // |
| 296 | returns("+-- +-+-+-+ ++ +--- ")); |
| 297 | } |
| 298 | |
Sam McCall | fad12f8 | 2017-12-01 17:08:02 +0000 | [diff] [blame] | 299 | } // namespace |
| 300 | } // namespace clangd |
| 301 | } // namespace clang |