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/Driver/Types.h"
29 #include "clang/Format/Format.h"
30 #include "clang/Lex/Lexer.h"
31 #include "clang/Tooling/Core/Replacement.h"
32 #include "clang/Tooling/Syntax/Tokens.h"
33 #include "llvm/ADT/None.h"
34 #include "llvm/ADT/Optional.h"
35 #include "llvm/ADT/STLExtras.h"
36 #include "llvm/ADT/StringRef.h"
37 #include "llvm/Support/Casting.h"
38 #include "llvm/Support/Error.h"
51 const FunctionDecl *getSelectedFunction(
const SelectionTree::Node *SelNode) {
54 const ast_type_traits::DynTypedNode &AstNode = SelNode->ASTNode;
55 if (
const FunctionDecl *FD = AstNode.get<FunctionDecl>())
57 if (AstNode.get<CompoundStmt>() &&
59 if (
const SelectionTree::Node *P = SelNode->Parent)
60 return P->ASTNode.get<FunctionDecl>();
65 llvm::Optional<Path> getSourceFile(llvm::StringRef
FileName,
66 const Tweak::Selection &Sel) {
69 &Sel.AST->getSourceManager().getFileManager().getVirtualFileSystem()))
78 llvm::Optional<const DeclContext *>
79 findContextForNS(llvm::StringRef TargetNS,
const DeclContext *CurContext) {
80 assert(TargetNS.empty() || TargetNS.endswith(
"::"));
82 CurContext = CurContext->getEnclosingNamespaceContext();
84 if (TargetNS.empty()) {
85 while (!CurContext->isTranslationUnit())
86 CurContext = CurContext->getParent();
91 std::string TargetContextNS =
92 CurContext->isNamespace()
93 ? llvm::cast<NamespaceDecl>(CurContext)->getQualifiedNameAsString()
95 TargetContextNS.append(
"::");
97 llvm::StringRef CurrentContextNS(TargetContextNS);
100 if (!CurrentContextNS.startswith(TargetNS))
103 while (CurrentContextNS != TargetNS) {
104 CurContext = CurContext->getParent();
107 CurrentContextNS = CurrentContextNS.take_front(
108 CurrentContextNS.drop_back(2).rfind(
"::") + 2);
116 llvm::Expected<std::string>
117 getFunctionSourceAfterReplacements(
const FunctionDecl *FD,
118 const tooling::Replacements &Replacements) {
119 const auto &SM = FD->getASTContext().getSourceManager();
121 SM, FD->getASTContext().getLangOpts(), FD->getSourceRange());
123 return llvm::createStringError(llvm::inconvertibleErrorCode(),
124 "Couldn't get range for function.");
126 if (
auto *FTD = FD->getDescribedFunctionTemplate())
127 OrigFuncRange->setBegin(FTD->getBeginLoc());
130 unsigned FuncBegin = SM.getFileOffset(OrigFuncRange->getBegin());
131 unsigned FuncEnd = Replacements.getShiftedCodePosition(
132 SM.getFileOffset(OrigFuncRange->getEnd()));
135 auto QualifiedFunc = tooling::applyAllReplacements(
136 SM.getBufferData(SM.getMainFileID()), Replacements);
138 return QualifiedFunc.takeError();
139 return QualifiedFunc->substr(FuncBegin, FuncEnd - FuncBegin + 1);
148 llvm::Expected<std::string>
149 getFunctionSourceCode(
const FunctionDecl *FD, llvm::StringRef TargetNamespace,
150 const syntax::TokenBuffer &TokBuf) {
153 auto TargetContext = findContextForNS(TargetNamespace, FD->getDeclContext());
155 return llvm::createStringError(
156 llvm::inconvertibleErrorCode(),
157 "define outline: couldn't find a context for target");
160 tooling::Replacements DeclarationCleanups;
168 if (Ref.Qualifier || Ref.Targets.empty() || Ref.NameLoc.isMacroID())
171 if (Ref.NameLoc != FD->getReturnTypeSourceRange().getBegin() &&
172 Ref.NameLoc != FD->getLocation())
175 for (
const NamedDecl *ND : Ref.Targets) {
176 if (ND->getDeclContext() != Ref.Targets.front()->getDeclContext()) {
177 elog(
"Targets from multiple contexts: {0}, {1}",
182 const NamedDecl *ND = Ref.Targets.front();
184 AST, *TargetContext, SM.getLocForStartOfFile(SM.getMainFileID()), ND);
185 if (
auto Err = DeclarationCleanups.add(
186 tooling::Replacement(SM, Ref.NameLoc, 0, Qualifier)))
187 Errors = llvm::joinErrors(std::move(Errors), std::move(Err));
192 for (
const auto *PVD : FD->parameters()) {
193 if (PVD->hasDefaultArg()) {
195 auto DelRange = CharSourceRange::getTokenRange(PVD->getDefaultArgRange());
197 auto Tokens = TokBuf.expandedTokens(PVD->getSourceRange())
198 .take_while([&SM, &DelRange](
const syntax::Token &Tok) {
199 return SM.isBeforeInTranslationUnit(
200 Tok.location(), DelRange.getBegin());
204 llvm::find_if(llvm::reverse(Tokens), [](
const syntax::Token &Tok) {
205 return Tok.kind() == tok::equal;
207 assert(Tok != Tokens.rend());
208 DelRange.setBegin(Tok->location());
210 DeclarationCleanups.add(tooling::Replacement(SM, DelRange,
"")))
211 Errors = llvm::joinErrors(std::move(Errors), std::move(Err));
215 auto DelAttr = [&](
const Attr *A) {
219 TokBuf.spelledForExpanded(TokBuf.expandedTokens(A->getRange()));
220 assert(A->getLocation().isValid());
221 if (!AttrTokens || AttrTokens->empty()) {
222 Errors = llvm::joinErrors(
224 llvm::createStringError(
225 llvm::inconvertibleErrorCode(),
226 llvm::StringRef(
"define outline: Can't move out of line as "
227 "function has a macro `") +
228 A->getSpelling() +
"` specifier."));
231 CharSourceRange DelRange =
232 syntax::Token::range(SM, AttrTokens->front(), AttrTokens->back())
235 DeclarationCleanups.add(tooling::Replacement(SM, DelRange,
"")))
236 Errors = llvm::joinErrors(std::move(Errors), std::move(Err));
239 DelAttr(FD->getAttr<OverrideAttr>());
240 DelAttr(FD->getAttr<FinalAttr>());
242 auto DelKeyword = [&](tok::TokenKind
Kind, SourceRange FromRange) {
243 bool FoundAny =
false;
244 for (
const auto &Tok : TokBuf.expandedTokens(FromRange)) {
245 if (Tok.kind() !=
Kind)
248 auto Spelling = TokBuf.spelledForExpanded(llvm::makeArrayRef(Tok));
250 Errors = llvm::joinErrors(
252 llvm::createStringError(
253 llvm::inconvertibleErrorCode(),
254 llvm::formatv(
"define outline: couldn't remove `{0}` keyword.",
255 tok::getKeywordSpelling(
Kind))));
258 CharSourceRange DelRange =
259 syntax::Token::range(SM, Spelling->front(), Spelling->back())
262 DeclarationCleanups.add(tooling::Replacement(SM, DelRange,
"")))
263 Errors = llvm::joinErrors(std::move(Errors), std::move(Err));
266 Errors = llvm::joinErrors(
268 llvm::createStringError(
269 llvm::inconvertibleErrorCode(),
271 "define outline: couldn't find `{0}` keyword to remove.",
272 tok::getKeywordSpelling(
Kind))));
276 if (
const auto *
MD = dyn_cast<CXXMethodDecl>(FD)) {
277 if (
MD->isVirtualAsWritten())
278 DelKeyword(tok::kw_virtual, {FD->getBeginLoc(), FD->getLocation()});
280 DelKeyword(tok::kw_static, {FD->getBeginLoc(), FD->getLocation()});
284 return std::move(Errors);
285 return getFunctionSourceAfterReplacements(FD, DeclarationCleanups);
297 llvm::Expected<InsertionPoint> getInsertionPoint(llvm::StringRef
Contents,
298 llvm::StringRef QualifiedName,
299 const LangOptions &LangOpts) {
302 assert(!Region.EligiblePoints.empty());
309 return Offset.takeError();
316 SourceRange getDeletionRange(
const FunctionDecl *FD,
317 const syntax::TokenBuffer &TokBuf) {
318 auto DeletionRange = FD->getBody()->getSourceRange();
319 if (
auto *CD = llvm::dyn_cast<CXXConstructorDecl>(FD)) {
322 SourceLocation InitStart;
324 for (
const auto *CInit : CD->inits()) {
326 if (CInit->getSourceOrder() != 0)
328 InitStart = CInit->getSourceLocation();
331 if (InitStart.isValid()) {
332 auto Toks = TokBuf.expandedTokens(CD->getSourceRange());
334 Toks = Toks.take_while([&TokBuf, &InitStart](
const syntax::Token &Tok) {
335 return TokBuf.sourceManager().isBeforeInTranslationUnit(Tok.location(),
340 llvm::find_if(llvm::reverse(Toks), [](
const syntax::Token &Tok) {
341 return Tok.kind() == tok::colon;
343 assert(Tok != Toks.rend());
344 DeletionRange.setBegin(Tok->location());
347 return DeletionRange;
366 class DefineOutline :
public Tweak {
368 const char *id()
const override;
370 bool hidden()
const override {
return false; }
371 Intent intent()
const override {
return Intent::Refactor; }
372 std::string title()
const override {
373 return "Move function body to out-of-line.";
376 bool prepare(
const Selection &Sel)
override {
380 if (!
isHeaderFile(Sel.AST->getSourceManager().getFilename(Sel.Cursor),
381 Sel.AST->getLangOpts()))
384 Source = getSelectedFunction(Sel.ASTSelection.commonAncestor());
386 if (!Source || !Source->doesThisDeclarationHaveABody() ||
387 Source->isOutOfLine())
392 if (
auto *
MD = llvm::dyn_cast<CXXMethodDecl>(Source)) {
393 if (
MD->getParent()->isTemplated())
403 Expected<Effect> apply(
const Selection &Sel)
override {
404 const SourceManager &SM = Sel.AST->getSourceManager();
408 return llvm::createStringError(
409 llvm::inconvertibleErrorCode(),
410 "Couldn't get absolute path for mainfile.");
412 auto CCFile = getSourceFile(*MainFileName, Sel);
414 return llvm::createStringError(
415 llvm::inconvertibleErrorCode(),
416 "Couldn't find a suitable implementation file.");
419 Sel.AST->getSourceManager().getFileManager().getVirtualFileSystem();
420 auto Buffer =
FS.getBufferForFile(*CCFile);
424 return llvm::createStringError(Buffer.getError(),
425 Buffer.getError().message());
426 auto Contents = Buffer->get()->getBuffer();
428 Contents, Source->getQualifiedNameAsString(), Sel.AST->getLangOpts());
432 auto FuncDef = getFunctionSourceCode(
433 Source,
InsertionPoint->EnclosingNamespace, Sel.AST->getTokens());
435 return FuncDef.takeError();
437 SourceManagerForFile SMFF(*CCFile,
Contents);
438 const tooling::Replacement InsertFunctionDef(
440 auto Effect = Effect::mainFileEdit(
441 SMFF.get(), tooling::Replacements(InsertFunctionDef));
443 return Effect.takeError();
446 const tooling::Replacement DeleteFuncBody(
447 Sel.AST->getSourceManager(),
449 SM, Sel.AST->getLangOpts(),
450 getDeletionRange(Source, Sel.AST->getTokens()))),
452 auto HeaderFE = Effect::fileEdit(SM, SM.getMainFileID(),
453 tooling::Replacements(DeleteFuncBody));
455 return HeaderFE.takeError();
457 Effect->ApplyEdits.try_emplace(HeaderFE->first,
458 std::move(HeaderFE->second));
459 return std::move(*Effect);
463 const FunctionDecl *Source =
nullptr;