diff --git a/clang-tools-extra/clangd/refactor/tweaks/DefineOutline.cpp b/clang-tools-extra/clangd/refactor/tweaks/DefineOutline.cpp index 4935aa00515322..353da26a0f91db 100644 --- a/clang-tools-extra/clangd/refactor/tweaks/DefineOutline.cpp +++ b/clang-tools-extra/clangd/refactor/tweaks/DefineOutline.cpp @@ -18,6 +18,7 @@ #include "clang/AST/ASTTypeTraits.h" #include "clang/AST/Decl.h" #include "clang/AST/DeclBase.h" +#include "clang/AST/DeclCXX.h" #include "clang/AST/DeclTemplate.h" #include "clang/AST/Stmt.h" #include "clang/Basic/SourceLocation.h" @@ -141,6 +142,7 @@ getFunctionSourceAfterReplacements(const FunctionDecl *FD, // Contains function signature, except defaulted parameter arguments, body and // template parameters if applicable. No need to qualify parameters, as they are // looked up in the context containing the function/method. +// FIXME: Drop attributes in function signature. llvm::Expected getFunctionSourceCode(const FunctionDecl *FD, llvm::StringRef TargetNamespace, const syntax::TokenBuffer &TokBuf) { @@ -238,6 +240,45 @@ getInsertionPoint(llvm::StringRef Contents, llvm::StringRef QualifiedName, return InsertionPoint{Region.EnclosingNamespace, *Offset}; } +// Returns the range that should be deleted from declaration, which always +// contains function body. In addition to that it might contain constructor +// initializers. +SourceRange getDeletionRange(const FunctionDecl *FD, + const syntax::TokenBuffer &TokBuf) { + auto DeletionRange = FD->getBody()->getSourceRange(); + if (auto *CD = llvm::dyn_cast(FD)) { + const auto &SM = TokBuf.sourceManager(); + // AST doesn't contain the location for ":" in ctor initializers. Therefore + // we find it by finding the first ":" before the first ctor initializer. + SourceLocation InitStart; + // Find the first initializer. + for (const auto *CInit : CD->inits()) { + // We don't care about in-class initializers. + if (CInit->isInClassMemberInitializer()) + continue; + if (InitStart.isInvalid() || + SM.isBeforeInTranslationUnit(CInit->getSourceLocation(), InitStart)) + InitStart = CInit->getSourceLocation(); + } + if (InitStart.isValid()) { + auto Toks = TokBuf.expandedTokens(CD->getSourceRange()); + // Drop any tokens after the initializer. + Toks = Toks.take_while([&TokBuf, &InitStart](const syntax::Token &Tok) { + return TokBuf.sourceManager().isBeforeInTranslationUnit(Tok.location(), + InitStart); + }); + // Look for the first colon. + auto Tok = + llvm::find_if(llvm::reverse(Toks), [](const syntax::Token &Tok) { + return Tok.kind() == tok::colon; + }); + assert(Tok != Toks.rend()); + DeletionRange.setBegin(Tok->location()); + } + } + return DeletionRange; +} + /// Moves definition of a function/method to an appropriate implementation file. /// /// Before: @@ -338,7 +379,8 @@ class DefineOutline : public Tweak { const tooling::Replacement DeleteFuncBody( Sel.AST.getSourceManager(), CharSourceRange::getTokenRange(*toHalfOpenFileRange( - SM, Sel.AST.getLangOpts(), Source->getBody()->getSourceRange())), + SM, Sel.AST.getLangOpts(), + getDeletionRange(Source, Sel.AST.getTokens()))), ";"); auto HeaderFE = Effect::fileEdit(SM, SM.getMainFileID(), tooling::Replacements(DeleteFuncBody)); diff --git a/clang-tools-extra/clangd/unittests/TweakTests.cpp b/clang-tools-extra/clangd/unittests/TweakTests.cpp index 0bc11a898d7ed0..d50d27afb1c8e1 100644 --- a/clang-tools-extra/clangd/unittests/TweakTests.cpp +++ b/clang-tools-extra/clangd/unittests/TweakTests.cpp @@ -1979,6 +1979,24 @@ TEST_F(DefineOutlineTest, ApplyTest) { "void foo(int x, int y = 5, int = 2, int (*foo)(int) = nullptr) ;", "void foo(int x, int y , int , int (*foo)(int) ) {}", }, + // Ctor initializers. + { + R"cpp( + class Foo { + int y = 2; + F^oo(int z) __attribute__((weak)) : bar(2){} + int bar; + int z = 2; + };)cpp", + R"cpp( + class Foo { + int y = 2; + Foo(int z) __attribute__((weak)) ; + int bar; + int z = 2; + };)cpp", + "Foo::Foo(int z) __attribute__((weak)) : bar(2){}\n", + }, }; for (const auto &Case : Cases) { SCOPED_TRACE(Case.Test);