xref: /llvm-project/clang-tools-extra/clangd/refactor/tweaks/DefineOutline.cpp (revision 9acd8e381091765a932d54bc22359cdafa9e75c6)
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