xref: /llvm-project/clang-tools-extra/clangd/refactor/InsertionPoint.cpp (revision edd690b02e16e991393bf7f67631196942369aed)
1fe68088dSSam McCall //===--- InsertionPoint.cpp - Where should we add new code? ---------------===//
2fe68088dSSam McCall //
3fe68088dSSam McCall // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4fe68088dSSam McCall // See https://llvm.org/LICENSE.txt for license information.
5fe68088dSSam McCall // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6fe68088dSSam McCall //
7fe68088dSSam McCall //===----------------------------------------------------------------------===//
8fe68088dSSam McCall 
9fe68088dSSam McCall #include "refactor/InsertionPoint.h"
10fe68088dSSam McCall #include "support/Logger.h"
11fe68088dSSam McCall #include "clang/AST/ASTContext.h"
12fe68088dSSam McCall #include "clang/AST/DeclCXX.h"
13fe68088dSSam McCall #include "clang/AST/DeclObjC.h"
14fe68088dSSam McCall #include "clang/AST/DeclTemplate.h"
15fe68088dSSam McCall #include "clang/Basic/SourceManager.h"
1671f55735SKazu Hirata #include <optional>
17fe68088dSSam McCall 
18fe68088dSSam McCall namespace clang {
19fe68088dSSam McCall namespace clangd {
20fe68088dSSam McCall namespace {
21fe68088dSSam McCall 
22fe68088dSSam McCall // Choose the decl to insert before, according to an anchor.
23fe68088dSSam McCall // Nullptr means insert at end of DC.
2438d8b1f2SKazu Hirata // std::nullopt means no valid place to insert.
insertionDecl(const DeclContext & DC,const Anchor & A)25f71ffd3bSKazu Hirata std::optional<const Decl *> insertionDecl(const DeclContext &DC,
26fe68088dSSam McCall                                           const Anchor &A) {
27fe68088dSSam McCall   bool LastMatched = false;
28fe68088dSSam McCall   bool ReturnNext = false;
29fe68088dSSam McCall   for (const auto *D : DC.decls()) {
30fe68088dSSam McCall     if (D->isImplicit())
31fe68088dSSam McCall       continue;
32fe68088dSSam McCall     if (ReturnNext)
33fe68088dSSam McCall       return D;
34fe68088dSSam McCall 
35fe68088dSSam McCall     const Decl *NonTemplate = D;
36fe68088dSSam McCall     if (auto *TD = llvm::dyn_cast<TemplateDecl>(D))
37fe68088dSSam McCall       NonTemplate = TD->getTemplatedDecl();
38fe68088dSSam McCall     bool Matches = A.Match(NonTemplate);
39fe68088dSSam McCall     dlog("    {0} {1} {2}", Matches, D->getDeclKindName(), D);
40fe68088dSSam McCall 
41fe68088dSSam McCall     switch (A.Direction) {
42fe68088dSSam McCall     case Anchor::Above:
43fe68088dSSam McCall       if (Matches && !LastMatched) {
44fe68088dSSam McCall         // Special case: if "above" matches an access specifier, we actually
45fe68088dSSam McCall         // want to insert below it!
46fe68088dSSam McCall         if (llvm::isa<AccessSpecDecl>(D)) {
47fe68088dSSam McCall           ReturnNext = true;
48fe68088dSSam McCall           continue;
49fe68088dSSam McCall         }
50fe68088dSSam McCall         return D;
51fe68088dSSam McCall       }
52fe68088dSSam McCall       break;
53fe68088dSSam McCall     case Anchor::Below:
54fe68088dSSam McCall       if (LastMatched && !Matches)
55fe68088dSSam McCall         return D;
56fe68088dSSam McCall       break;
57fe68088dSSam McCall     }
58fe68088dSSam McCall 
59fe68088dSSam McCall     LastMatched = Matches;
60fe68088dSSam McCall   }
61fe68088dSSam McCall   if (ReturnNext || (LastMatched && A.Direction == Anchor::Below))
62fe68088dSSam McCall     return nullptr;
63059a23c0SKazu Hirata   return std::nullopt;
64fe68088dSSam McCall }
65fe68088dSSam McCall 
beginLoc(const Decl & D)66fe68088dSSam McCall SourceLocation beginLoc(const Decl &D) {
67fe68088dSSam McCall   auto Loc = D.getBeginLoc();
68fe68088dSSam McCall   if (RawComment *Comment = D.getASTContext().getRawCommentForDeclNoCache(&D)) {
69fe68088dSSam McCall     auto CommentLoc = Comment->getBeginLoc();
70fe68088dSSam McCall     if (CommentLoc.isValid() && Loc.isValid() &&
71fe68088dSSam McCall         D.getASTContext().getSourceManager().isBeforeInTranslationUnit(
72fe68088dSSam McCall             CommentLoc, Loc))
73fe68088dSSam McCall       Loc = CommentLoc;
74fe68088dSSam McCall   }
75fe68088dSSam McCall   return Loc;
76fe68088dSSam McCall }
77fe68088dSSam McCall 
any(const Decl * D)78fe68088dSSam McCall bool any(const Decl *D) { return true; }
79fe68088dSSam McCall 
endLoc(const DeclContext & DC)80fe68088dSSam McCall SourceLocation endLoc(const DeclContext &DC) {
81fe68088dSSam McCall   const Decl *D = llvm::cast<Decl>(&DC);
82fe68088dSSam McCall   if (auto *OCD = llvm::dyn_cast<ObjCContainerDecl>(D))
83fe68088dSSam McCall     return OCD->getAtEndRange().getBegin();
84fe68088dSSam McCall   return D->getEndLoc();
85fe68088dSSam McCall }
86fe68088dSSam McCall 
getAccessAtEnd(const CXXRecordDecl & C)87fe68088dSSam McCall AccessSpecifier getAccessAtEnd(const CXXRecordDecl &C) {
88*edd690b0SVlad Serebrennikov   AccessSpecifier Spec =
89*edd690b0SVlad Serebrennikov       (C.getTagKind() == TagTypeKind::Class ? AS_private : AS_public);
90fe68088dSSam McCall   for (const auto *D : C.decls())
91fe68088dSSam McCall     if (const auto *ASD = llvm::dyn_cast<AccessSpecDecl>(D))
92fe68088dSSam McCall       Spec = ASD->getAccess();
93fe68088dSSam McCall   return Spec;
94fe68088dSSam McCall }
95fe68088dSSam McCall 
96fe68088dSSam McCall } // namespace
97fe68088dSSam McCall 
insertionPoint(const DeclContext & DC,llvm::ArrayRef<Anchor> Anchors)98fe68088dSSam McCall SourceLocation insertionPoint(const DeclContext &DC,
99fe68088dSSam McCall                               llvm::ArrayRef<Anchor> Anchors) {
100fe68088dSSam McCall   dlog("Looking for insertion point in {0}", DC.getDeclKindName());
101fe68088dSSam McCall   for (const auto &A : Anchors) {
102fe68088dSSam McCall     dlog("  anchor ({0})", A.Direction == Anchor::Above ? "above" : "below");
103fe68088dSSam McCall     if (auto D = insertionDecl(DC, A)) {
104fe68088dSSam McCall       dlog("  anchor matched before {0}", *D);
105fe68088dSSam McCall       return *D ? beginLoc(**D) : endLoc(DC);
106fe68088dSSam McCall     }
107fe68088dSSam McCall   }
108fe68088dSSam McCall   dlog("no anchor matched");
109fe68088dSSam McCall   return SourceLocation();
110fe68088dSSam McCall }
111fe68088dSSam McCall 
112fe68088dSSam McCall llvm::Expected<tooling::Replacement>
insertDecl(llvm::StringRef Code,const DeclContext & DC,llvm::ArrayRef<Anchor> Anchors)113fe68088dSSam McCall insertDecl(llvm::StringRef Code, const DeclContext &DC,
114fe68088dSSam McCall            llvm::ArrayRef<Anchor> Anchors) {
115fe68088dSSam McCall   auto Loc = insertionPoint(DC, Anchors);
116fe68088dSSam McCall   // Fallback: insert at the end.
117fe68088dSSam McCall   if (Loc.isInvalid())
118fe68088dSSam McCall     Loc = endLoc(DC);
119fe68088dSSam McCall   const auto &SM = DC.getParentASTContext().getSourceManager();
120fe68088dSSam McCall   if (!SM.isWrittenInSameFile(Loc, cast<Decl>(DC).getLocation()))
121fe68088dSSam McCall     return error("{0} body in wrong file: {1}", DC.getDeclKindName(),
122fe68088dSSam McCall                  Loc.printToString(SM));
123fe68088dSSam McCall   return tooling::Replacement(SM, Loc, 0, Code);
124fe68088dSSam McCall }
125fe68088dSSam McCall 
insertionPoint(const CXXRecordDecl & InClass,std::vector<Anchor> Anchors,AccessSpecifier Protection)126fe68088dSSam McCall SourceLocation insertionPoint(const CXXRecordDecl &InClass,
127fe68088dSSam McCall                               std::vector<Anchor> Anchors,
128fe68088dSSam McCall                               AccessSpecifier Protection) {
129fe68088dSSam McCall   for (auto &A : Anchors)
130fe68088dSSam McCall     A.Match = [Inner(std::move(A.Match)), Protection](const Decl *D) {
131fe68088dSSam McCall       return D->getAccess() == Protection && Inner(D);
132fe68088dSSam McCall     };
133fe68088dSSam McCall   return insertionPoint(InClass, Anchors);
134fe68088dSSam McCall }
135fe68088dSSam McCall 
insertDecl(llvm::StringRef Code,const CXXRecordDecl & InClass,std::vector<Anchor> Anchors,AccessSpecifier Protection)136fe68088dSSam McCall llvm::Expected<tooling::Replacement> insertDecl(llvm::StringRef Code,
137fe68088dSSam McCall                                                 const CXXRecordDecl &InClass,
138fe68088dSSam McCall                                                 std::vector<Anchor> Anchors,
139fe68088dSSam McCall                                                 AccessSpecifier Protection) {
140fe68088dSSam McCall   // Fallback: insert at the bottom of the relevant access section.
141fe68088dSSam McCall   Anchors.push_back({any, Anchor::Below});
142fe68088dSSam McCall   auto Loc = insertionPoint(InClass, std::move(Anchors), Protection);
143fe68088dSSam McCall   std::string CodeBuffer;
144fe68088dSSam McCall   auto &SM = InClass.getASTContext().getSourceManager();
145fe68088dSSam McCall   // Fallback: insert at the end of the class. Check if protection matches!
146fe68088dSSam McCall   if (Loc.isInvalid()) {
147fe68088dSSam McCall     Loc = InClass.getBraceRange().getEnd();
148fe68088dSSam McCall     if (Protection != getAccessAtEnd(InClass)) {
149fe68088dSSam McCall       CodeBuffer = (getAccessSpelling(Protection) + ":\n" + Code).str();
150fe68088dSSam McCall       Code = CodeBuffer;
151fe68088dSSam McCall     }
152fe68088dSSam McCall   }
153fe68088dSSam McCall   if (!SM.isWrittenInSameFile(Loc, InClass.getLocation()))
154fe68088dSSam McCall     return error("Class body in wrong file: {0}", Loc.printToString(SM));
155fe68088dSSam McCall   return tooling::Replacement(SM, Loc, 0, Code);
156fe68088dSSam McCall }
157fe68088dSSam McCall 
158fe68088dSSam McCall } // namespace clangd
159fe68088dSSam McCall } // namespace clang
160