xref: /netbsd-src/external/apache2/llvm/dist/clang/tools/clang-refactor/TestSupport.cpp (revision e038c9c4676b0f19b1b7dd08a940c6ed64a6d5ae)
17330f729Sjoerg //===--- TestSupport.cpp - Clang-based refactoring tool -------------------===//
27330f729Sjoerg //
37330f729Sjoerg // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
47330f729Sjoerg // See https://llvm.org/LICENSE.txt for license information.
57330f729Sjoerg // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
67330f729Sjoerg //
77330f729Sjoerg //===----------------------------------------------------------------------===//
87330f729Sjoerg ///
97330f729Sjoerg /// \file
107330f729Sjoerg /// This file implements routines that provide refactoring testing
117330f729Sjoerg /// utilities.
127330f729Sjoerg ///
137330f729Sjoerg //===----------------------------------------------------------------------===//
147330f729Sjoerg 
157330f729Sjoerg #include "TestSupport.h"
167330f729Sjoerg #include "clang/Basic/DiagnosticError.h"
17*e038c9c4Sjoerg #include "clang/Basic/FileManager.h"
187330f729Sjoerg #include "clang/Basic/SourceManager.h"
197330f729Sjoerg #include "clang/Lex/Lexer.h"
207330f729Sjoerg #include "llvm/ADT/STLExtras.h"
217330f729Sjoerg #include "llvm/Support/Error.h"
227330f729Sjoerg #include "llvm/Support/ErrorOr.h"
237330f729Sjoerg #include "llvm/Support/LineIterator.h"
247330f729Sjoerg #include "llvm/Support/MemoryBuffer.h"
257330f729Sjoerg #include "llvm/Support/Regex.h"
267330f729Sjoerg #include "llvm/Support/raw_ostream.h"
277330f729Sjoerg 
287330f729Sjoerg using namespace llvm;
297330f729Sjoerg 
307330f729Sjoerg namespace clang {
317330f729Sjoerg namespace refactor {
327330f729Sjoerg 
dump(raw_ostream & OS) const337330f729Sjoerg void TestSelectionRangesInFile::dump(raw_ostream &OS) const {
347330f729Sjoerg   for (const auto &Group : GroupedRanges) {
357330f729Sjoerg     OS << "Test selection group '" << Group.Name << "':\n";
367330f729Sjoerg     for (const auto &Range : Group.Ranges) {
377330f729Sjoerg       OS << "  " << Range.Begin << "-" << Range.End << "\n";
387330f729Sjoerg     }
397330f729Sjoerg   }
407330f729Sjoerg }
417330f729Sjoerg 
foreachRange(const SourceManager & SM,llvm::function_ref<void (SourceRange)> Callback) const427330f729Sjoerg bool TestSelectionRangesInFile::foreachRange(
437330f729Sjoerg     const SourceManager &SM,
447330f729Sjoerg     llvm::function_ref<void(SourceRange)> Callback) const {
457330f729Sjoerg   auto FE = SM.getFileManager().getFile(Filename);
467330f729Sjoerg   FileID FID = FE ? SM.translateFile(*FE) : FileID();
477330f729Sjoerg   if (!FE || FID.isInvalid()) {
487330f729Sjoerg     llvm::errs() << "error: -selection=test:" << Filename
497330f729Sjoerg                  << " : given file is not in the target TU";
507330f729Sjoerg     return true;
517330f729Sjoerg   }
527330f729Sjoerg   SourceLocation FileLoc = SM.getLocForStartOfFile(FID);
537330f729Sjoerg   for (const auto &Group : GroupedRanges) {
547330f729Sjoerg     for (const TestSelectionRange &Range : Group.Ranges) {
557330f729Sjoerg       // Translate the offset pair to a true source range.
567330f729Sjoerg       SourceLocation Start =
577330f729Sjoerg           SM.getMacroArgExpandedLocation(FileLoc.getLocWithOffset(Range.Begin));
587330f729Sjoerg       SourceLocation End =
597330f729Sjoerg           SM.getMacroArgExpandedLocation(FileLoc.getLocWithOffset(Range.End));
607330f729Sjoerg       assert(Start.isValid() && End.isValid() && "unexpected invalid range");
617330f729Sjoerg       Callback(SourceRange(Start, End));
627330f729Sjoerg     }
637330f729Sjoerg   }
647330f729Sjoerg   return false;
657330f729Sjoerg }
667330f729Sjoerg 
677330f729Sjoerg namespace {
687330f729Sjoerg 
dumpChanges(const tooling::AtomicChanges & Changes,raw_ostream & OS)697330f729Sjoerg void dumpChanges(const tooling::AtomicChanges &Changes, raw_ostream &OS) {
707330f729Sjoerg   for (const auto &Change : Changes)
717330f729Sjoerg     OS << const_cast<tooling::AtomicChange &>(Change).toYAMLString() << "\n";
727330f729Sjoerg }
737330f729Sjoerg 
areChangesSame(const tooling::AtomicChanges & LHS,const tooling::AtomicChanges & RHS)747330f729Sjoerg bool areChangesSame(const tooling::AtomicChanges &LHS,
757330f729Sjoerg                     const tooling::AtomicChanges &RHS) {
767330f729Sjoerg   if (LHS.size() != RHS.size())
777330f729Sjoerg     return false;
78*e038c9c4Sjoerg   for (auto I : llvm::zip(LHS, RHS)) {
797330f729Sjoerg     if (!(std::get<0>(I) == std::get<1>(I)))
807330f729Sjoerg       return false;
817330f729Sjoerg   }
827330f729Sjoerg   return true;
837330f729Sjoerg }
847330f729Sjoerg 
printRewrittenSources(const tooling::AtomicChanges & Changes,raw_ostream & OS)857330f729Sjoerg bool printRewrittenSources(const tooling::AtomicChanges &Changes,
867330f729Sjoerg                            raw_ostream &OS) {
877330f729Sjoerg   std::set<std::string> Files;
887330f729Sjoerg   for (const auto &Change : Changes)
897330f729Sjoerg     Files.insert(Change.getFilePath());
907330f729Sjoerg   tooling::ApplyChangesSpec Spec;
917330f729Sjoerg   Spec.Cleanup = false;
927330f729Sjoerg   for (const auto &File : Files) {
937330f729Sjoerg     llvm::ErrorOr<std::unique_ptr<llvm::MemoryBuffer>> BufferErr =
947330f729Sjoerg         llvm::MemoryBuffer::getFile(File);
957330f729Sjoerg     if (!BufferErr) {
967330f729Sjoerg       llvm::errs() << "failed to open" << File << "\n";
977330f729Sjoerg       return true;
987330f729Sjoerg     }
997330f729Sjoerg     auto Result = tooling::applyAtomicChanges(File, (*BufferErr)->getBuffer(),
1007330f729Sjoerg                                               Changes, Spec);
1017330f729Sjoerg     if (!Result) {
1027330f729Sjoerg       llvm::errs() << toString(Result.takeError());
1037330f729Sjoerg       return true;
1047330f729Sjoerg     }
1057330f729Sjoerg     OS << *Result;
1067330f729Sjoerg   }
1077330f729Sjoerg   return false;
1087330f729Sjoerg }
1097330f729Sjoerg 
1107330f729Sjoerg class TestRefactoringResultConsumer final
1117330f729Sjoerg     : public ClangRefactorToolConsumerInterface {
1127330f729Sjoerg public:
TestRefactoringResultConsumer(const TestSelectionRangesInFile & TestRanges)1137330f729Sjoerg   TestRefactoringResultConsumer(const TestSelectionRangesInFile &TestRanges)
1147330f729Sjoerg       : TestRanges(TestRanges) {
1157330f729Sjoerg     Results.push_back({});
1167330f729Sjoerg   }
1177330f729Sjoerg 
~TestRefactoringResultConsumer()1187330f729Sjoerg   ~TestRefactoringResultConsumer() {
1197330f729Sjoerg     // Ensure all results are checked.
1207330f729Sjoerg     for (auto &Group : Results) {
1217330f729Sjoerg       for (auto &Result : Group) {
1227330f729Sjoerg         if (!Result) {
1237330f729Sjoerg           (void)llvm::toString(Result.takeError());
1247330f729Sjoerg         }
1257330f729Sjoerg       }
1267330f729Sjoerg     }
1277330f729Sjoerg   }
1287330f729Sjoerg 
handleError(llvm::Error Err)1297330f729Sjoerg   void handleError(llvm::Error Err) override { handleResult(std::move(Err)); }
1307330f729Sjoerg 
handle(tooling::AtomicChanges Changes)1317330f729Sjoerg   void handle(tooling::AtomicChanges Changes) override {
1327330f729Sjoerg     handleResult(std::move(Changes));
1337330f729Sjoerg   }
1347330f729Sjoerg 
handle(tooling::SymbolOccurrences Occurrences)1357330f729Sjoerg   void handle(tooling::SymbolOccurrences Occurrences) override {
1367330f729Sjoerg     tooling::RefactoringResultConsumer::handle(std::move(Occurrences));
1377330f729Sjoerg   }
1387330f729Sjoerg 
1397330f729Sjoerg private:
1407330f729Sjoerg   bool handleAllResults();
1417330f729Sjoerg 
handleResult(Expected<tooling::AtomicChanges> Result)1427330f729Sjoerg   void handleResult(Expected<tooling::AtomicChanges> Result) {
1437330f729Sjoerg     Results.back().push_back(std::move(Result));
1447330f729Sjoerg     size_t GroupIndex = Results.size() - 1;
1457330f729Sjoerg     if (Results.back().size() >=
1467330f729Sjoerg         TestRanges.GroupedRanges[GroupIndex].Ranges.size()) {
1477330f729Sjoerg       ++GroupIndex;
1487330f729Sjoerg       if (GroupIndex >= TestRanges.GroupedRanges.size()) {
1497330f729Sjoerg         if (handleAllResults())
1507330f729Sjoerg           exit(1); // error has occurred.
1517330f729Sjoerg         return;
1527330f729Sjoerg       }
1537330f729Sjoerg       Results.push_back({});
1547330f729Sjoerg     }
1557330f729Sjoerg   }
1567330f729Sjoerg 
1577330f729Sjoerg   const TestSelectionRangesInFile &TestRanges;
1587330f729Sjoerg   std::vector<std::vector<Expected<tooling::AtomicChanges>>> Results;
1597330f729Sjoerg };
1607330f729Sjoerg 
getLineColumn(StringRef Filename,unsigned Offset)1617330f729Sjoerg std::pair<unsigned, unsigned> getLineColumn(StringRef Filename,
1627330f729Sjoerg                                             unsigned Offset) {
1637330f729Sjoerg   ErrorOr<std::unique_ptr<MemoryBuffer>> ErrOrFile =
1647330f729Sjoerg       MemoryBuffer::getFile(Filename);
1657330f729Sjoerg   if (!ErrOrFile)
1667330f729Sjoerg     return {0, 0};
1677330f729Sjoerg   StringRef Source = ErrOrFile.get()->getBuffer();
1687330f729Sjoerg   Source = Source.take_front(Offset);
1697330f729Sjoerg   size_t LastLine = Source.find_last_of("\r\n");
1707330f729Sjoerg   return {Source.count('\n') + 1,
1717330f729Sjoerg           (LastLine == StringRef::npos ? Offset : Offset - LastLine) + 1};
1727330f729Sjoerg }
1737330f729Sjoerg 
1747330f729Sjoerg } // end anonymous namespace
1757330f729Sjoerg 
handleAllResults()1767330f729Sjoerg bool TestRefactoringResultConsumer::handleAllResults() {
1777330f729Sjoerg   bool Failed = false;
1787330f729Sjoerg   for (auto &Group : llvm::enumerate(Results)) {
1797330f729Sjoerg     // All ranges in the group must produce the same result.
1807330f729Sjoerg     Optional<tooling::AtomicChanges> CanonicalResult;
1817330f729Sjoerg     Optional<std::string> CanonicalErrorMessage;
1827330f729Sjoerg     for (auto &I : llvm::enumerate(Group.value())) {
1837330f729Sjoerg       Expected<tooling::AtomicChanges> &Result = I.value();
1847330f729Sjoerg       std::string ErrorMessage;
1857330f729Sjoerg       bool HasResult = !!Result;
1867330f729Sjoerg       if (!HasResult) {
1877330f729Sjoerg         handleAllErrors(
1887330f729Sjoerg             Result.takeError(),
1897330f729Sjoerg             [&](StringError &Err) { ErrorMessage = Err.getMessage(); },
1907330f729Sjoerg             [&](DiagnosticError &Err) {
1917330f729Sjoerg               const PartialDiagnosticAt &Diag = Err.getDiagnostic();
1927330f729Sjoerg               llvm::SmallString<100> DiagText;
1937330f729Sjoerg               Diag.second.EmitToString(getDiags(), DiagText);
194*e038c9c4Sjoerg               ErrorMessage = std::string(DiagText);
1957330f729Sjoerg             });
1967330f729Sjoerg       }
1977330f729Sjoerg       if (!CanonicalResult && !CanonicalErrorMessage) {
1987330f729Sjoerg         if (HasResult)
1997330f729Sjoerg           CanonicalResult = std::move(*Result);
2007330f729Sjoerg         else
2017330f729Sjoerg           CanonicalErrorMessage = std::move(ErrorMessage);
2027330f729Sjoerg         continue;
2037330f729Sjoerg       }
2047330f729Sjoerg 
2057330f729Sjoerg       // Verify that this result corresponds to the canonical result.
2067330f729Sjoerg       if (CanonicalErrorMessage) {
2077330f729Sjoerg         // The error messages must match.
2087330f729Sjoerg         if (!HasResult && ErrorMessage == *CanonicalErrorMessage)
2097330f729Sjoerg           continue;
2107330f729Sjoerg       } else {
2117330f729Sjoerg         assert(CanonicalResult && "missing canonical result");
2127330f729Sjoerg         // The results must match.
2137330f729Sjoerg         if (HasResult && areChangesSame(*Result, *CanonicalResult))
2147330f729Sjoerg           continue;
2157330f729Sjoerg       }
2167330f729Sjoerg       Failed = true;
2177330f729Sjoerg       // Report the mismatch.
2187330f729Sjoerg       std::pair<unsigned, unsigned> LineColumn = getLineColumn(
2197330f729Sjoerg           TestRanges.Filename,
2207330f729Sjoerg           TestRanges.GroupedRanges[Group.index()].Ranges[I.index()].Begin);
2217330f729Sjoerg       llvm::errs()
2227330f729Sjoerg           << "error: unexpected refactoring result for range starting at "
2237330f729Sjoerg           << LineColumn.first << ':' << LineColumn.second << " in group '"
2247330f729Sjoerg           << TestRanges.GroupedRanges[Group.index()].Name << "':\n  ";
2257330f729Sjoerg       if (HasResult)
2267330f729Sjoerg         llvm::errs() << "valid result";
2277330f729Sjoerg       else
2287330f729Sjoerg         llvm::errs() << "error '" << ErrorMessage << "'";
2297330f729Sjoerg       llvm::errs() << " does not match initial ";
2307330f729Sjoerg       if (CanonicalErrorMessage)
2317330f729Sjoerg         llvm::errs() << "error '" << *CanonicalErrorMessage << "'\n";
2327330f729Sjoerg       else
2337330f729Sjoerg         llvm::errs() << "valid result\n";
2347330f729Sjoerg       if (HasResult && !CanonicalErrorMessage) {
2357330f729Sjoerg         llvm::errs() << "  Expected to Produce:\n";
2367330f729Sjoerg         dumpChanges(*CanonicalResult, llvm::errs());
2377330f729Sjoerg         llvm::errs() << "  Produced:\n";
2387330f729Sjoerg         dumpChanges(*Result, llvm::errs());
2397330f729Sjoerg       }
2407330f729Sjoerg     }
2417330f729Sjoerg 
2427330f729Sjoerg     // Dump the results:
2437330f729Sjoerg     const auto &TestGroup = TestRanges.GroupedRanges[Group.index()];
2447330f729Sjoerg     if (!CanonicalResult) {
2457330f729Sjoerg       llvm::outs() << TestGroup.Ranges.size() << " '" << TestGroup.Name
2467330f729Sjoerg                    << "' results:\n";
2477330f729Sjoerg       llvm::outs() << *CanonicalErrorMessage << "\n";
2487330f729Sjoerg     } else {
2497330f729Sjoerg       llvm::outs() << TestGroup.Ranges.size() << " '" << TestGroup.Name
2507330f729Sjoerg                    << "' results:\n";
2517330f729Sjoerg       if (printRewrittenSources(*CanonicalResult, llvm::outs()))
2527330f729Sjoerg         return true;
2537330f729Sjoerg     }
2547330f729Sjoerg   }
2557330f729Sjoerg   return Failed;
2567330f729Sjoerg }
2577330f729Sjoerg 
2587330f729Sjoerg std::unique_ptr<ClangRefactorToolConsumerInterface>
createConsumer() const2597330f729Sjoerg TestSelectionRangesInFile::createConsumer() const {
2607330f729Sjoerg   return std::make_unique<TestRefactoringResultConsumer>(*this);
2617330f729Sjoerg }
2627330f729Sjoerg 
2637330f729Sjoerg /// Adds the \p ColumnOffset to file offset \p Offset, without going past a
2647330f729Sjoerg /// newline.
addColumnOffset(StringRef Source,unsigned Offset,unsigned ColumnOffset)2657330f729Sjoerg static unsigned addColumnOffset(StringRef Source, unsigned Offset,
2667330f729Sjoerg                                 unsigned ColumnOffset) {
2677330f729Sjoerg   if (!ColumnOffset)
2687330f729Sjoerg     return Offset;
2697330f729Sjoerg   StringRef Substr = Source.drop_front(Offset).take_front(ColumnOffset);
2707330f729Sjoerg   size_t NewlinePos = Substr.find_first_of("\r\n");
2717330f729Sjoerg   return Offset +
2727330f729Sjoerg          (NewlinePos == StringRef::npos ? ColumnOffset : (unsigned)NewlinePos);
2737330f729Sjoerg }
2747330f729Sjoerg 
addEndLineOffsetAndEndColumn(StringRef Source,unsigned Offset,unsigned LineNumberOffset,unsigned Column)2757330f729Sjoerg static unsigned addEndLineOffsetAndEndColumn(StringRef Source, unsigned Offset,
2767330f729Sjoerg                                              unsigned LineNumberOffset,
2777330f729Sjoerg                                              unsigned Column) {
2787330f729Sjoerg   StringRef Line = Source.drop_front(Offset);
2797330f729Sjoerg   unsigned LineOffset = 0;
2807330f729Sjoerg   for (; LineNumberOffset != 0; --LineNumberOffset) {
2817330f729Sjoerg     size_t NewlinePos = Line.find_first_of("\r\n");
2827330f729Sjoerg     // Line offset goes out of bounds.
2837330f729Sjoerg     if (NewlinePos == StringRef::npos)
2847330f729Sjoerg       break;
2857330f729Sjoerg     LineOffset += NewlinePos + 1;
2867330f729Sjoerg     Line = Line.drop_front(NewlinePos + 1);
2877330f729Sjoerg   }
2887330f729Sjoerg   // Source now points to the line at +lineOffset;
2897330f729Sjoerg   size_t LineStart = Source.find_last_of("\r\n", /*From=*/Offset + LineOffset);
2907330f729Sjoerg   return addColumnOffset(
2917330f729Sjoerg       Source, LineStart == StringRef::npos ? 0 : LineStart + 1, Column - 1);
2927330f729Sjoerg }
2937330f729Sjoerg 
2947330f729Sjoerg Optional<TestSelectionRangesInFile>
findTestSelectionRanges(StringRef Filename)2957330f729Sjoerg findTestSelectionRanges(StringRef Filename) {
2967330f729Sjoerg   ErrorOr<std::unique_ptr<MemoryBuffer>> ErrOrFile =
2977330f729Sjoerg       MemoryBuffer::getFile(Filename);
2987330f729Sjoerg   if (!ErrOrFile) {
2997330f729Sjoerg     llvm::errs() << "error: -selection=test:" << Filename
3007330f729Sjoerg                  << " : could not open the given file";
3017330f729Sjoerg     return None;
3027330f729Sjoerg   }
3037330f729Sjoerg   StringRef Source = ErrOrFile.get()->getBuffer();
3047330f729Sjoerg 
3057330f729Sjoerg   // See the doc comment for this function for the explanation of this
3067330f729Sjoerg   // syntax.
307*e038c9c4Sjoerg   static const Regex RangeRegex(
308*e038c9c4Sjoerg       "range[[:blank:]]*([[:alpha:]_]*)?[[:blank:]]*=[[:"
3097330f729Sjoerg       "blank:]]*(\\+[[:digit:]]+)?[[:blank:]]*(->[[:blank:]"
3107330f729Sjoerg       "]*[\\+\\:[:digit:]]+)?");
3117330f729Sjoerg 
3127330f729Sjoerg   std::map<std::string, SmallVector<TestSelectionRange, 8>> GroupedRanges;
3137330f729Sjoerg 
3147330f729Sjoerg   LangOptions LangOpts;
3157330f729Sjoerg   LangOpts.CPlusPlus = 1;
3167330f729Sjoerg   LangOpts.CPlusPlus11 = 1;
3177330f729Sjoerg   Lexer Lex(SourceLocation::getFromRawEncoding(0), LangOpts, Source.begin(),
3187330f729Sjoerg             Source.begin(), Source.end());
3197330f729Sjoerg   Lex.SetCommentRetentionState(true);
3207330f729Sjoerg   Token Tok;
3217330f729Sjoerg   for (Lex.LexFromRawLexer(Tok); Tok.isNot(tok::eof);
3227330f729Sjoerg        Lex.LexFromRawLexer(Tok)) {
3237330f729Sjoerg     if (Tok.isNot(tok::comment))
3247330f729Sjoerg       continue;
3257330f729Sjoerg     StringRef Comment =
3267330f729Sjoerg         Source.substr(Tok.getLocation().getRawEncoding(), Tok.getLength());
3277330f729Sjoerg     SmallVector<StringRef, 4> Matches;
3287330f729Sjoerg     // Try to detect mistyped 'range:' comments to ensure tests don't miss
3297330f729Sjoerg     // anything.
3307330f729Sjoerg     auto DetectMistypedCommand = [&]() -> bool {
3317330f729Sjoerg       if (Comment.contains_lower("range") && Comment.contains("=") &&
3327330f729Sjoerg           !Comment.contains_lower("run") && !Comment.contains("CHECK")) {
3337330f729Sjoerg         llvm::errs() << "error: suspicious comment '" << Comment
3347330f729Sjoerg                      << "' that "
3357330f729Sjoerg                         "resembles the range command found\n";
3367330f729Sjoerg         llvm::errs() << "note: please reword if this isn't a range command\n";
3377330f729Sjoerg       }
3387330f729Sjoerg       return false;
3397330f729Sjoerg     };
3407330f729Sjoerg     // Allow CHECK: comments to contain range= commands.
3417330f729Sjoerg     if (!RangeRegex.match(Comment, &Matches) || Comment.contains("CHECK")) {
3427330f729Sjoerg       if (DetectMistypedCommand())
3437330f729Sjoerg         return None;
3447330f729Sjoerg       continue;
3457330f729Sjoerg     }
3467330f729Sjoerg     unsigned Offset = Tok.getEndLoc().getRawEncoding();
3477330f729Sjoerg     unsigned ColumnOffset = 0;
3487330f729Sjoerg     if (!Matches[2].empty()) {
3497330f729Sjoerg       // Don't forget to drop the '+'!
3507330f729Sjoerg       if (Matches[2].drop_front().getAsInteger(10, ColumnOffset))
3517330f729Sjoerg         assert(false && "regex should have produced a number");
3527330f729Sjoerg     }
3537330f729Sjoerg     Offset = addColumnOffset(Source, Offset, ColumnOffset);
3547330f729Sjoerg     unsigned EndOffset;
3557330f729Sjoerg 
3567330f729Sjoerg     if (!Matches[3].empty()) {
357*e038c9c4Sjoerg       static const Regex EndLocRegex(
3587330f729Sjoerg           "->[[:blank:]]*(\\+[[:digit:]]+):([[:digit:]]+)");
3597330f729Sjoerg       SmallVector<StringRef, 4> EndLocMatches;
3607330f729Sjoerg       if (!EndLocRegex.match(Matches[3], &EndLocMatches)) {
3617330f729Sjoerg         if (DetectMistypedCommand())
3627330f729Sjoerg           return None;
3637330f729Sjoerg         continue;
3647330f729Sjoerg       }
3657330f729Sjoerg       unsigned EndLineOffset = 0, EndColumn = 0;
3667330f729Sjoerg       if (EndLocMatches[1].drop_front().getAsInteger(10, EndLineOffset) ||
3677330f729Sjoerg           EndLocMatches[2].getAsInteger(10, EndColumn))
3687330f729Sjoerg         assert(false && "regex should have produced a number");
3697330f729Sjoerg       EndOffset = addEndLineOffsetAndEndColumn(Source, Offset, EndLineOffset,
3707330f729Sjoerg                                                EndColumn);
3717330f729Sjoerg     } else {
3727330f729Sjoerg       EndOffset = Offset;
3737330f729Sjoerg     }
3747330f729Sjoerg     TestSelectionRange Range = {Offset, EndOffset};
3757330f729Sjoerg     auto It = GroupedRanges.insert(std::make_pair(
3767330f729Sjoerg         Matches[1].str(), SmallVector<TestSelectionRange, 8>{Range}));
3777330f729Sjoerg     if (!It.second)
3787330f729Sjoerg       It.first->second.push_back(Range);
3797330f729Sjoerg   }
3807330f729Sjoerg   if (GroupedRanges.empty()) {
3817330f729Sjoerg     llvm::errs() << "error: -selection=test:" << Filename
3827330f729Sjoerg                  << ": no 'range' commands";
3837330f729Sjoerg     return None;
3847330f729Sjoerg   }
3857330f729Sjoerg 
3867330f729Sjoerg   TestSelectionRangesInFile TestRanges = {Filename.str(), {}};
3877330f729Sjoerg   for (auto &Group : GroupedRanges)
3887330f729Sjoerg     TestRanges.GroupedRanges.push_back({Group.first, std::move(Group.second)});
3897330f729Sjoerg   return std::move(TestRanges);
3907330f729Sjoerg }
3917330f729Sjoerg 
3927330f729Sjoerg } // end namespace refactor
3937330f729Sjoerg } // end namespace clang
394