1 //===--- DefineOutline.cpp ---------------------------------------*- C++-*-===// 2 // 3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. 4 // See https://llvm.org/LICENSE.txt for license information. 5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception 6 // 7 //===----------------------------------------------------------------------===// 8 9 #include "AST.h" 10 #include "FindTarget.h" 11 #include "HeaderSourceSwitch.h" 12 #include "ParsedAST.h" 13 #include "Selection.h" 14 #include "SourceCode.h" 15 #include "refactor/Tweak.h" 16 #include "support/Logger.h" 17 #include "support/Path.h" 18 #include "clang/AST/ASTTypeTraits.h" 19 #include "clang/AST/Attr.h" 20 #include "clang/AST/Decl.h" 21 #include "clang/AST/DeclBase.h" 22 #include "clang/AST/DeclCXX.h" 23 #include "clang/AST/DeclTemplate.h" 24 #include "clang/AST/Stmt.h" 25 #include "clang/Basic/SourceLocation.h" 26 #include "clang/Basic/SourceManager.h" 27 #include "clang/Basic/TokenKinds.h" 28 #include "clang/Tooling/Core/Replacement.h" 29 #include "clang/Tooling/Syntax/Tokens.h" 30 #include "llvm/ADT/STLExtras.h" 31 #include "llvm/ADT/StringRef.h" 32 #include "llvm/Support/Casting.h" 33 #include "llvm/Support/Error.h" 34 #include <cstddef> 35 #include <optional> 36 #include <string> 37 38 namespace clang { 39 namespace clangd { 40 namespace { 41 42 // Deduces the FunctionDecl from a selection. Requires either the function body 43 // or the function decl to be selected. Returns null if none of the above 44 // criteria is met. 45 // FIXME: This is shared with define inline, move them to a common header once 46 // we have a place for such. 47 const FunctionDecl *getSelectedFunction(const SelectionTree::Node *SelNode) { 48 if (!SelNode) 49 return nullptr; 50 const DynTypedNode &AstNode = SelNode->ASTNode; 51 if (const FunctionDecl *FD = AstNode.get<FunctionDecl>()) 52 return FD; 53 if (AstNode.get<CompoundStmt>() && 54 SelNode->Selected == SelectionTree::Complete) { 55 if (const SelectionTree::Node *P = SelNode->Parent) 56 return P->ASTNode.get<FunctionDecl>(); 57 } 58 return nullptr; 59 } 60 61 std::optional<Path> getSourceFile(llvm::StringRef FileName, 62 const Tweak::Selection &Sel) { 63 assert(Sel.FS); 64 if (auto Source = getCorrespondingHeaderOrSource(FileName, Sel.FS)) 65 return *Source; 66 return getCorrespondingHeaderOrSource(FileName, *Sel.AST, Sel.Index); 67 } 68 69 // Synthesize a DeclContext for TargetNS from CurContext. TargetNS must be empty 70 // for global namespace, and endwith "::" otherwise. 71 // Returns std::nullopt if TargetNS is not a prefix of CurContext. 72 std::optional<const DeclContext *> 73 findContextForNS(llvm::StringRef TargetNS, const DeclContext *CurContext) { 74 assert(TargetNS.empty() || TargetNS.ends_with("::")); 75 // Skip any non-namespace contexts, e.g. TagDecls, functions/methods. 76 CurContext = CurContext->getEnclosingNamespaceContext(); 77 // If TargetNS is empty, it means global ns, which is translation unit. 78 if (TargetNS.empty()) { 79 while (!CurContext->isTranslationUnit()) 80 CurContext = CurContext->getParent(); 81 return CurContext; 82 } 83 // Otherwise we need to drop any trailing namespaces from CurContext until 84 // we reach TargetNS. 85 std::string TargetContextNS = 86 CurContext->isNamespace() 87 ? llvm::cast<NamespaceDecl>(CurContext)->getQualifiedNameAsString() 88 : ""; 89 TargetContextNS.append("::"); 90 91 llvm::StringRef CurrentContextNS(TargetContextNS); 92 // If TargetNS is not a prefix of CurrentContext, there's no way to reach 93 // it. 94 if (!CurrentContextNS.starts_with(TargetNS)) 95 return std::nullopt; 96 97 while (CurrentContextNS != TargetNS) { 98 CurContext = CurContext->getParent(); 99 // These colons always exists since TargetNS is a prefix of 100 // CurrentContextNS, it ends with "::" and they are not equal. 101 CurrentContextNS = CurrentContextNS.take_front( 102 CurrentContextNS.drop_back(2).rfind("::") + 2); 103 } 104 return CurContext; 105 } 106 107 // Returns source code for FD after applying Replacements. 108 // FIXME: Make the function take a parameter to return only the function body, 109 // afterwards it can be shared with define-inline code action. 110 llvm::Expected<std::string> 111 getFunctionSourceAfterReplacements(const FunctionDecl *FD, 112 const tooling::Replacements &Replacements, 113 bool TargetFileIsHeader) { 114 const auto &SM = FD->getASTContext().getSourceManager(); 115 auto OrigFuncRange = toHalfOpenFileRange( 116 SM, FD->getASTContext().getLangOpts(), FD->getSourceRange()); 117 if (!OrigFuncRange) 118 return error("Couldn't get range for function."); 119 120 // Get new begin and end positions for the qualified function definition. 121 unsigned FuncBegin = SM.getFileOffset(OrigFuncRange->getBegin()); 122 unsigned FuncEnd = Replacements.getShiftedCodePosition( 123 SM.getFileOffset(OrigFuncRange->getEnd())); 124 125 // Trim the result to function definition. 126 auto QualifiedFunc = tooling::applyAllReplacements( 127 SM.getBufferData(SM.getMainFileID()), Replacements); 128 if (!QualifiedFunc) 129 return QualifiedFunc.takeError(); 130 131 auto Source = QualifiedFunc->substr(FuncBegin, FuncEnd - FuncBegin + 1); 132 std::string TemplatePrefix; 133 auto AddToTemplatePrefixIfApplicable = [&](const Decl *D) { 134 const TemplateParameterList *Params = D->getDescribedTemplateParams(); 135 if (!Params) 136 return; 137 for (Decl *P : *Params) { 138 if (auto *TTP = dyn_cast<TemplateTypeParmDecl>(P)) 139 TTP->removeDefaultArgument(); 140 else if (auto *NTTP = dyn_cast<NonTypeTemplateParmDecl>(P)) 141 NTTP->removeDefaultArgument(); 142 else if (auto *TTPD = dyn_cast<TemplateTemplateParmDecl>(P)) 143 TTPD->removeDefaultArgument(); 144 } 145 std::string S; 146 llvm::raw_string_ostream Stream(S); 147 Params->print(Stream, FD->getASTContext()); 148 if (!S.empty()) 149 *S.rbegin() = '\n'; // Replace space with newline 150 TemplatePrefix.insert(0, S); 151 }; 152 AddToTemplatePrefixIfApplicable(FD); 153 if (auto *MD = llvm::dyn_cast<CXXMethodDecl>(FD)) { 154 for (const CXXRecordDecl *Parent = MD->getParent(); Parent; 155 Parent = 156 llvm::dyn_cast_or_null<const CXXRecordDecl>(Parent->getParent())) { 157 AddToTemplatePrefixIfApplicable(Parent); 158 } 159 } 160 161 if (TargetFileIsHeader) 162 Source.insert(0, "inline "); 163 if (!TemplatePrefix.empty()) 164 Source.insert(0, TemplatePrefix); 165 return Source; 166 } 167 168 // Returns replacements to delete tokens with kind `Kind` in the range 169 // `FromRange`. Removes matching instances of given token preceeding the 170 // function defition. 171 llvm::Expected<tooling::Replacements> 172 deleteTokensWithKind(const syntax::TokenBuffer &TokBuf, tok::TokenKind Kind, 173 SourceRange FromRange) { 174 tooling::Replacements DelKeywordCleanups; 175 llvm::Error Errors = llvm::Error::success(); 176 bool FoundAny = false; 177 for (const auto &Tok : TokBuf.expandedTokens(FromRange)) { 178 if (Tok.kind() != Kind) 179 continue; 180 FoundAny = true; 181 auto Spelling = TokBuf.spelledForExpanded(llvm::ArrayRef(Tok)); 182 if (!Spelling) { 183 Errors = llvm::joinErrors( 184 std::move(Errors), 185 error("define outline: couldn't remove `{0}` keyword.", 186 tok::getKeywordSpelling(Kind))); 187 break; 188 } 189 auto &SM = TokBuf.sourceManager(); 190 CharSourceRange DelRange = 191 syntax::Token::range(SM, Spelling->front(), Spelling->back()) 192 .toCharRange(SM); 193 if (auto Err = 194 DelKeywordCleanups.add(tooling::Replacement(SM, DelRange, ""))) 195 Errors = llvm::joinErrors(std::move(Errors), std::move(Err)); 196 } 197 if (!FoundAny) { 198 Errors = llvm::joinErrors( 199 std::move(Errors), 200 error("define outline: couldn't find `{0}` keyword to remove.", 201 tok::getKeywordSpelling(Kind))); 202 } 203 204 if (Errors) 205 return std::move(Errors); 206 return DelKeywordCleanups; 207 } 208 209 // Creates a modified version of function definition that can be inserted at a 210 // different location, qualifies return value and function name to achieve that. 211 // Contains function signature, except defaulted parameter arguments, body and 212 // template parameters if applicable. No need to qualify parameters, as they are 213 // looked up in the context containing the function/method. 214 // FIXME: Drop attributes in function signature. 215 llvm::Expected<std::string> 216 getFunctionSourceCode(const FunctionDecl *FD, const DeclContext *TargetContext, 217 const syntax::TokenBuffer &TokBuf, 218 const HeuristicResolver *Resolver, 219 bool TargetFileIsHeader) { 220 auto &AST = FD->getASTContext(); 221 auto &SM = AST.getSourceManager(); 222 223 llvm::Error Errors = llvm::Error::success(); 224 tooling::Replacements DeclarationCleanups; 225 226 // Finds the first unqualified name in function return type and name, then 227 // qualifies those to be valid in TargetContext. 228 findExplicitReferences( 229 FD, 230 [&](ReferenceLoc Ref) { 231 // It is enough to qualify the first qualifier, so skip references with 232 // a qualifier. Also we can't do much if there are no targets or name is 233 // inside a macro body. 234 if (Ref.Qualifier || Ref.Targets.empty() || Ref.NameLoc.isMacroID()) 235 return; 236 // Only qualify return type and function name. 237 if (Ref.NameLoc != FD->getReturnTypeSourceRange().getBegin() && 238 Ref.NameLoc != FD->getLocation()) 239 return; 240 241 for (const NamedDecl *ND : Ref.Targets) { 242 if (ND->getKind() == Decl::TemplateTypeParm) 243 return; 244 if (ND->getDeclContext() != Ref.Targets.front()->getDeclContext()) { 245 elog("Targets from multiple contexts: {0}, {1}", 246 printQualifiedName(*Ref.Targets.front()), 247 printQualifiedName(*ND)); 248 return; 249 } 250 } 251 const NamedDecl *ND = Ref.Targets.front(); 252 std::string Qualifier = 253 getQualification(AST, TargetContext, 254 SM.getLocForStartOfFile(SM.getMainFileID()), ND); 255 if (ND->getDeclContext()->isDependentContext() && 256 llvm::isa<TypeDecl>(ND)) { 257 Qualifier.insert(0, "typename "); 258 } 259 if (auto Err = DeclarationCleanups.add( 260 tooling::Replacement(SM, Ref.NameLoc, 0, Qualifier))) 261 Errors = llvm::joinErrors(std::move(Errors), std::move(Err)); 262 }, 263 Resolver); 264 265 // findExplicitReferences doesn't provide references to 266 // constructor/destructors, it only provides references to type names inside 267 // them. 268 // this works for constructors, but doesn't work for destructor as type name 269 // doesn't cover leading `~`, so handle it specially. 270 if (const auto *Destructor = llvm::dyn_cast<CXXDestructorDecl>(FD)) { 271 if (auto Err = DeclarationCleanups.add(tooling::Replacement( 272 SM, Destructor->getLocation(), 0, 273 getQualification(AST, TargetContext, 274 SM.getLocForStartOfFile(SM.getMainFileID()), 275 Destructor)))) 276 Errors = llvm::joinErrors(std::move(Errors), std::move(Err)); 277 } 278 279 // Get rid of default arguments, since they should not be specified in 280 // out-of-line definition. 281 for (const auto *PVD : FD->parameters()) { 282 if (!PVD->hasDefaultArg()) 283 continue; 284 // Deletion range spans the initializer, usually excluding the `=`. 285 auto DelRange = CharSourceRange::getTokenRange(PVD->getDefaultArgRange()); 286 // Get all tokens before the default argument. 287 auto Tokens = TokBuf.expandedTokens(PVD->getSourceRange()) 288 .take_while([&SM, &DelRange](const syntax::Token &Tok) { 289 return SM.isBeforeInTranslationUnit( 290 Tok.location(), DelRange.getBegin()); 291 }); 292 if (TokBuf.expandedTokens(DelRange.getAsRange()).front().kind() != 293 tok::equal) { 294 // Find the last `=` if it isn't included in the initializer, and update 295 // the DelRange to include it. 296 auto Tok = 297 llvm::find_if(llvm::reverse(Tokens), [](const syntax::Token &Tok) { 298 return Tok.kind() == tok::equal; 299 }); 300 assert(Tok != Tokens.rend()); 301 DelRange.setBegin(Tok->location()); 302 } 303 if (auto Err = 304 DeclarationCleanups.add(tooling::Replacement(SM, DelRange, ""))) 305 Errors = llvm::joinErrors(std::move(Errors), std::move(Err)); 306 } 307 308 auto DelAttr = [&](const Attr *A) { 309 if (!A) 310 return; 311 auto AttrTokens = 312 TokBuf.spelledForExpanded(TokBuf.expandedTokens(A->getRange())); 313 assert(A->getLocation().isValid()); 314 if (!AttrTokens || AttrTokens->empty()) { 315 Errors = llvm::joinErrors( 316 std::move(Errors), error("define outline: Can't move out of line as " 317 "function has a macro `{0}` specifier.", 318 A->getSpelling())); 319 return; 320 } 321 CharSourceRange DelRange = 322 syntax::Token::range(SM, AttrTokens->front(), AttrTokens->back()) 323 .toCharRange(SM); 324 if (auto Err = 325 DeclarationCleanups.add(tooling::Replacement(SM, DelRange, ""))) 326 Errors = llvm::joinErrors(std::move(Errors), std::move(Err)); 327 }; 328 329 DelAttr(FD->getAttr<OverrideAttr>()); 330 DelAttr(FD->getAttr<FinalAttr>()); 331 332 auto DelKeyword = [&](tok::TokenKind Kind, SourceRange FromRange) { 333 auto DelKeywords = deleteTokensWithKind(TokBuf, Kind, FromRange); 334 if (!DelKeywords) { 335 Errors = llvm::joinErrors(std::move(Errors), DelKeywords.takeError()); 336 return; 337 } 338 DeclarationCleanups = DeclarationCleanups.merge(*DelKeywords); 339 }; 340 341 if (FD->isInlineSpecified()) 342 DelKeyword(tok::kw_inline, {FD->getBeginLoc(), FD->getLocation()}); 343 if (const auto *MD = dyn_cast<CXXMethodDecl>(FD)) { 344 if (MD->isVirtualAsWritten()) 345 DelKeyword(tok::kw_virtual, {FD->getBeginLoc(), FD->getLocation()}); 346 if (MD->isStatic()) 347 DelKeyword(tok::kw_static, {FD->getBeginLoc(), FD->getLocation()}); 348 } 349 if (const auto *CD = dyn_cast<CXXConstructorDecl>(FD)) { 350 if (CD->isExplicit()) 351 DelKeyword(tok::kw_explicit, {FD->getBeginLoc(), FD->getLocation()}); 352 } 353 354 if (Errors) 355 return std::move(Errors); 356 return getFunctionSourceAfterReplacements(FD, DeclarationCleanups, 357 TargetFileIsHeader); 358 } 359 360 struct InsertionPoint { 361 const DeclContext *EnclosingNamespace = nullptr; 362 size_t Offset; 363 }; 364 365 // Returns the range that should be deleted from declaration, which always 366 // contains function body. In addition to that it might contain constructor 367 // initializers. 368 SourceRange getDeletionRange(const FunctionDecl *FD, 369 const syntax::TokenBuffer &TokBuf) { 370 auto DeletionRange = FD->getBody()->getSourceRange(); 371 if (auto *CD = llvm::dyn_cast<CXXConstructorDecl>(FD)) { 372 // AST doesn't contain the location for ":" in ctor initializers. Therefore 373 // we find it by finding the first ":" before the first ctor initializer. 374 SourceLocation InitStart; 375 // Find the first initializer. 376 for (const auto *CInit : CD->inits()) { 377 // SourceOrder is -1 for implicit initializers. 378 if (CInit->getSourceOrder() != 0) 379 continue; 380 InitStart = CInit->getSourceLocation(); 381 break; 382 } 383 if (InitStart.isValid()) { 384 auto Toks = TokBuf.expandedTokens(CD->getSourceRange()); 385 // Drop any tokens after the initializer. 386 Toks = Toks.take_while([&TokBuf, &InitStart](const syntax::Token &Tok) { 387 return TokBuf.sourceManager().isBeforeInTranslationUnit(Tok.location(), 388 InitStart); 389 }); 390 // Look for the first colon. 391 auto Tok = 392 llvm::find_if(llvm::reverse(Toks), [](const syntax::Token &Tok) { 393 return Tok.kind() == tok::colon; 394 }); 395 assert(Tok != Toks.rend()); 396 DeletionRange.setBegin(Tok->location()); 397 } 398 } 399 return DeletionRange; 400 } 401 402 /// Moves definition of a function/method to an appropriate implementation file. 403 /// 404 /// Before: 405 /// a.h 406 /// void foo() { return; } 407 /// a.cc 408 /// #include "a.h" 409 /// 410 /// ---------------- 411 /// 412 /// After: 413 /// a.h 414 /// void foo(); 415 /// a.cc 416 /// #include "a.h" 417 /// void foo() { return; } 418 class DefineOutline : public Tweak { 419 public: 420 const char *id() const override; 421 422 bool hidden() const override { return false; } 423 llvm::StringLiteral kind() const override { 424 return CodeAction::REFACTOR_KIND; 425 } 426 std::string title() const override { 427 return "Move function body to out-of-line"; 428 } 429 430 bool prepare(const Selection &Sel) override { 431 SameFile = !isHeaderFile(Sel.AST->tuPath(), Sel.AST->getLangOpts()); 432 Source = getSelectedFunction(Sel.ASTSelection.commonAncestor()); 433 434 // Bail out if the selection is not a in-line function definition. 435 if (!Source || !Source->doesThisDeclarationHaveABody() || 436 Source->isOutOfLine()) 437 return false; 438 439 // Bail out if this is a function template specialization, as their 440 // definitions need to be visible in all including translation units. 441 if (Source->getTemplateSpecializationInfo()) 442 return false; 443 444 auto *MD = llvm::dyn_cast<CXXMethodDecl>(Source); 445 if (!MD) { 446 if (Source->getDescribedFunctionTemplate()) 447 return false; 448 // Can't outline free-standing functions in the same file. 449 return !SameFile; 450 } 451 452 for (const CXXRecordDecl *Parent = MD->getParent(); Parent; 453 Parent = 454 llvm::dyn_cast_or_null<const CXXRecordDecl>(Parent->getParent())) { 455 if (const TemplateParameterList *Params = 456 Parent->getDescribedTemplateParams()) { 457 458 // Class template member functions must be defined in the 459 // same file. 460 SameFile = true; 461 462 // Bail out if the template parameter is unnamed. 463 for (NamedDecl *P : *Params) { 464 if (!P->getIdentifier()) 465 return false; 466 } 467 } 468 } 469 470 // Function templates must be defined in the same file. 471 if (MD->getDescribedTemplate()) 472 SameFile = true; 473 474 // The refactoring is meaningless for unnamed classes and namespaces, 475 // unless we're outlining in the same file 476 for (const DeclContext *DC = MD->getParent(); DC; DC = DC->getParent()) { 477 if (auto *ND = llvm::dyn_cast<NamedDecl>(DC)) { 478 if (ND->getDeclName().isEmpty() && 479 (!SameFile || !llvm::dyn_cast<NamespaceDecl>(ND))) 480 return false; 481 } 482 } 483 484 // Note that we don't check whether an implementation file exists or not in 485 // the prepare, since performing disk IO on each prepare request might be 486 // expensive. 487 return true; 488 } 489 490 Expected<Effect> apply(const Selection &Sel) override { 491 const SourceManager &SM = Sel.AST->getSourceManager(); 492 auto CCFile = SameFile ? Sel.AST->tuPath().str() 493 : getSourceFile(Sel.AST->tuPath(), Sel); 494 if (!CCFile) 495 return error("Couldn't find a suitable implementation file."); 496 assert(Sel.FS && "FS Must be set in apply"); 497 auto Buffer = Sel.FS->getBufferForFile(*CCFile); 498 // FIXME: Maybe we should consider creating the implementation file if it 499 // doesn't exist? 500 if (!Buffer) 501 return llvm::errorCodeToError(Buffer.getError()); 502 auto Contents = Buffer->get()->getBuffer(); 503 auto InsertionPoint = getInsertionPoint(Contents, Sel); 504 if (!InsertionPoint) 505 return InsertionPoint.takeError(); 506 507 auto FuncDef = getFunctionSourceCode( 508 Source, InsertionPoint->EnclosingNamespace, Sel.AST->getTokens(), 509 Sel.AST->getHeuristicResolver(), 510 SameFile && isHeaderFile(Sel.AST->tuPath(), Sel.AST->getLangOpts())); 511 if (!FuncDef) 512 return FuncDef.takeError(); 513 514 SourceManagerForFile SMFF(*CCFile, Contents); 515 const tooling::Replacement InsertFunctionDef( 516 *CCFile, InsertionPoint->Offset, 0, *FuncDef); 517 auto Effect = Effect::mainFileEdit( 518 SMFF.get(), tooling::Replacements(InsertFunctionDef)); 519 if (!Effect) 520 return Effect.takeError(); 521 522 tooling::Replacements HeaderUpdates(tooling::Replacement( 523 Sel.AST->getSourceManager(), 524 CharSourceRange::getTokenRange(*toHalfOpenFileRange( 525 SM, Sel.AST->getLangOpts(), 526 getDeletionRange(Source, Sel.AST->getTokens()))), 527 ";")); 528 529 if (Source->isInlineSpecified()) { 530 auto DelInline = 531 deleteTokensWithKind(Sel.AST->getTokens(), tok::kw_inline, 532 {Source->getBeginLoc(), Source->getLocation()}); 533 if (!DelInline) 534 return DelInline.takeError(); 535 HeaderUpdates = HeaderUpdates.merge(*DelInline); 536 } 537 538 if (SameFile) { 539 tooling::Replacements &R = Effect->ApplyEdits[*CCFile].Replacements; 540 R = R.merge(HeaderUpdates); 541 } else { 542 auto HeaderFE = Effect::fileEdit(SM, SM.getMainFileID(), HeaderUpdates); 543 if (!HeaderFE) 544 return HeaderFE.takeError(); 545 Effect->ApplyEdits.try_emplace(HeaderFE->first, 546 std::move(HeaderFE->second)); 547 } 548 return std::move(*Effect); 549 } 550 551 // Returns the most natural insertion point for \p QualifiedName in \p 552 // Contents. This currently cares about only the namespace proximity, but in 553 // feature it should also try to follow ordering of declarations. For example, 554 // if decls come in order `foo, bar, baz` then this function should return 555 // some point between foo and baz for inserting bar. 556 // FIXME: The selection can be made smarter by looking at the definition 557 // locations for adjacent decls to Source. Unfortunately pseudo parsing in 558 // getEligibleRegions only knows about namespace begin/end events so we 559 // can't match function start/end positions yet. 560 llvm::Expected<InsertionPoint> getInsertionPoint(llvm::StringRef Contents, 561 const Selection &Sel) { 562 // If the definition goes to the same file and there is a namespace, 563 // we should (and, in the case of anonymous namespaces, need to) 564 // put the definition into the original namespace block. 565 if (SameFile) { 566 auto *Klass = Source->getDeclContext()->getOuterLexicalRecordContext(); 567 if (!Klass) 568 return error("moving to same file not supported for free functions"); 569 const SourceLocation EndLoc = Klass->getBraceRange().getEnd(); 570 const auto &TokBuf = Sel.AST->getTokens(); 571 auto Tokens = TokBuf.expandedTokens(); 572 auto It = llvm::lower_bound( 573 Tokens, EndLoc, [](const syntax::Token &Tok, SourceLocation EndLoc) { 574 return Tok.location() < EndLoc; 575 }); 576 while (It != Tokens.end()) { 577 if (It->kind() != tok::semi) { 578 ++It; 579 continue; 580 } 581 unsigned Offset = Sel.AST->getSourceManager() 582 .getDecomposedLoc(It->endLocation()) 583 .second; 584 return InsertionPoint{Klass->getEnclosingNamespaceContext(), Offset}; 585 } 586 return error( 587 "failed to determine insertion location: no end of class found"); 588 } 589 590 auto Region = getEligiblePoints( 591 Contents, Source->getQualifiedNameAsString(), Sel.AST->getLangOpts()); 592 593 assert(!Region.EligiblePoints.empty()); 594 auto Offset = positionToOffset(Contents, Region.EligiblePoints.back()); 595 if (!Offset) 596 return Offset.takeError(); 597 598 auto TargetContext = 599 findContextForNS(Region.EnclosingNamespace, Source->getDeclContext()); 600 if (!TargetContext) 601 return error("define outline: couldn't find a context for target"); 602 603 return InsertionPoint{*TargetContext, *Offset}; 604 } 605 606 private: 607 const FunctionDecl *Source = nullptr; 608 bool SameFile = false; 609 }; 610 611 REGISTER_TWEAK(DefineOutline) 612 613 } // namespace 614 } // namespace clangd 615 } // namespace clang 616