19#include "clang/AST/ASTTypeTraits.h"
20#include "clang/AST/Attr.h"
21#include "clang/AST/Decl.h"
22#include "clang/AST/DeclBase.h"
23#include "clang/AST/DeclCXX.h"
24#include "clang/AST/DeclTemplate.h"
25#include "clang/AST/Stmt.h"
26#include "clang/Basic/SourceLocation.h"
27#include "clang/Basic/SourceManager.h"
28#include "clang/Basic/TokenKinds.h"
29#include "clang/Tooling/Core/Replacement.h"
30#include "clang/Tooling/Syntax/Tokens.h"
31#include "llvm/ADT/STLExtras.h"
32#include "llvm/ADT/StringRef.h"
33#include "llvm/Support/Casting.h"
34#include "llvm/Support/Error.h"
52 const DynTypedNode &AstNode = SelNode->
ASTNode;
53 if (
const FunctionDecl *FD = AstNode.get<FunctionDecl>())
55 if (AstNode.get<CompoundStmt>() &&
58 return P->ASTNode.get<FunctionDecl>();
63std::optional<Path> getSourceFile(llvm::StringRef FileName,
74std::optional<const DeclContext *>
75findContextForNS(llvm::StringRef TargetNS,
const DeclContext *CurContext) {
76 assert(TargetNS.empty() || TargetNS.ends_with(
"::"));
78 CurContext = CurContext->getEnclosingNamespaceContext();
80 if (TargetNS.empty()) {
81 while (!CurContext->isTranslationUnit())
82 CurContext = CurContext->getParent();
87 std::string TargetContextNS =
88 CurContext->isNamespace()
89 ? llvm::cast<NamespaceDecl>(CurContext)->getQualifiedNameAsString()
91 TargetContextNS.append(
"::");
93 llvm::StringRef CurrentContextNS(TargetContextNS);
96 if (!CurrentContextNS.starts_with(TargetNS))
99 while (CurrentContextNS != TargetNS) {
100 CurContext = CurContext->getParent();
103 CurrentContextNS = CurrentContextNS.take_front(
104 CurrentContextNS.drop_back(2).rfind(
"::") + 2);
112llvm::Expected<std::string>
113getFunctionSourceAfterReplacements(
const FunctionDecl *FD,
114 const tooling::Replacements &Replacements,
115 bool TargetFileIsHeader) {
116 const auto &SM = FD->getASTContext().getSourceManager();
118 SM, FD->getASTContext().getLangOpts(), FD->getSourceRange());
120 return error(
"Couldn't get range for function.");
123 unsigned FuncBegin = SM.getFileOffset(OrigFuncRange->getBegin());
124 unsigned FuncEnd = Replacements.getShiftedCodePosition(
125 SM.getFileOffset(OrigFuncRange->getEnd()));
128 auto QualifiedFunc = tooling::applyAllReplacements(
129 SM.getBufferData(SM.getMainFileID()), Replacements);
131 return QualifiedFunc.takeError();
133 auto Source = QualifiedFunc->substr(FuncBegin, FuncEnd - FuncBegin + 1);
134 std::string TemplatePrefix;
135 auto AddToTemplatePrefixIfApplicable = [&](
const Decl *
D) {
136 const TemplateParameterList *Params =
D->getDescribedTemplateParams();
139 for (Decl *P : *Params) {
140 if (
auto *TTP = dyn_cast<TemplateTypeParmDecl>(P))
141 TTP->removeDefaultArgument();
142 else if (
auto *NTTP = dyn_cast<NonTypeTemplateParmDecl>(P))
143 NTTP->removeDefaultArgument();
144 else if (
auto *TTPD = dyn_cast<TemplateTemplateParmDecl>(P))
145 TTPD->removeDefaultArgument();
148 llvm::raw_string_ostream Stream(S);
149 Params->print(Stream, FD->getASTContext());
152 TemplatePrefix.insert(0, S);
154 AddToTemplatePrefixIfApplicable(FD);
155 if (
auto *MD = llvm::dyn_cast<CXXMethodDecl>(FD)) {
156 for (
const CXXRecordDecl *Parent = MD->getParent(); Parent;
158 llvm::dyn_cast_or_null<const CXXRecordDecl>(Parent->getParent())) {
159 AddToTemplatePrefixIfApplicable(Parent);
163 if (TargetFileIsHeader)
164 Source.insert(0,
"inline ");
165 if (!TemplatePrefix.empty())
166 Source.insert(0, TemplatePrefix);
173llvm::Expected<tooling::Replacements>
174deleteTokensWithKind(
const syntax::TokenBuffer &TokBuf, tok::TokenKind Kind,
175 SourceRange FromRange) {
176 tooling::Replacements DelKeywordCleanups;
177 llvm::Error Errors = llvm::Error::success();
178 bool FoundAny =
false;
179 for (
const auto &Tok : TokBuf.expandedTokens(FromRange)) {
180 if (Tok.kind() != Kind)
183 auto Spelling = TokBuf.spelledForExpanded(llvm::ArrayRef(Tok));
185 Errors = llvm::joinErrors(
187 error(
"define outline: couldn't remove `{0}` keyword.",
188 tok::getKeywordSpelling(Kind)));
191 auto &SM = TokBuf.sourceManager();
192 CharSourceRange DelRange =
193 syntax::Token::range(SM, Spelling->front(), Spelling->back())
196 DelKeywordCleanups.add(tooling::Replacement(SM, DelRange,
"")))
197 Errors = llvm::joinErrors(std::move(Errors), std::move(Err));
200 Errors = llvm::joinErrors(
202 error(
"define outline: couldn't find `{0}` keyword to remove.",
203 tok::getKeywordSpelling(Kind)));
207 return std::move(Errors);
208 return DelKeywordCleanups;
217llvm::Expected<std::string>
218getFunctionSourceCode(
const FunctionDecl *FD,
const DeclContext *TargetContext,
219 const syntax::TokenBuffer &TokBuf,
220 const HeuristicResolver *Resolver,
221 bool TargetFileIsHeader) {
222 auto &
AST = FD->getASTContext();
223 auto &SM =
AST.getSourceManager();
225 llvm::Error Errors = llvm::Error::success();
226 tooling::Replacements DeclarationCleanups;
236 if (
Ref.Qualifier ||
Ref.Targets.empty() ||
Ref.NameLoc.isMacroID())
239 if (
Ref.NameLoc != FD->getReturnTypeSourceRange().getBegin() &&
240 Ref.NameLoc != FD->getLocation())
243 for (
const NamedDecl *ND :
Ref.Targets) {
244 if (ND->getKind() == Decl::TemplateTypeParm)
246 if (ND->getDeclContext() !=
Ref.Targets.front()->getDeclContext()) {
247 elog(
"Targets from multiple contexts: {0}, {1}",
253 const NamedDecl *ND =
Ref.Targets.front();
254 std::string Qualifier =
256 SM.getLocForStartOfFile(SM.getMainFileID()), ND);
257 if (ND->getDeclContext()->isDependentContext() &&
258 llvm::isa<TypeDecl>(ND)) {
259 Qualifier.insert(0,
"typename ");
261 if (
auto Err = DeclarationCleanups.add(
262 tooling::Replacement(SM,
Ref.NameLoc, 0, Qualifier)))
263 Errors = llvm::joinErrors(std::move(Errors), std::move(Err));
272 if (
const auto *Destructor = llvm::dyn_cast<CXXDestructorDecl>(FD)) {
273 if (
auto Err = DeclarationCleanups.add(tooling::Replacement(
274 SM, Destructor->getLocation(), 0,
276 SM.getLocForStartOfFile(SM.getMainFileID()),
278 Errors = llvm::joinErrors(std::move(Errors), std::move(Err));
283 for (
const auto *PVD : FD->parameters()) {
284 if (!PVD->hasDefaultArg())
287 auto DelRange = CharSourceRange::getTokenRange(PVD->getDefaultArgRange());
289 auto Tokens = TokBuf.expandedTokens(PVD->getSourceRange())
290 .take_while([&SM, &DelRange](
const syntax::Token &Tok) {
291 return SM.isBeforeInTranslationUnit(
292 Tok.location(), DelRange.getBegin());
294 if (TokBuf.expandedTokens(DelRange.getAsRange()).front().kind() !=
299 llvm::find_if(llvm::reverse(Tokens), [](
const syntax::Token &Tok) {
300 return Tok.kind() == tok::equal;
302 assert(Tok != Tokens.rend());
303 DelRange.setBegin(Tok->location());
306 DeclarationCleanups.add(tooling::Replacement(SM, DelRange,
"")))
307 Errors = llvm::joinErrors(std::move(Errors), std::move(Err));
310 auto DelAttr = [&](
const Attr *
A) {
314 TokBuf.spelledForExpanded(TokBuf.expandedTokens(
A->getRange()));
315 assert(
A->getLocation().isValid());
316 if (!AttrTokens || AttrTokens->empty()) {
317 Errors = llvm::joinErrors(
318 std::move(Errors),
error(
"define outline: Can't move out of line as "
319 "function has a macro `{0}` specifier.",
323 CharSourceRange DelRange =
324 syntax::Token::range(SM, AttrTokens->front(), AttrTokens->back())
327 DeclarationCleanups.add(tooling::Replacement(SM, DelRange,
"")))
328 Errors = llvm::joinErrors(std::move(Errors), std::move(Err));
331 DelAttr(FD->getAttr<OverrideAttr>());
332 DelAttr(FD->getAttr<FinalAttr>());
334 auto DelKeyword = [&](tok::TokenKind Kind, SourceRange FromRange) {
335 auto DelKeywords = deleteTokensWithKind(TokBuf, Kind, FromRange);
337 Errors = llvm::joinErrors(std::move(Errors), DelKeywords.takeError());
340 DeclarationCleanups = DeclarationCleanups.merge(*DelKeywords);
343 if (FD->isInlineSpecified())
344 DelKeyword(tok::kw_inline, {FD->getBeginLoc(), FD->getLocation()});
345 if (
const auto *MD = dyn_cast<CXXMethodDecl>(FD)) {
346 if (MD->isVirtualAsWritten())
347 DelKeyword(tok::kw_virtual, {FD->getBeginLoc(), FD->getLocation()});
349 DelKeyword(tok::kw_static, {FD->getBeginLoc(), FD->getLocation()});
351 if (
const auto *CD = dyn_cast<CXXConstructorDecl>(FD)) {
352 if (CD->isExplicit())
353 DelKeyword(tok::kw_explicit, {FD->getBeginLoc(), FD->getLocation()});
357 return std::move(Errors);
358 return getFunctionSourceAfterReplacements(FD, DeclarationCleanups,
362struct InsertionPoint {
363 const DeclContext *EnclosingNamespace =
nullptr;
367enum class RelativeInsertPos { Before, After };
368struct InsertionAnchor {
370 RelativeInsertPos RelInsertPos = RelativeInsertPos::Before;
376SourceRange getDeletionRange(
const FunctionDecl *FD,
377 const syntax::TokenBuffer &TokBuf) {
378 auto DeletionRange = FD->getBody()->getSourceRange();
379 if (
auto *CD = llvm::dyn_cast<CXXConstructorDecl>(FD)) {
382 SourceLocation InitStart;
384 for (
const auto *CInit : CD->inits()) {
386 if (CInit->getSourceOrder() != 0)
388 InitStart = CInit->getSourceLocation();
391 if (InitStart.isValid()) {
392 auto Toks = TokBuf.expandedTokens(CD->getSourceRange());
394 Toks = Toks.take_while([&TokBuf, &InitStart](
const syntax::Token &Tok) {
395 return TokBuf.sourceManager().isBeforeInTranslationUnit(Tok.location(),
400 llvm::find_if(llvm::reverse(Toks), [](
const syntax::Token &Tok) {
401 return Tok.kind() == tok::colon;
403 assert(Tok != Toks.rend());
404 DeletionRange.setBegin(Tok->location());
407 return DeletionRange;
426class DefineOutline :
public Tweak {
428 const char *id()
const override;
430 bool hidden()
const override {
return false; }
431 llvm::StringLiteral kind()
const override {
434 std::string title()
const override {
435 return "Move function body to out-of-line";
438 bool prepare(
const Selection &Sel)
override {
439 SameFile = !
isHeaderFile(Sel.AST->tuPath(), Sel.AST->getLangOpts());
440 Source = getSelectedFunction(Sel.ASTSelection.commonAncestor());
443 if (!Source || !Source->doesThisDeclarationHaveABody() ||
444 Source->isOutOfLine())
449 if (Source->getTemplateSpecializationInfo())
452 auto *MD = llvm::dyn_cast<CXXMethodDecl>(Source);
454 if (Source->getDescribedFunctionTemplate())
460 for (
const CXXRecordDecl *Parent = MD->getParent(); Parent;
462 llvm::dyn_cast_or_null<const CXXRecordDecl>(Parent->getParent())) {
463 if (
const TemplateParameterList *Params =
464 Parent->getDescribedTemplateParams()) {
471 for (NamedDecl *P : *Params) {
472 if (!
P->getIdentifier())
479 if (MD->getDescribedTemplate())
484 for (
const DeclContext *DC = MD->getParent(); DC; DC = DC->getParent()) {
485 if (
auto *ND = llvm::dyn_cast<NamedDecl>(DC)) {
486 if (ND->getDeclName().isEmpty() &&
487 (!SameFile || !llvm::dyn_cast<NamespaceDecl>(ND)))
498 Expected<Effect> apply(
const Selection &Sel)
override {
499 const SourceManager &SM = Sel.AST->getSourceManager();
500 std::optional<Path> CCFile;
501 auto Anchor = getDefinitionOfAdjacentDecl(Sel);
503 CCFile = Anchor->Loc.uri.file();
505 CCFile = SameFile ? Sel.AST->tuPath().str()
506 : getSourceFile(Sel.AST->tuPath(), Sel);
509 return error(
"Couldn't find a suitable implementation file.");
510 assert(Sel.FS &&
"FS Must be set in apply");
511 auto Buffer = Sel.FS->getBufferForFile(*CCFile);
515 return llvm::errorCodeToError(Buffer.getError());
517 auto Contents = Buffer->get()->getBuffer();
518 SourceManagerForFile SMFF(*CCFile, Contents);
520 std::optional<Position> InsertionPos;
522 if (
auto P = getInsertionPointFromExistingDefinition(
523 SMFF, **Buffer, Anchor->Loc, Anchor->RelInsertPos, Sel.AST)) {
528 std::optional<std::size_t> Offset;
529 const DeclContext *EnclosingNamespace =
nullptr;
530 std::string EnclosingNamespaceName;
534 Sel.AST->getLangOpts());
535 }
else if (SameFile) {
536 auto P = getInsertionPointInMainFile(Sel.AST);
538 return P.takeError();
540 EnclosingNamespace =
P->EnclosingNamespace;
543 Contents, Source->getQualifiedNameAsString(), Sel.AST->getLangOpts());
544 assert(!Region.EligiblePoints.empty());
545 EnclosingNamespaceName = Region.EnclosingNamespace;
546 InsertionPos = Region.EligiblePoints.back();
552 return O.takeError();
555 findContextForNS(EnclosingNamespaceName, Source->getDeclContext());
557 return error(
"define outline: couldn't find a context for target");
558 EnclosingNamespace = *TargetContext;
562 assert(EnclosingNamespace);
564 auto FuncDef = getFunctionSourceCode(
565 Source, EnclosingNamespace, Sel.AST->getTokens(),
566 Sel.AST->getHeuristicResolver(),
567 SameFile &&
isHeaderFile(Sel.AST->tuPath(), Sel.AST->getLangOpts()));
569 return FuncDef.takeError();
571 const tooling::Replacement InsertFunctionDef(*CCFile, *Offset, 0, *FuncDef);
572 auto Effect = Effect::mainFileEdit(
573 SMFF.get(), tooling::Replacements(InsertFunctionDef));
575 return Effect.takeError();
577 tooling::Replacements HeaderUpdates(tooling::Replacement(
578 Sel.AST->getSourceManager(),
580 SM, Sel.AST->getLangOpts(),
581 getDeletionRange(Source, Sel.AST->getTokens()))),
584 if (Source->isInlineSpecified()) {
586 deleteTokensWithKind(Sel.AST->getTokens(), tok::kw_inline,
587 {Source->getBeginLoc(), Source->getLocation()});
589 return DelInline.takeError();
590 HeaderUpdates = HeaderUpdates.merge(*DelInline);
594 tooling::Replacements &R = Effect->ApplyEdits[*CCFile].Replacements;
595 R = R.merge(HeaderUpdates);
597 auto HeaderFE = Effect::fileEdit(SM, SM.getMainFileID(), HeaderUpdates);
599 return HeaderFE.takeError();
600 Effect->ApplyEdits.try_emplace(HeaderFE->first,
601 std::move(HeaderFE->second));
603 return std::move(*Effect);
606 std::optional<InsertionAnchor>
607 getDefinitionOfAdjacentDecl(
const Selection &Sel) {
610 std::optional<Location> Anchor;
612 auto CheckCandidate = [&](Decl *Candidate) {
613 assert(Candidate != Source);
614 if (
auto Func = llvm::dyn_cast_or_null<FunctionDecl>(Candidate);
615 !Func || Func->isThisDeclarationADefinition()) {
618 std::optional<Location> CandidateLoc;
619 Sel.Index->lookup({{
getSymbolID(Candidate)}}, [&](
const Symbol &S) {
624 log(
"getDefinitionOfAdjacentDecl: {0}", Loc.takeError());
638 bool CandidateSameFile = TuURI == CandidateLoc->uri.uri();
639 if (SameFile && !CandidateSameFile)
641 if (!SameFile && CandidateSameFile) {
642 if (Candidate->isTemplateDecl())
646 Anchor = *CandidateLoc;
652 const DeclContext *ParentContext = Source->getParent();
653 const auto SourceIt = llvm::find_if(
654 ParentContext->decls(), [
this](
const Decl *D) { return D == Source; });
655 if (SourceIt == ParentContext->decls_end())
657 const int Preceding = std::distance(ParentContext->decls_begin(), SourceIt);
658 const int Following =
659 std::distance(SourceIt, ParentContext->decls_end()) - 1;
660 for (
int Offset = 1; Offset <= Preceding || Offset <= Following; ++Offset) {
661 if (Offset <= Preceding)
663 *std::next(ParentContext->decls_begin(), Preceding - Offset));
665 return InsertionAnchor{*Anchor, RelativeInsertPos::After};
666 if (Offset <= Following)
667 CheckCandidate(*std::next(SourceIt, Offset));
669 return InsertionAnchor{*Anchor, RelativeInsertPos::Before};
677 std::optional<Position> getInsertionPointFromExistingDefinition(
678 SourceManagerForFile &SMFF,
const llvm::MemoryBuffer &Buffer,
679 const Location &Loc, RelativeInsertPos RelInsertPos, ParsedAST *
AST) {
683 SourceLocation InsertionLoc;
684 SourceManager &SM = SMFF.get();
686 auto InsertBefore = [&] {
693 auto Tokens = syntax::tokenize(
694 syntax::FileRange(SM.getMainFileID(), 0, *StartOffset), SM,
698 for (
auto I = std::rbegin(Tokens);
699 InsertionLoc.isInvalid() && I != std::rend(Tokens); ++I) {
704 if (I != std::rbegin(Tokens))
705 InsertionLoc = std::prev(I)->location();
707 InsertionLoc = I->endLocation();
713 if (InsertionLoc.isInvalid())
714 InsertionLoc = Tokens.front().location();
717 if (RelInsertPos == RelativeInsertPos::Before) {
724 syntax::tokenize(syntax::FileRange(SM.getMainFileID(), *StartOffset,
725 Buffer.getBuffer().size()),
726 SM,
AST->getLangOpts());
727 bool SkippedParams =
false;
730 std::optional<syntax::Token> Tok;
731 for (
const auto &T : Tokens) {
732 tok::TokenKind StartKind = SkippedParams ? tok::l_brace : tok::l_paren;
733 tok::TokenKind EndKind = SkippedParams ? tok::r_brace : tok::r_paren;
734 int &Count = SkippedParams ? OpenBraces : OpenParens;
735 if (
T.kind() == StartKind) {
737 }
else if (
T.kind() == EndKind) {
743 SkippedParams =
true;
744 }
else if (Count < 0) {
750 InsertionLoc = Tok->endLocation();
755 if (!InsertionLoc.isValid())
763 llvm::Expected<InsertionPoint> getInsertionPointInMainFile(ParsedAST *
AST) {
767 auto *Klass = Source->getDeclContext()->getOuterLexicalRecordContext();
769 return error(
"moving to same file not supported for free functions");
770 const SourceLocation EndLoc = Klass->getBraceRange().getEnd();
771 const auto &TokBuf =
AST->getTokens();
772 auto Tokens = TokBuf.expandedTokens();
773 auto It = llvm::lower_bound(
774 Tokens, EndLoc, [](
const syntax::Token &Tok, SourceLocation EndLoc) {
775 return Tok.location() < EndLoc;
777 while (It != Tokens.end()) {
778 if (It->kind() != tok::semi) {
783 AST->getSourceManager().getDecomposedLoc(It->endLocation()).second;
784 return InsertionPoint{Klass->getEnclosingNamespaceContext(), Offset};
787 "failed to determine insertion location: no end of class found");
791 const FunctionDecl *Source =
nullptr;
792 bool SameFile =
false;
#define REGISTER_TWEAK(Subclass)
An interface base for small context-sensitive refactoring actions.
static URI createFile(llvm::StringRef AbsolutePath)
This creates a file:// URI for AbsolutePath. The path must be absolute.
std::string toString() const
Returns a string URI with all components percent-encoded.
FIXME: Skip testing on windows temporarily due to the different escaping code mode.
llvm::Expected< Location > indexToLSPLocation(const SymbolLocation &Loc, llvm::StringRef TUPath)
Helper function for deriving an LSP Location from an index SymbolLocation.
SymbolID getSymbolID(const Decl *D)
Gets the symbol ID for a declaration. Returned SymbolID might be null.
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.
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.
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)
Position sourceLocToPosition(const SourceManager &SM, SourceLocation Loc)
Turn a SourceLocation into a [line, column] pair.
void log(const char *Fmt, Ts &&... Vals)
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.
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.
std::string getNamespaceAtPosition(StringRef Code, const Position &Pos, const LangOptions &LangOpts)
std::string printQualifiedName(const NamedDecl &ND)
Returns the qualified name of ND.
void elog(const char *Fmt, Ts &&... Vals)
bool isHeaderFile(llvm::StringRef FileName, std::optional< LangOptions > LangOpts)
Infers whether this is a header from the FileName and LangOpts (if presents).
cppcoreguidelines::ProBoundsAvoidUncheckedContainerAccessCheck P
===– Representation.cpp - ClangDoc Representation --------—*- C++ -*-===//
Simplified description of a clang AST node.
static const llvm::StringLiteral REFACTOR_KIND
Represents a symbol occurrence in the source file.
Information about a reference written in the source code, independent of the actual AST node that thi...
Input to prepare and apply tweaks.