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