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()))
77 llvm::Optional<const DeclContext *>
78 findContextForNS(llvm::StringRef TargetNS,
const DeclContext *CurContext) {
79 assert(TargetNS.empty() || TargetNS.endswith(
"::"));
81 CurContext = CurContext->getEnclosingNamespaceContext();
83 if (TargetNS.empty()) {
84 while (!CurContext->isTranslationUnit())
85 CurContext = CurContext->getParent();
90 std::string TargetContextNS =
91 CurContext->isNamespace()
92 ? llvm::cast<NamespaceDecl>(CurContext)->getQualifiedNameAsString()
94 TargetContextNS.append(
"::");
96 llvm::StringRef CurrentContextNS(TargetContextNS);
99 if (!CurrentContextNS.startswith(TargetNS))
102 while (CurrentContextNS != TargetNS) {
103 CurContext = CurContext->getParent();
106 CurrentContextNS = CurrentContextNS.take_front(
107 CurrentContextNS.drop_back(2).rfind(
"::") + 2);
115 llvm::Expected<std::string>
116 getFunctionSourceAfterReplacements(
const FunctionDecl *FD,
117 const tooling::Replacements &Replacements) {
118 const auto &SM = FD->getASTContext().getSourceManager();
120 SM, FD->getASTContext().getLangOpts(), FD->getSourceRange());
122 return llvm::createStringError(llvm::inconvertibleErrorCode(),
123 "Couldn't get range for function.");
125 if (
auto *FTD = FD->getDescribedFunctionTemplate())
126 OrigFuncRange->setBegin(FTD->getBeginLoc());
129 unsigned FuncBegin = SM.getFileOffset(OrigFuncRange->getBegin());
130 unsigned FuncEnd = Replacements.getShiftedCodePosition(
131 SM.getFileOffset(OrigFuncRange->getEnd()));
134 auto QualifiedFunc = tooling::applyAllReplacements(
135 SM.getBufferData(SM.getMainFileID()), Replacements);
137 return QualifiedFunc.takeError();
138 return QualifiedFunc->substr(FuncBegin, FuncEnd - FuncBegin + 1);
147 llvm::Expected<std::string>
148 getFunctionSourceCode(
const FunctionDecl *FD, llvm::StringRef TargetNamespace,
149 const syntax::TokenBuffer &TokBuf) {
152 auto TargetContext = findContextForNS(TargetNamespace, FD->getDeclContext());
154 return llvm::createStringError(
155 llvm::inconvertibleErrorCode(),
156 "define outline: couldn't find a context for target");
159 tooling::Replacements DeclarationCleanups;
167 if (Ref.Qualifier || Ref.Targets.empty() || Ref.NameLoc.isMacroID())
170 if (Ref.NameLoc != FD->getReturnTypeSourceRange().getBegin() &&
171 Ref.NameLoc != FD->getLocation())
174 for (
const NamedDecl *ND : Ref.Targets) {
175 if (ND->getDeclContext() != Ref.Targets.front()->getDeclContext()) {
176 elog(
"Targets from multiple contexts: {0}, {1}",
181 const NamedDecl *ND = Ref.Targets.front();
183 AST, *TargetContext, SM.getLocForStartOfFile(SM.getMainFileID()), ND);
184 if (
auto Err = DeclarationCleanups.add(
185 tooling::Replacement(SM, Ref.NameLoc, 0, Qualifier)))
186 Errors = llvm::joinErrors(std::move(Errors), std::move(Err));
191 for (
const auto *PVD : FD->parameters()) {
192 if (PVD->hasDefaultArg()) {
196 auto Tokens = TokBuf.expandedTokens(PVD->getSourceRange())
197 .take_while([&SM, &DelRange](
const syntax::Token &Tok) {
198 return SM.isBeforeInTranslationUnit(
199 Tok.location(), DelRange.getBegin());
203 llvm::find_if(llvm::reverse(Tokens), [](
const syntax::Token &Tok) {
204 return Tok.kind() == tok::equal;
206 assert(Tok != Tokens.rend());
207 DelRange.setBegin(Tok->location());
209 DeclarationCleanups.add(tooling::Replacement(SM, DelRange,
"")))
210 Errors = llvm::joinErrors(std::move(Errors), std::move(Err));
214 auto DelAttr = [&](
const Attr *A) {
218 TokBuf.spelledForExpanded(TokBuf.expandedTokens(A->getRange()));
219 assert(A->getLocation().isValid());
220 if (!AttrTokens || AttrTokens->empty()) {
221 Errors = llvm::joinErrors(
223 llvm::createStringError(
224 llvm::inconvertibleErrorCode(),
225 llvm::StringRef(
"define outline: Can't move out of line as " 226 "function has a macro `") +
227 A->getSpelling() +
"` specifier."));
230 CharSourceRange DelRange =
231 syntax::Token::range(SM, AttrTokens->front(), AttrTokens->back())
234 DeclarationCleanups.add(tooling::Replacement(SM, DelRange,
"")))
235 Errors = llvm::joinErrors(std::move(Errors), std::move(Err));
238 DelAttr(FD->getAttr<OverrideAttr>());
239 DelAttr(FD->getAttr<FinalAttr>());
241 if (FD->isVirtualAsWritten()) {
242 SourceRange SpecRange{FD->getBeginLoc(), FD->getLocation()};
243 bool HasErrors =
true;
247 for (
const auto &Tok : TokBuf.expandedTokens(SpecRange)) {
248 if (Tok.kind() != tok::kw_virtual)
250 auto Spelling = TokBuf.spelledForExpanded(llvm::makeArrayRef(Tok));
256 CharSourceRange DelRange =
257 syntax::Token::range(SM, Spelling->front(), Spelling->back())
260 DeclarationCleanups.add(tooling::Replacement(SM, DelRange,
"")))
261 Errors = llvm::joinErrors(std::move(Errors), std::move(Err));
264 Errors = llvm::joinErrors(
266 llvm::createStringError(llvm::inconvertibleErrorCode(),
267 "define outline: Can't move out of line as " 268 "function has a macro `virtual` specifier."));
273 return std::move(Errors);
274 return getFunctionSourceAfterReplacements(FD, DeclarationCleanups);
286 llvm::Expected<InsertionPoint>
287 getInsertionPoint(llvm::StringRef
Contents, llvm::StringRef QualifiedName,
291 assert(!Region.EligiblePoints.empty());
298 return Offset.takeError();
305 SourceRange getDeletionRange(
const FunctionDecl *FD,
306 const syntax::TokenBuffer &TokBuf) {
307 auto DeletionRange = FD->getBody()->getSourceRange();
308 if (
auto *CD = llvm::dyn_cast<CXXConstructorDecl>(FD)) {
309 const auto &SM = TokBuf.sourceManager();
312 SourceLocation InitStart;
314 for (
const auto *CInit : CD->inits()) {
316 if (CInit->isInClassMemberInitializer())
318 if (InitStart.isInvalid() ||
319 SM.isBeforeInTranslationUnit(CInit->getSourceLocation(), InitStart))
320 InitStart = CInit->getSourceLocation();
322 if (InitStart.isValid()) {
323 auto Toks = TokBuf.expandedTokens(CD->getSourceRange());
325 Toks = Toks.take_while([&TokBuf, &InitStart](
const syntax::Token &Tok) {
326 return TokBuf.sourceManager().isBeforeInTranslationUnit(Tok.location(),
331 llvm::find_if(llvm::reverse(Toks), [](
const syntax::Token &Tok) {
332 return Tok.kind() == tok::colon;
334 assert(Tok != Toks.rend());
335 DeletionRange.setBegin(Tok->location());
338 return DeletionRange;
357 class DefineOutline :
public Tweak {
359 const char *id()
const override;
361 bool hidden()
const override {
return true; }
362 Intent intent()
const override {
return Intent::Refactor; }
363 std::string title()
const override {
364 return "Move function body to out-of-line.";
367 bool prepare(
const Selection &Sel)
override {
371 if (!
isHeaderFile(Sel.AST->getSourceManager().getFilename(Sel.Cursor),
372 Sel.AST->getLangOpts()))
375 Source = getSelectedFunction(Sel.ASTSelection.commonAncestor());
377 if (!Source || !Source->doesThisDeclarationHaveABody() ||
378 Source->isOutOfLine())
383 if (
auto *
MD = llvm::dyn_cast<CXXMethodDecl>(Source)) {
384 if (
MD->getParent()->isTemplated())
394 Expected<Effect> apply(
const Selection &Sel)
override {
395 const SourceManager &SM = Sel.AST->getSourceManager();
399 return llvm::createStringError(
400 llvm::inconvertibleErrorCode(),
401 "Couldn't get absolute path for mainfile.");
403 auto CCFile = getSourceFile(*MainFileName, Sel);
405 return llvm::createStringError(
406 llvm::inconvertibleErrorCode(),
407 "Couldn't find a suitable implementation file.");
410 Sel.AST->getSourceManager().getFileManager().getVirtualFileSystem();
411 auto Buffer =
FS.getBufferForFile(*CCFile);
415 return llvm::createStringError(Buffer.getError(),
416 Buffer.getError().message());
417 auto Contents = Buffer->get()->getBuffer();
419 getInsertionPoint(Contents, Source->getQualifiedNameAsString(),
424 auto FuncDef = getFunctionSourceCode(
425 Source,
InsertionPoint->EnclosingNamespace, Sel.AST->getTokens());
427 return FuncDef.takeError();
429 SourceManagerForFile SMFF(*CCFile, Contents);
430 const tooling::Replacement InsertFunctionDef(
432 auto Effect = Effect::mainFileEdit(
433 SMFF.get(), tooling::Replacements(InsertFunctionDef));
435 return Effect.takeError();
438 const tooling::Replacement DeleteFuncBody(
439 Sel.AST->getSourceManager(),
441 SM, Sel.AST->getLangOpts(),
442 getDeletionRange(Source, Sel.AST->getTokens()))),
444 auto HeaderFE = Effect::fileEdit(SM, SM.getMainFileID(),
445 tooling::Replacements(DeleteFuncBody));
447 return HeaderFE.takeError();
449 Effect->ApplyEdits.try_emplace(HeaderFE->first,
450 std::move(HeaderFE->second));
451 return std::move(*Effect);
455 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))