diff --git a/clang-tools-extra/clangd/refactor/tweaks/DefineOutline.cpp b/clang-tools-extra/clangd/refactor/tweaks/DefineOutline.cpp --- a/clang-tools-extra/clangd/refactor/tweaks/DefineOutline.cpp +++ b/clang-tools-extra/clangd/refactor/tweaks/DefineOutline.cpp @@ -18,16 +18,20 @@ #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" #include "clang/Basic/SourceManager.h" +#include "clang/Basic/TokenKinds.h" #include "clang/Driver/Types.h" #include "clang/Format/Format.h" #include "clang/Lex/Lexer.h" #include "clang/Tooling/Core/Replacement.h" +#include "clang/Tooling/Syntax/Tokens.h" #include "llvm/ADT/None.h" #include "llvm/ADT/Optional.h" +#include "llvm/ADT/STLExtras.h" #include "llvm/ADT/StringRef.h" #include "llvm/Support/Casting.h" #include "llvm/Support/Error.h" @@ -228,6 +232,46 @@ 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. + for (auto &Tok : llvm::reverse(Toks)) { + if (Tok.kind() == tok::colon) { + InitStart = Tok.location(); + break; + } + } + DeletionRange.setBegin(InitStart); + } + } + return DeletionRange; +} + /// Moves definition of a function/method to an appropriate implementation file. /// /// Before: @@ -328,7 +372,8 @@ 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 --- a/clang-tools-extra/clangd/unittests/TweakTests.cpp +++ b/clang-tools-extra/clangd/unittests/TweakTests.cpp @@ -1941,6 +1941,24 @@ "void foo(int x, int y = 5, int = 2) ;", "void foo(int x, int y, int ) {}", }, + // Ctor initializers. + { + R"cpp( + class Foo { + int y = 2; + F^oo() : bar(2){} + int bar; + int z = 2; + };)cpp", + R"cpp( + class Foo { + int y = 2; + Foo() ; + int bar; + int z = 2; + };)cpp", + "Foo::Foo() : bar(2){}\n", + }, }; for (const auto &Case : Cases) { SCOPED_TRACE(Case.Test);