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