18 #include "clang/AST/ASTTypeTraits.h" 19 #include "clang/AST/Decl.h" 20 #include "clang/AST/DeclBase.h" 21 #include "clang/AST/DeclCXX.h" 22 #include "clang/AST/DeclTemplate.h" 23 #include "clang/AST/Stmt.h" 24 #include "clang/Basic/SourceLocation.h" 25 #include "clang/Basic/SourceManager.h" 26 #include "clang/Basic/TokenKinds.h" 27 #include "clang/Driver/Types.h" 28 #include "clang/Format/Format.h" 29 #include "clang/Lex/Lexer.h" 30 #include "clang/Tooling/Core/Replacement.h" 31 #include "clang/Tooling/Syntax/Tokens.h" 32 #include "llvm/ADT/None.h" 33 #include "llvm/ADT/Optional.h" 34 #include "llvm/ADT/STLExtras.h" 35 #include "llvm/ADT/StringRef.h" 36 #include "llvm/Support/Casting.h" 37 #include "llvm/Support/Error.h" 50 const FunctionDecl *getSelectedFunction(
const SelectionTree::Node *SelNode) {
53 const ast_type_traits::DynTypedNode &AstNode = SelNode->ASTNode;
54 if (
const FunctionDecl *FD = AstNode.get<FunctionDecl>())
56 if (AstNode.get<CompoundStmt>() &&
58 if (
const SelectionTree::Node *P = SelNode->Parent)
59 return P->ASTNode.get<FunctionDecl>();
64 llvm::Optional<Path> getSourceFile(llvm::StringRef
FileName,
65 const Tweak::Selection &Sel) {
68 &Sel.AST->getSourceManager().getFileManager().getVirtualFileSystem()))
76 llvm::Optional<const DeclContext *>
77 findContextForNS(llvm::StringRef TargetNS,
const DeclContext *CurContext) {
78 assert(TargetNS.empty() || TargetNS.endswith(
"::"));
80 CurContext = CurContext->getEnclosingNamespaceContext();
82 if (TargetNS.empty()) {
83 while (!CurContext->isTranslationUnit())
84 CurContext = CurContext->getParent();
89 std::string TargetContextNS =
90 CurContext->isNamespace()
91 ? llvm::cast<NamespaceDecl>(CurContext)->getQualifiedNameAsString()
93 TargetContextNS.append(
"::");
95 llvm::StringRef CurrentContextNS(TargetContextNS);
98 if (!CurrentContextNS.startswith(TargetNS))
101 while (CurrentContextNS != TargetNS) {
102 CurContext = CurContext->getParent();
105 CurrentContextNS = CurrentContextNS.take_front(
106 CurrentContextNS.drop_back(2).rfind(
"::") + 2);
114 llvm::Expected<std::string>
115 getFunctionSourceAfterReplacements(
const FunctionDecl *FD,
116 const tooling::Replacements &Replacements) {
117 const auto &SM = FD->getASTContext().getSourceManager();
119 SM, FD->getASTContext().getLangOpts(), FD->getSourceRange());
121 return llvm::createStringError(llvm::inconvertibleErrorCode(),
122 "Couldn't get range for function.");
124 if (
auto *FTD = FD->getDescribedFunctionTemplate())
125 OrigFuncRange->setBegin(FTD->getBeginLoc());
128 unsigned FuncBegin = SM.getFileOffset(OrigFuncRange->getBegin());
129 unsigned FuncEnd = Replacements.getShiftedCodePosition(
130 SM.getFileOffset(OrigFuncRange->getEnd()));
133 auto QualifiedFunc = tooling::applyAllReplacements(
134 SM.getBufferData(SM.getMainFileID()), Replacements);
136 return QualifiedFunc.takeError();
137 return QualifiedFunc->substr(FuncBegin, FuncEnd - FuncBegin + 1);
146 llvm::Expected<std::string>
147 getFunctionSourceCode(
const FunctionDecl *FD, llvm::StringRef TargetNamespace,
148 const syntax::TokenBuffer &TokBuf) {
151 auto TargetContext = findContextForNS(TargetNamespace, FD->getDeclContext());
153 return llvm::createStringError(
154 llvm::inconvertibleErrorCode(),
155 "define outline: couldn't find a context for target");
158 tooling::Replacements QualifierInsertions;
166 if (Ref.Qualifier || Ref.Targets.empty() || Ref.NameLoc.isMacroID())
169 if (Ref.NameLoc != FD->getReturnTypeSourceRange().getBegin() &&
170 Ref.NameLoc != FD->getLocation())
173 for (
const NamedDecl *ND : Ref.Targets) {
174 if (ND->getDeclContext() != Ref.Targets.front()->getDeclContext()) {
175 elog(
"Targets from multiple contexts: {0}, {1}",
180 const NamedDecl *ND = Ref.Targets.front();
182 AST, *TargetContext, SM.getLocForStartOfFile(SM.getMainFileID()), ND);
183 if (
auto Err = QualifierInsertions.add(
184 tooling::Replacement(SM, Ref.NameLoc, 0, Qualifier)))
185 Errors = llvm::joinErrors(std::move(Errors), std::move(Err));
190 for (
const auto *PVD : FD->parameters()) {
191 if (PVD->hasDefaultArg()) {
195 auto Tokens = TokBuf.expandedTokens(PVD->getSourceRange())
196 .take_while([&SM, &DelRange](
const syntax::Token &Tok) {
197 return SM.isBeforeInTranslationUnit(
198 Tok.location(), DelRange.getBegin());
202 llvm::find_if(llvm::reverse(Tokens), [](
const syntax::Token &Tok) {
203 return Tok.kind() == tok::equal;
205 assert(Tok != Tokens.rend());
206 DelRange.setBegin(Tok->location());
208 QualifierInsertions.add(tooling::Replacement(SM, DelRange,
"")))
209 Errors = llvm::joinErrors(std::move(Errors), std::move(Err));
214 return std::move(Errors);
215 return getFunctionSourceAfterReplacements(FD, QualifierInsertions);
227 llvm::Expected<InsertionPoint>
228 getInsertionPoint(llvm::StringRef
Contents, llvm::StringRef QualifiedName,
232 assert(!Region.EligiblePoints.empty());
239 return Offset.takeError();
246 SourceRange getDeletionRange(
const FunctionDecl *FD,
247 const syntax::TokenBuffer &TokBuf) {
248 auto DeletionRange = FD->getBody()->getSourceRange();
249 if (
auto *CD = llvm::dyn_cast<CXXConstructorDecl>(FD)) {
250 const auto &SM = TokBuf.sourceManager();
253 SourceLocation InitStart;
255 for (
const auto *CInit : CD->inits()) {
257 if (CInit->isInClassMemberInitializer())
259 if (InitStart.isInvalid() ||
260 SM.isBeforeInTranslationUnit(CInit->getSourceLocation(), InitStart))
261 InitStart = CInit->getSourceLocation();
263 if (InitStart.isValid()) {
264 auto Toks = TokBuf.expandedTokens(CD->getSourceRange());
266 Toks = Toks.take_while([&TokBuf, &InitStart](
const syntax::Token &Tok) {
267 return TokBuf.sourceManager().isBeforeInTranslationUnit(Tok.location(),
272 llvm::find_if(llvm::reverse(Toks), [](
const syntax::Token &Tok) {
273 return Tok.kind() == tok::colon;
275 assert(Tok != Toks.rend());
276 DeletionRange.setBegin(Tok->location());
279 return DeletionRange;
298 class DefineOutline :
public Tweak {
300 const char *id()
const override;
302 bool hidden()
const override {
return true; }
303 Intent intent()
const override {
return Intent::Refactor; }
304 std::string title()
const override {
305 return "Move function body to out-of-line.";
308 bool prepare(
const Selection &Sel)
override {
312 if (!
isHeaderFile(Sel.AST->getSourceManager().getFilename(Sel.Cursor),
313 Sel.AST->getLangOpts()))
316 Source = getSelectedFunction(Sel.ASTSelection.commonAncestor());
318 if (!Source || !Source->doesThisDeclarationHaveABody() ||
319 Source->isOutOfLine())
324 if (
auto *
MD = llvm::dyn_cast<CXXMethodDecl>(Source)) {
325 if (
MD->getParent()->isTemplated())
335 Expected<Effect> apply(
const Selection &Sel)
override {
336 const SourceManager &SM = Sel.AST->getSourceManager();
340 return llvm::createStringError(
341 llvm::inconvertibleErrorCode(),
342 "Couldn't get absolute path for mainfile.");
344 auto CCFile = getSourceFile(*MainFileName, Sel);
346 return llvm::createStringError(
347 llvm::inconvertibleErrorCode(),
348 "Couldn't find a suitable implementation file.");
351 Sel.AST->getSourceManager().getFileManager().getVirtualFileSystem();
352 auto Buffer =
FS.getBufferForFile(*CCFile);
356 return llvm::createStringError(Buffer.getError(),
357 Buffer.getError().message());
358 auto Contents = Buffer->get()->getBuffer();
360 getInsertionPoint(Contents, Source->getQualifiedNameAsString(),
365 auto FuncDef = getFunctionSourceCode(
366 Source,
InsertionPoint->EnclosingNamespace, Sel.AST->getTokens());
368 return FuncDef.takeError();
370 SourceManagerForFile SMFF(*CCFile, Contents);
371 const tooling::Replacement InsertFunctionDef(
373 auto Effect = Effect::mainFileEdit(
374 SMFF.get(), tooling::Replacements(InsertFunctionDef));
376 return Effect.takeError();
379 const tooling::Replacement DeleteFuncBody(
380 Sel.AST->getSourceManager(),
382 SM, Sel.AST->getLangOpts(),
383 getDeletionRange(Source, Sel.AST->getTokens()))),
385 auto HeaderFE = Effect::fileEdit(SM, SM.getMainFileID(),
386 tooling::Replacements(DeleteFuncBody));
388 return HeaderFE.takeError();
390 Effect->ApplyEdits.try_emplace(HeaderFE->first,
391 std::move(HeaderFE->second));
392 return std::move(*Effect);
396 const FunctionDecl *Source =
nullptr;
std::string printQualifiedName(const NamedDecl &ND)
Returns the qualified name of ND.
#define REGISTER_TWEAK(Subclass)
Documents should not be synced at all.
void elog(const char *Fmt, Ts &&... Vals)
ASTContext & getASTContext()
Note that the returned ast will not contain decls from the preamble that were not deserialized during...
std::string EnclosingNamespace
llvm::Expected< size_t > positionToOffset(llvm::StringRef Code, Position P, bool AllowColumnsBeyondLineLength)
Turn a [line, column] pair into an offset in Code.
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.
EligibleRegion getEligiblePoints(llvm::StringRef Code, llvm::StringRef FullyQualifiedName, const format::FormatStyle &Style)
Returns most eligible region to insert a definition for FullyQualifiedName in the Code...
llvm::Optional< Range > getTokenRange(const SourceManager &SM, const LangOptions &LangOpts, SourceLocation TokLoc)
Returns the taken range at TokLoc.
format::FormatStyle getFormatStyleForFile(llvm::StringRef File, llvm::StringRef Content, llvm::vfs::FileSystem *FS)
Choose the clang-format style we should apply to a certain file.
llvm::Optional< SourceRange > toHalfOpenFileRange(const SourceManager &SM, const LangOptions &LangOpts, SourceRange R)
Turns a token range into a half-open range and checks its correctness.
SourceManager & getSourceManager()
llvm::Optional< Path > getCorrespondingHeaderOrSource(const Path &OriginalFile, llvm::IntrusiveRefCntPtr< llvm::vfs::FileSystem > VFS)
Given a header file, returns the best matching source file, and vice visa.
===– Representation.cpp - ClangDoc Representation --------—*- C++ -*-===//
void findExplicitReferences(const Stmt *S, llvm::function_ref< void(ReferenceLoc)> Out)
Recursively traverse S and report all references explicitly written in the code.
static GeneratorRegistry::Add< MDGenerator > MD(MDGenerator::Format, "Generator for MD output.")
llvm::Optional< std::string > getCanonicalPath(const FileEntry *F, const SourceManager &SourceMgr)
Get the canonical path of F.
bool isHeaderFile(llvm::StringRef FileName, llvm::Optional< LangOptions > LangOpts)
Infers whether this is a header from the FileName and LangOpts (if presents).
static cl::opt< std::string > FormatStyle("format-style", cl::desc(R"(
Style for formatting code around applied fixes:
- 'none' (default) turns off formatting
- 'file' (literally 'file', not a placeholder)
uses .clang-format file in the closest parent
directory
- '{ <json> }' specifies options inline, e.g.
-format-style='{BasedOnStyle: llvm, IndentWidth: 8}'
- 'llvm', 'google', 'webkit', 'mozilla'
See clang-format documentation for the up-to-date
information about formatting styles and options.
This option overrides the 'FormatStyle` option in
.clang-tidy file, if any.
)"), cl::init("none"), cl::cat(ClangTidyCategory))