xref: /openbsd-src/gnu/llvm/clang/tools/clang-refactor/TestSupport.cpp (revision e5dd70708596ae51455a0ffa086a00c5b29f8583)
1*e5dd7070Spatrick //===--- TestSupport.cpp - Clang-based refactoring tool -------------------===//
2*e5dd7070Spatrick //
3*e5dd7070Spatrick // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4*e5dd7070Spatrick // See https://llvm.org/LICENSE.txt for license information.
5*e5dd7070Spatrick // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6*e5dd7070Spatrick //
7*e5dd7070Spatrick //===----------------------------------------------------------------------===//
8*e5dd7070Spatrick ///
9*e5dd7070Spatrick /// \file
10*e5dd7070Spatrick /// This file implements routines that provide refactoring testing
11*e5dd7070Spatrick /// utilities.
12*e5dd7070Spatrick ///
13*e5dd7070Spatrick //===----------------------------------------------------------------------===//
14*e5dd7070Spatrick 
15*e5dd7070Spatrick #include "TestSupport.h"
16*e5dd7070Spatrick #include "clang/Basic/DiagnosticError.h"
17*e5dd7070Spatrick #include "clang/Basic/SourceManager.h"
18*e5dd7070Spatrick #include "clang/Lex/Lexer.h"
19*e5dd7070Spatrick #include "llvm/ADT/STLExtras.h"
20*e5dd7070Spatrick #include "llvm/Support/Error.h"
21*e5dd7070Spatrick #include "llvm/Support/ErrorOr.h"
22*e5dd7070Spatrick #include "llvm/Support/LineIterator.h"
23*e5dd7070Spatrick #include "llvm/Support/MemoryBuffer.h"
24*e5dd7070Spatrick #include "llvm/Support/Regex.h"
25*e5dd7070Spatrick #include "llvm/Support/raw_ostream.h"
26*e5dd7070Spatrick 
27*e5dd7070Spatrick using namespace llvm;
28*e5dd7070Spatrick 
29*e5dd7070Spatrick namespace clang {
30*e5dd7070Spatrick namespace refactor {
31*e5dd7070Spatrick 
32*e5dd7070Spatrick void TestSelectionRangesInFile::dump(raw_ostream &OS) const {
33*e5dd7070Spatrick   for (const auto &Group : GroupedRanges) {
34*e5dd7070Spatrick     OS << "Test selection group '" << Group.Name << "':\n";
35*e5dd7070Spatrick     for (const auto &Range : Group.Ranges) {
36*e5dd7070Spatrick       OS << "  " << Range.Begin << "-" << Range.End << "\n";
37*e5dd7070Spatrick     }
38*e5dd7070Spatrick   }
39*e5dd7070Spatrick }
40*e5dd7070Spatrick 
41*e5dd7070Spatrick bool TestSelectionRangesInFile::foreachRange(
42*e5dd7070Spatrick     const SourceManager &SM,
43*e5dd7070Spatrick     llvm::function_ref<void(SourceRange)> Callback) const {
44*e5dd7070Spatrick   auto FE = SM.getFileManager().getFile(Filename);
45*e5dd7070Spatrick   FileID FID = FE ? SM.translateFile(*FE) : FileID();
46*e5dd7070Spatrick   if (!FE || FID.isInvalid()) {
47*e5dd7070Spatrick     llvm::errs() << "error: -selection=test:" << Filename
48*e5dd7070Spatrick                  << " : given file is not in the target TU";
49*e5dd7070Spatrick     return true;
50*e5dd7070Spatrick   }
51*e5dd7070Spatrick   SourceLocation FileLoc = SM.getLocForStartOfFile(FID);
52*e5dd7070Spatrick   for (const auto &Group : GroupedRanges) {
53*e5dd7070Spatrick     for (const TestSelectionRange &Range : Group.Ranges) {
54*e5dd7070Spatrick       // Translate the offset pair to a true source range.
55*e5dd7070Spatrick       SourceLocation Start =
56*e5dd7070Spatrick           SM.getMacroArgExpandedLocation(FileLoc.getLocWithOffset(Range.Begin));
57*e5dd7070Spatrick       SourceLocation End =
58*e5dd7070Spatrick           SM.getMacroArgExpandedLocation(FileLoc.getLocWithOffset(Range.End));
59*e5dd7070Spatrick       assert(Start.isValid() && End.isValid() && "unexpected invalid range");
60*e5dd7070Spatrick       Callback(SourceRange(Start, End));
61*e5dd7070Spatrick     }
62*e5dd7070Spatrick   }
63*e5dd7070Spatrick   return false;
64*e5dd7070Spatrick }
65*e5dd7070Spatrick 
66*e5dd7070Spatrick namespace {
67*e5dd7070Spatrick 
68*e5dd7070Spatrick void dumpChanges(const tooling::AtomicChanges &Changes, raw_ostream &OS) {
69*e5dd7070Spatrick   for (const auto &Change : Changes)
70*e5dd7070Spatrick     OS << const_cast<tooling::AtomicChange &>(Change).toYAMLString() << "\n";
71*e5dd7070Spatrick }
72*e5dd7070Spatrick 
73*e5dd7070Spatrick bool areChangesSame(const tooling::AtomicChanges &LHS,
74*e5dd7070Spatrick                     const tooling::AtomicChanges &RHS) {
75*e5dd7070Spatrick   if (LHS.size() != RHS.size())
76*e5dd7070Spatrick     return false;
77*e5dd7070Spatrick   for (auto I : llvm::zip(LHS, RHS)) {
78*e5dd7070Spatrick     if (!(std::get<0>(I) == std::get<1>(I)))
79*e5dd7070Spatrick       return false;
80*e5dd7070Spatrick   }
81*e5dd7070Spatrick   return true;
82*e5dd7070Spatrick }
83*e5dd7070Spatrick 
84*e5dd7070Spatrick bool printRewrittenSources(const tooling::AtomicChanges &Changes,
85*e5dd7070Spatrick                            raw_ostream &OS) {
86*e5dd7070Spatrick   std::set<std::string> Files;
87*e5dd7070Spatrick   for (const auto &Change : Changes)
88*e5dd7070Spatrick     Files.insert(Change.getFilePath());
89*e5dd7070Spatrick   tooling::ApplyChangesSpec Spec;
90*e5dd7070Spatrick   Spec.Cleanup = false;
91*e5dd7070Spatrick   for (const auto &File : Files) {
92*e5dd7070Spatrick     llvm::ErrorOr<std::unique_ptr<llvm::MemoryBuffer>> BufferErr =
93*e5dd7070Spatrick         llvm::MemoryBuffer::getFile(File);
94*e5dd7070Spatrick     if (!BufferErr) {
95*e5dd7070Spatrick       llvm::errs() << "failed to open" << File << "\n";
96*e5dd7070Spatrick       return true;
97*e5dd7070Spatrick     }
98*e5dd7070Spatrick     auto Result = tooling::applyAtomicChanges(File, (*BufferErr)->getBuffer(),
99*e5dd7070Spatrick                                               Changes, Spec);
100*e5dd7070Spatrick     if (!Result) {
101*e5dd7070Spatrick       llvm::errs() << toString(Result.takeError());
102*e5dd7070Spatrick       return true;
103*e5dd7070Spatrick     }
104*e5dd7070Spatrick     OS << *Result;
105*e5dd7070Spatrick   }
106*e5dd7070Spatrick   return false;
107*e5dd7070Spatrick }
108*e5dd7070Spatrick 
109*e5dd7070Spatrick class TestRefactoringResultConsumer final
110*e5dd7070Spatrick     : public ClangRefactorToolConsumerInterface {
111*e5dd7070Spatrick public:
112*e5dd7070Spatrick   TestRefactoringResultConsumer(const TestSelectionRangesInFile &TestRanges)
113*e5dd7070Spatrick       : TestRanges(TestRanges) {
114*e5dd7070Spatrick     Results.push_back({});
115*e5dd7070Spatrick   }
116*e5dd7070Spatrick 
117*e5dd7070Spatrick   ~TestRefactoringResultConsumer() {
118*e5dd7070Spatrick     // Ensure all results are checked.
119*e5dd7070Spatrick     for (auto &Group : Results) {
120*e5dd7070Spatrick       for (auto &Result : Group) {
121*e5dd7070Spatrick         if (!Result) {
122*e5dd7070Spatrick           (void)llvm::toString(Result.takeError());
123*e5dd7070Spatrick         }
124*e5dd7070Spatrick       }
125*e5dd7070Spatrick     }
126*e5dd7070Spatrick   }
127*e5dd7070Spatrick 
128*e5dd7070Spatrick   void handleError(llvm::Error Err) override { handleResult(std::move(Err)); }
129*e5dd7070Spatrick 
130*e5dd7070Spatrick   void handle(tooling::AtomicChanges Changes) override {
131*e5dd7070Spatrick     handleResult(std::move(Changes));
132*e5dd7070Spatrick   }
133*e5dd7070Spatrick 
134*e5dd7070Spatrick   void handle(tooling::SymbolOccurrences Occurrences) override {
135*e5dd7070Spatrick     tooling::RefactoringResultConsumer::handle(std::move(Occurrences));
136*e5dd7070Spatrick   }
137*e5dd7070Spatrick 
138*e5dd7070Spatrick private:
139*e5dd7070Spatrick   bool handleAllResults();
140*e5dd7070Spatrick 
141*e5dd7070Spatrick   void handleResult(Expected<tooling::AtomicChanges> Result) {
142*e5dd7070Spatrick     Results.back().push_back(std::move(Result));
143*e5dd7070Spatrick     size_t GroupIndex = Results.size() - 1;
144*e5dd7070Spatrick     if (Results.back().size() >=
145*e5dd7070Spatrick         TestRanges.GroupedRanges[GroupIndex].Ranges.size()) {
146*e5dd7070Spatrick       ++GroupIndex;
147*e5dd7070Spatrick       if (GroupIndex >= TestRanges.GroupedRanges.size()) {
148*e5dd7070Spatrick         if (handleAllResults())
149*e5dd7070Spatrick           exit(1); // error has occurred.
150*e5dd7070Spatrick         return;
151*e5dd7070Spatrick       }
152*e5dd7070Spatrick       Results.push_back({});
153*e5dd7070Spatrick     }
154*e5dd7070Spatrick   }
155*e5dd7070Spatrick 
156*e5dd7070Spatrick   const TestSelectionRangesInFile &TestRanges;
157*e5dd7070Spatrick   std::vector<std::vector<Expected<tooling::AtomicChanges>>> Results;
158*e5dd7070Spatrick };
159*e5dd7070Spatrick 
160*e5dd7070Spatrick std::pair<unsigned, unsigned> getLineColumn(StringRef Filename,
161*e5dd7070Spatrick                                             unsigned Offset) {
162*e5dd7070Spatrick   ErrorOr<std::unique_ptr<MemoryBuffer>> ErrOrFile =
163*e5dd7070Spatrick       MemoryBuffer::getFile(Filename);
164*e5dd7070Spatrick   if (!ErrOrFile)
165*e5dd7070Spatrick     return {0, 0};
166*e5dd7070Spatrick   StringRef Source = ErrOrFile.get()->getBuffer();
167*e5dd7070Spatrick   Source = Source.take_front(Offset);
168*e5dd7070Spatrick   size_t LastLine = Source.find_last_of("\r\n");
169*e5dd7070Spatrick   return {Source.count('\n') + 1,
170*e5dd7070Spatrick           (LastLine == StringRef::npos ? Offset : Offset - LastLine) + 1};
171*e5dd7070Spatrick }
172*e5dd7070Spatrick 
173*e5dd7070Spatrick } // end anonymous namespace
174*e5dd7070Spatrick 
175*e5dd7070Spatrick bool TestRefactoringResultConsumer::handleAllResults() {
176*e5dd7070Spatrick   bool Failed = false;
177*e5dd7070Spatrick   for (auto &Group : llvm::enumerate(Results)) {
178*e5dd7070Spatrick     // All ranges in the group must produce the same result.
179*e5dd7070Spatrick     Optional<tooling::AtomicChanges> CanonicalResult;
180*e5dd7070Spatrick     Optional<std::string> CanonicalErrorMessage;
181*e5dd7070Spatrick     for (auto &I : llvm::enumerate(Group.value())) {
182*e5dd7070Spatrick       Expected<tooling::AtomicChanges> &Result = I.value();
183*e5dd7070Spatrick       std::string ErrorMessage;
184*e5dd7070Spatrick       bool HasResult = !!Result;
185*e5dd7070Spatrick       if (!HasResult) {
186*e5dd7070Spatrick         handleAllErrors(
187*e5dd7070Spatrick             Result.takeError(),
188*e5dd7070Spatrick             [&](StringError &Err) { ErrorMessage = Err.getMessage(); },
189*e5dd7070Spatrick             [&](DiagnosticError &Err) {
190*e5dd7070Spatrick               const PartialDiagnosticAt &Diag = Err.getDiagnostic();
191*e5dd7070Spatrick               llvm::SmallString<100> DiagText;
192*e5dd7070Spatrick               Diag.second.EmitToString(getDiags(), DiagText);
193*e5dd7070Spatrick               ErrorMessage = DiagText.str().str();
194*e5dd7070Spatrick             });
195*e5dd7070Spatrick       }
196*e5dd7070Spatrick       if (!CanonicalResult && !CanonicalErrorMessage) {
197*e5dd7070Spatrick         if (HasResult)
198*e5dd7070Spatrick           CanonicalResult = std::move(*Result);
199*e5dd7070Spatrick         else
200*e5dd7070Spatrick           CanonicalErrorMessage = std::move(ErrorMessage);
201*e5dd7070Spatrick         continue;
202*e5dd7070Spatrick       }
203*e5dd7070Spatrick 
204*e5dd7070Spatrick       // Verify that this result corresponds to the canonical result.
205*e5dd7070Spatrick       if (CanonicalErrorMessage) {
206*e5dd7070Spatrick         // The error messages must match.
207*e5dd7070Spatrick         if (!HasResult && ErrorMessage == *CanonicalErrorMessage)
208*e5dd7070Spatrick           continue;
209*e5dd7070Spatrick       } else {
210*e5dd7070Spatrick         assert(CanonicalResult && "missing canonical result");
211*e5dd7070Spatrick         // The results must match.
212*e5dd7070Spatrick         if (HasResult && areChangesSame(*Result, *CanonicalResult))
213*e5dd7070Spatrick           continue;
214*e5dd7070Spatrick       }
215*e5dd7070Spatrick       Failed = true;
216*e5dd7070Spatrick       // Report the mismatch.
217*e5dd7070Spatrick       std::pair<unsigned, unsigned> LineColumn = getLineColumn(
218*e5dd7070Spatrick           TestRanges.Filename,
219*e5dd7070Spatrick           TestRanges.GroupedRanges[Group.index()].Ranges[I.index()].Begin);
220*e5dd7070Spatrick       llvm::errs()
221*e5dd7070Spatrick           << "error: unexpected refactoring result for range starting at "
222*e5dd7070Spatrick           << LineColumn.first << ':' << LineColumn.second << " in group '"
223*e5dd7070Spatrick           << TestRanges.GroupedRanges[Group.index()].Name << "':\n  ";
224*e5dd7070Spatrick       if (HasResult)
225*e5dd7070Spatrick         llvm::errs() << "valid result";
226*e5dd7070Spatrick       else
227*e5dd7070Spatrick         llvm::errs() << "error '" << ErrorMessage << "'";
228*e5dd7070Spatrick       llvm::errs() << " does not match initial ";
229*e5dd7070Spatrick       if (CanonicalErrorMessage)
230*e5dd7070Spatrick         llvm::errs() << "error '" << *CanonicalErrorMessage << "'\n";
231*e5dd7070Spatrick       else
232*e5dd7070Spatrick         llvm::errs() << "valid result\n";
233*e5dd7070Spatrick       if (HasResult && !CanonicalErrorMessage) {
234*e5dd7070Spatrick         llvm::errs() << "  Expected to Produce:\n";
235*e5dd7070Spatrick         dumpChanges(*CanonicalResult, llvm::errs());
236*e5dd7070Spatrick         llvm::errs() << "  Produced:\n";
237*e5dd7070Spatrick         dumpChanges(*Result, llvm::errs());
238*e5dd7070Spatrick       }
239*e5dd7070Spatrick     }
240*e5dd7070Spatrick 
241*e5dd7070Spatrick     // Dump the results:
242*e5dd7070Spatrick     const auto &TestGroup = TestRanges.GroupedRanges[Group.index()];
243*e5dd7070Spatrick     if (!CanonicalResult) {
244*e5dd7070Spatrick       llvm::outs() << TestGroup.Ranges.size() << " '" << TestGroup.Name
245*e5dd7070Spatrick                    << "' results:\n";
246*e5dd7070Spatrick       llvm::outs() << *CanonicalErrorMessage << "\n";
247*e5dd7070Spatrick     } else {
248*e5dd7070Spatrick       llvm::outs() << TestGroup.Ranges.size() << " '" << TestGroup.Name
249*e5dd7070Spatrick                    << "' results:\n";
250*e5dd7070Spatrick       if (printRewrittenSources(*CanonicalResult, llvm::outs()))
251*e5dd7070Spatrick         return true;
252*e5dd7070Spatrick     }
253*e5dd7070Spatrick   }
254*e5dd7070Spatrick   return Failed;
255*e5dd7070Spatrick }
256*e5dd7070Spatrick 
257*e5dd7070Spatrick std::unique_ptr<ClangRefactorToolConsumerInterface>
258*e5dd7070Spatrick TestSelectionRangesInFile::createConsumer() const {
259*e5dd7070Spatrick   return std::make_unique<TestRefactoringResultConsumer>(*this);
260*e5dd7070Spatrick }
261*e5dd7070Spatrick 
262*e5dd7070Spatrick /// Adds the \p ColumnOffset to file offset \p Offset, without going past a
263*e5dd7070Spatrick /// newline.
264*e5dd7070Spatrick static unsigned addColumnOffset(StringRef Source, unsigned Offset,
265*e5dd7070Spatrick                                 unsigned ColumnOffset) {
266*e5dd7070Spatrick   if (!ColumnOffset)
267*e5dd7070Spatrick     return Offset;
268*e5dd7070Spatrick   StringRef Substr = Source.drop_front(Offset).take_front(ColumnOffset);
269*e5dd7070Spatrick   size_t NewlinePos = Substr.find_first_of("\r\n");
270*e5dd7070Spatrick   return Offset +
271*e5dd7070Spatrick          (NewlinePos == StringRef::npos ? ColumnOffset : (unsigned)NewlinePos);
272*e5dd7070Spatrick }
273*e5dd7070Spatrick 
274*e5dd7070Spatrick static unsigned addEndLineOffsetAndEndColumn(StringRef Source, unsigned Offset,
275*e5dd7070Spatrick                                              unsigned LineNumberOffset,
276*e5dd7070Spatrick                                              unsigned Column) {
277*e5dd7070Spatrick   StringRef Line = Source.drop_front(Offset);
278*e5dd7070Spatrick   unsigned LineOffset = 0;
279*e5dd7070Spatrick   for (; LineNumberOffset != 0; --LineNumberOffset) {
280*e5dd7070Spatrick     size_t NewlinePos = Line.find_first_of("\r\n");
281*e5dd7070Spatrick     // Line offset goes out of bounds.
282*e5dd7070Spatrick     if (NewlinePos == StringRef::npos)
283*e5dd7070Spatrick       break;
284*e5dd7070Spatrick     LineOffset += NewlinePos + 1;
285*e5dd7070Spatrick     Line = Line.drop_front(NewlinePos + 1);
286*e5dd7070Spatrick   }
287*e5dd7070Spatrick   // Source now points to the line at +lineOffset;
288*e5dd7070Spatrick   size_t LineStart = Source.find_last_of("\r\n", /*From=*/Offset + LineOffset);
289*e5dd7070Spatrick   return addColumnOffset(
290*e5dd7070Spatrick       Source, LineStart == StringRef::npos ? 0 : LineStart + 1, Column - 1);
291*e5dd7070Spatrick }
292*e5dd7070Spatrick 
293*e5dd7070Spatrick Optional<TestSelectionRangesInFile>
294*e5dd7070Spatrick findTestSelectionRanges(StringRef Filename) {
295*e5dd7070Spatrick   ErrorOr<std::unique_ptr<MemoryBuffer>> ErrOrFile =
296*e5dd7070Spatrick       MemoryBuffer::getFile(Filename);
297*e5dd7070Spatrick   if (!ErrOrFile) {
298*e5dd7070Spatrick     llvm::errs() << "error: -selection=test:" << Filename
299*e5dd7070Spatrick                  << " : could not open the given file";
300*e5dd7070Spatrick     return None;
301*e5dd7070Spatrick   }
302*e5dd7070Spatrick   StringRef Source = ErrOrFile.get()->getBuffer();
303*e5dd7070Spatrick 
304*e5dd7070Spatrick   // See the doc comment for this function for the explanation of this
305*e5dd7070Spatrick   // syntax.
306*e5dd7070Spatrick   static const Regex RangeRegex(
307*e5dd7070Spatrick       "range[[:blank:]]*([[:alpha:]_]*)?[[:blank:]]*=[[:"
308*e5dd7070Spatrick       "blank:]]*(\\+[[:digit:]]+)?[[:blank:]]*(->[[:blank:]"
309*e5dd7070Spatrick       "]*[\\+\\:[:digit:]]+)?");
310*e5dd7070Spatrick 
311*e5dd7070Spatrick   std::map<std::string, SmallVector<TestSelectionRange, 8>> GroupedRanges;
312*e5dd7070Spatrick 
313*e5dd7070Spatrick   LangOptions LangOpts;
314*e5dd7070Spatrick   LangOpts.CPlusPlus = 1;
315*e5dd7070Spatrick   LangOpts.CPlusPlus11 = 1;
316*e5dd7070Spatrick   Lexer Lex(SourceLocation::getFromRawEncoding(0), LangOpts, Source.begin(),
317*e5dd7070Spatrick             Source.begin(), Source.end());
318*e5dd7070Spatrick   Lex.SetCommentRetentionState(true);
319*e5dd7070Spatrick   Token Tok;
320*e5dd7070Spatrick   for (Lex.LexFromRawLexer(Tok); Tok.isNot(tok::eof);
321*e5dd7070Spatrick        Lex.LexFromRawLexer(Tok)) {
322*e5dd7070Spatrick     if (Tok.isNot(tok::comment))
323*e5dd7070Spatrick       continue;
324*e5dd7070Spatrick     StringRef Comment =
325*e5dd7070Spatrick         Source.substr(Tok.getLocation().getRawEncoding(), Tok.getLength());
326*e5dd7070Spatrick     SmallVector<StringRef, 4> Matches;
327*e5dd7070Spatrick     // Try to detect mistyped 'range:' comments to ensure tests don't miss
328*e5dd7070Spatrick     // anything.
329*e5dd7070Spatrick     auto DetectMistypedCommand = [&]() -> bool {
330*e5dd7070Spatrick       if (Comment.contains_lower("range") && Comment.contains("=") &&
331*e5dd7070Spatrick           !Comment.contains_lower("run") && !Comment.contains("CHECK")) {
332*e5dd7070Spatrick         llvm::errs() << "error: suspicious comment '" << Comment
333*e5dd7070Spatrick                      << "' that "
334*e5dd7070Spatrick                         "resembles the range command found\n";
335*e5dd7070Spatrick         llvm::errs() << "note: please reword if this isn't a range command\n";
336*e5dd7070Spatrick       }
337*e5dd7070Spatrick       return false;
338*e5dd7070Spatrick     };
339*e5dd7070Spatrick     // Allow CHECK: comments to contain range= commands.
340*e5dd7070Spatrick     if (!RangeRegex.match(Comment, &Matches) || Comment.contains("CHECK")) {
341*e5dd7070Spatrick       if (DetectMistypedCommand())
342*e5dd7070Spatrick         return None;
343*e5dd7070Spatrick       continue;
344*e5dd7070Spatrick     }
345*e5dd7070Spatrick     unsigned Offset = Tok.getEndLoc().getRawEncoding();
346*e5dd7070Spatrick     unsigned ColumnOffset = 0;
347*e5dd7070Spatrick     if (!Matches[2].empty()) {
348*e5dd7070Spatrick       // Don't forget to drop the '+'!
349*e5dd7070Spatrick       if (Matches[2].drop_front().getAsInteger(10, ColumnOffset))
350*e5dd7070Spatrick         assert(false && "regex should have produced a number");
351*e5dd7070Spatrick     }
352*e5dd7070Spatrick     Offset = addColumnOffset(Source, Offset, ColumnOffset);
353*e5dd7070Spatrick     unsigned EndOffset;
354*e5dd7070Spatrick 
355*e5dd7070Spatrick     if (!Matches[3].empty()) {
356*e5dd7070Spatrick       static const Regex EndLocRegex(
357*e5dd7070Spatrick           "->[[:blank:]]*(\\+[[:digit:]]+):([[:digit:]]+)");
358*e5dd7070Spatrick       SmallVector<StringRef, 4> EndLocMatches;
359*e5dd7070Spatrick       if (!EndLocRegex.match(Matches[3], &EndLocMatches)) {
360*e5dd7070Spatrick         if (DetectMistypedCommand())
361*e5dd7070Spatrick           return None;
362*e5dd7070Spatrick         continue;
363*e5dd7070Spatrick       }
364*e5dd7070Spatrick       unsigned EndLineOffset = 0, EndColumn = 0;
365*e5dd7070Spatrick       if (EndLocMatches[1].drop_front().getAsInteger(10, EndLineOffset) ||
366*e5dd7070Spatrick           EndLocMatches[2].getAsInteger(10, EndColumn))
367*e5dd7070Spatrick         assert(false && "regex should have produced a number");
368*e5dd7070Spatrick       EndOffset = addEndLineOffsetAndEndColumn(Source, Offset, EndLineOffset,
369*e5dd7070Spatrick                                                EndColumn);
370*e5dd7070Spatrick     } else {
371*e5dd7070Spatrick       EndOffset = Offset;
372*e5dd7070Spatrick     }
373*e5dd7070Spatrick     TestSelectionRange Range = {Offset, EndOffset};
374*e5dd7070Spatrick     auto It = GroupedRanges.insert(std::make_pair(
375*e5dd7070Spatrick         Matches[1].str(), SmallVector<TestSelectionRange, 8>{Range}));
376*e5dd7070Spatrick     if (!It.second)
377*e5dd7070Spatrick       It.first->second.push_back(Range);
378*e5dd7070Spatrick   }
379*e5dd7070Spatrick   if (GroupedRanges.empty()) {
380*e5dd7070Spatrick     llvm::errs() << "error: -selection=test:" << Filename
381*e5dd7070Spatrick                  << ": no 'range' commands";
382*e5dd7070Spatrick     return None;
383*e5dd7070Spatrick   }
384*e5dd7070Spatrick 
385*e5dd7070Spatrick   TestSelectionRangesInFile TestRanges = {Filename.str(), {}};
386*e5dd7070Spatrick   for (auto &Group : GroupedRanges)
387*e5dd7070Spatrick     TestRanges.GroupedRanges.push_back({Group.first, std::move(Group.second)});
388*e5dd7070Spatrick   return std::move(TestRanges);
389*e5dd7070Spatrick }
390*e5dd7070Spatrick 
391*e5dd7070Spatrick } // end namespace refactor
392*e5dd7070Spatrick } // end namespace clang
393