clang-tools 20.0.0git
DefineOutline.cpp
Go to the documentation of this file.
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
38namespace clang {
39namespace clangd {
40namespace {
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.
47const 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
61std::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.
72std::optional<const DeclContext *>
73findContextForNS(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.
110llvm::Expected<std::string>
111getFunctionSourceAfterReplacements(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.
171llvm::Expected<tooling::Replacements>
172deleteTokensWithKind(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.
215llvm::Expected<std::string>
216getFunctionSourceCode(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.
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
360struct 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.
368SourceRange 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; }
418class DefineOutline : public Tweak {
419public:
420 const char *id() const override;
421
422 bool hidden() const override { return false; }
423 llvm::StringLiteral kind() const override {
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
606private:
607 const FunctionDecl *Source = nullptr;
608 bool SameFile = false;
609};
610
611REGISTER_TWEAK(DefineOutline)
612
613} // namespace
614} // namespace clangd
615} // namespace clang
const FunctionDecl * Decl
BindArgumentKind Kind
size_t Offset
std::optional< std::string > EnclosingNamespace
int Errors
const Node * Parent
StringRef FileName
#define REGISTER_TWEAK(Subclass)
Definition: Tweak.h:129
std::optional< SourceRange > toHalfOpenFileRange(const SourceManager &SM, const LangOptions &LangOpts, SourceRange R)
Turns a token range into a half-open range and checks its correctness.
Definition: SourceCode.cpp:430
std::string getQualification(ASTContext &Context, const DeclContext *DestContext, SourceLocation InsertionPoint, const NamedDecl *ND)
Gets the nested name specifier necessary for spelling ND in DestContext, at InsertionPoint.
Definition: AST.cpp:660
void findExplicitReferences(const Stmt *S, llvm::function_ref< void(ReferenceLoc)> Out, const HeuristicResolver *Resolver)
Recursively traverse S and report all references explicitly written in the code.
llvm::Error error(std::error_code EC, const char *Fmt, Ts &&... Vals)
Definition: Logger.h:79
EligibleRegion getEligiblePoints(llvm::StringRef Code, llvm::StringRef FullyQualifiedName, const LangOptions &LangOpts)
Returns most eligible region to insert a definition for FullyQualifiedName in the Code.
llvm::Expected< size_t > positionToOffset(llvm::StringRef Code, Position P, bool AllowColumnsBeyondLineLength)
Turn a [line, column] pair into an offset in Code.
Definition: SourceCode.cpp:173
std::optional< Path > getCorrespondingHeaderOrSource(PathRef OriginalFile, llvm::IntrusiveRefCntPtr< llvm::vfs::FileSystem > VFS)
Given a header file, returns the best matching source file, and vice visa.
bool isHeaderFile(llvm::StringRef FileName, std::optional< LangOptions > LangOpts)
Infers whether this is a header from the FileName and LangOpts (if presents).
===– Representation.cpp - ClangDoc Representation --------—*- C++ -*-===//
static const llvm::StringLiteral REFACTOR_KIND
Definition: Protocol.h:1074