1 //===--- AddUsing.cpp --------------------------------------------*- C++-*-===// 2 // 3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. 4 // See https://llvm.org/LICENSE.txt for license information. 5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception 6 // 7 //===----------------------------------------------------------------------===// 8 9 #include "AST.h" 10 #include "FindTarget.h" 11 #include "Logger.h" 12 #include "refactor/Tweak.h" 13 #include "clang/AST/Decl.h" 14 #include "clang/AST/RecursiveASTVisitor.h" 15 16 namespace clang { 17 namespace clangd { 18 namespace { 19 20 // Tweak for removing full namespace qualifier under cursor on DeclRefExpr and 21 // types and adding "using" statement instead. 22 // 23 // Only qualifiers that refer exclusively to namespaces (no record types) are 24 // supported. There is some guessing of appropriate place to insert the using 25 // declaration. If we find any existing usings, we insert it there. If not, we 26 // insert right after the inner-most relevant namespace declaration. If there is 27 // none, or there is, but it was declared via macro, we insert above the first 28 // top level decl. 29 // 30 // Currently this only removes qualifier from under the cursor. In the future, 31 // we should improve this to remove qualifier from all occurrences of this 32 // symbol. 33 class AddUsing : public Tweak { 34 public: 35 const char *id() const override; 36 37 bool prepare(const Selection &Inputs) override; 38 Expected<Effect> apply(const Selection &Inputs) override; 39 std::string title() const override; 40 Intent intent() const override { return Refactor; } 41 42 private: 43 // The qualifier to remove. Set by prepare(). 44 NestedNameSpecifierLoc QualifierToRemove; 45 // The name following QualifierToRemove. Set by prepare(). 46 llvm::StringRef Name; 47 }; 48 REGISTER_TWEAK(AddUsing) 49 50 std::string AddUsing::title() const { 51 return std::string(llvm::formatv( 52 "Add using-declaration for {0} and remove qualifier.", Name)); 53 } 54 55 // Locates all "using" statements relevant to SelectionDeclContext. 56 class UsingFinder : public RecursiveASTVisitor<UsingFinder> { 57 public: 58 UsingFinder(std::vector<const UsingDecl *> &Results, 59 const DeclContext *SelectionDeclContext, const SourceManager &SM) 60 : Results(Results), SelectionDeclContext(SelectionDeclContext), SM(SM) {} 61 62 bool VisitUsingDecl(UsingDecl *D) { 63 auto Loc = D->getUsingLoc(); 64 if (SM.getFileID(Loc) != SM.getMainFileID()) { 65 return true; 66 } 67 if (D->getDeclContext()->Encloses(SelectionDeclContext)) { 68 Results.push_back(D); 69 } 70 return true; 71 } 72 73 bool TraverseDecl(Decl *Node) { 74 // There is no need to go deeper into nodes that do not enclose selection, 75 // since "using" there will not affect selection, nor would it make a good 76 // insertion point. 77 if (Node->getDeclContext()->Encloses(SelectionDeclContext)) { 78 return RecursiveASTVisitor<UsingFinder>::TraverseDecl(Node); 79 } 80 return true; 81 } 82 83 private: 84 std::vector<const UsingDecl *> &Results; 85 const DeclContext *SelectionDeclContext; 86 const SourceManager &SM; 87 }; 88 89 struct InsertionPointData { 90 // Location to insert the "using" statement. If invalid then the statement 91 // should not be inserted at all (it already exists). 92 SourceLocation Loc; 93 // Extra suffix to place after the "using" statement. Depending on what the 94 // insertion point is anchored to, we may need one or more \n to ensure 95 // proper formatting. 96 std::string Suffix; 97 }; 98 99 // Finds the best place to insert the "using" statement. Returns invalid 100 // SourceLocation if the "using" statement already exists. 101 // 102 // The insertion point might be a little awkward if the decl we're anchoring to 103 // has a comment in an unfortunate place (e.g. directly above function or using 104 // decl, or immediately following "namespace {". We should add some helpers for 105 // dealing with that and use them in other code modifications as well. 106 llvm::Expected<InsertionPointData> 107 findInsertionPoint(const Tweak::Selection &Inputs, 108 const NestedNameSpecifierLoc &QualifierToRemove, 109 const llvm::StringRef Name) { 110 auto &SM = Inputs.AST->getSourceManager(); 111 112 // Search for all using decls that affect this point in file. We need this for 113 // two reasons: to skip adding "using" if one already exists and to find best 114 // place to add it, if it doesn't exist. 115 SourceLocation LastUsingLoc; 116 std::vector<const UsingDecl *> Usings; 117 UsingFinder(Usings, &Inputs.ASTSelection.commonAncestor()->getDeclContext(), 118 SM) 119 .TraverseAST(Inputs.AST->getASTContext()); 120 121 for (auto &U : Usings) { 122 if (SM.isBeforeInTranslationUnit(Inputs.Cursor, U->getUsingLoc())) 123 // "Usings" is sorted, so we're done. 124 break; 125 if (U->getQualifier()->getAsNamespace()->getCanonicalDecl() == 126 QualifierToRemove.getNestedNameSpecifier() 127 ->getAsNamespace() 128 ->getCanonicalDecl() && 129 U->getName() == Name) { 130 return InsertionPointData(); 131 } 132 // Insertion point will be before last UsingDecl that affects cursor 133 // position. For most cases this should stick with the local convention of 134 // add using inside or outside namespace. 135 LastUsingLoc = U->getUsingLoc(); 136 } 137 if (LastUsingLoc.isValid()) { 138 InsertionPointData Out; 139 Out.Loc = LastUsingLoc; 140 return Out; 141 } 142 143 // No relevant "using" statements. Try the nearest namespace level. 144 const auto *NS = Inputs.ASTSelection.commonAncestor() 145 ->getDeclContext() 146 .getEnclosingNamespaceContext(); 147 if (auto *ND = dyn_cast<NamespaceDecl>(NS)) { 148 auto Toks = Inputs.AST->getTokens().expandedTokens(ND->getSourceRange()); 149 const auto *Tok = llvm::find_if(Toks, [](const syntax::Token &Tok) { 150 return Tok.kind() == tok::l_brace; 151 }); 152 if (Tok == Toks.end() || Tok->endLocation().isInvalid()) { 153 return llvm::createStringError(llvm::inconvertibleErrorCode(), 154 "Namespace with no {"); 155 } 156 if (!Tok->endLocation().isMacroID()) { 157 InsertionPointData Out; 158 Out.Loc = Tok->endLocation(); 159 Out.Suffix = "\n"; 160 return Out; 161 } 162 } 163 // No using, no namespace, no idea where to insert. Try above the first 164 // top level decl. 165 auto TLDs = Inputs.AST->getLocalTopLevelDecls(); 166 if (TLDs.empty()) { 167 return llvm::createStringError(llvm::inconvertibleErrorCode(), 168 "Cannot find place to insert \"using\""); 169 } 170 InsertionPointData Out; 171 Out.Loc = SM.getExpansionLoc(TLDs[0]->getBeginLoc()); 172 Out.Suffix = "\n\n"; 173 return Out; 174 } 175 176 bool AddUsing::prepare(const Selection &Inputs) { 177 auto &SM = Inputs.AST->getSourceManager(); 178 auto *Node = Inputs.ASTSelection.commonAncestor(); 179 if (Node == nullptr) 180 return false; 181 182 // If we're looking at a type or NestedNameSpecifier, walk up the tree until 183 // we find the "main" node we care about, which would be ElaboratedTypeLoc or 184 // DeclRefExpr. 185 for (; Node->Parent; Node = Node->Parent) { 186 if (Node->ASTNode.get<NestedNameSpecifierLoc>()) { 187 continue; 188 } else if (auto *T = Node->ASTNode.get<TypeLoc>()) { 189 if (T->getAs<ElaboratedTypeLoc>()) { 190 break; 191 } else if (Node->Parent->ASTNode.get<TypeLoc>() || 192 Node->Parent->ASTNode.get<NestedNameSpecifierLoc>()) { 193 // Node is TypeLoc, but it's parent is either TypeLoc or 194 // NestedNameSpecifier. In both cases, we want to go up, to find 195 // the outermost TypeLoc. 196 continue; 197 } 198 } 199 break; 200 } 201 if (Node == nullptr) 202 return false; 203 204 if (auto *D = Node->ASTNode.get<DeclRefExpr>()) { 205 QualifierToRemove = D->getQualifierLoc(); 206 Name = D->getDecl()->getName(); 207 } else if (auto *T = Node->ASTNode.get<TypeLoc>()) { 208 if (auto E = T->getAs<ElaboratedTypeLoc>()) { 209 if (auto *BaseTypeIdentifier = 210 E.getType().getUnqualifiedType().getBaseTypeIdentifier()) { 211 Name = BaseTypeIdentifier->getName(); 212 QualifierToRemove = E.getQualifierLoc(); 213 } 214 } 215 } 216 217 // FIXME: This only supports removing qualifiers that are made up of just 218 // namespace names. If qualifier contains a type, we could take the longest 219 // namespace prefix and remove that. 220 if (!QualifierToRemove.hasQualifier() || 221 !QualifierToRemove.getNestedNameSpecifier()->getAsNamespace() || 222 Name.empty()) { 223 return false; 224 } 225 226 // Macros are difficult. We only want to offer code action when what's spelled 227 // under the cursor is a namespace qualifier. If it's a macro that expands to 228 // a qualifier, user would not know what code action will actually change. 229 // On the other hand, if the qualifier is part of the macro argument, we 230 // should still support that. 231 if (SM.isMacroBodyExpansion(QualifierToRemove.getBeginLoc()) || 232 !SM.isWrittenInSameFile(QualifierToRemove.getBeginLoc(), 233 QualifierToRemove.getEndLoc())) { 234 return false; 235 } 236 237 return true; 238 } 239 240 Expected<Tweak::Effect> AddUsing::apply(const Selection &Inputs) { 241 auto &SM = Inputs.AST->getSourceManager(); 242 auto &TB = Inputs.AST->getTokens(); 243 244 // Determine the length of the qualifier under the cursor, then remove it. 245 auto SpelledTokens = TB.spelledForExpanded( 246 TB.expandedTokens(QualifierToRemove.getSourceRange())); 247 if (!SpelledTokens) { 248 return llvm::createStringError( 249 llvm::inconvertibleErrorCode(), 250 "Could not determine length of the qualifier"); 251 } 252 unsigned Length = 253 syntax::Token::range(SM, SpelledTokens->front(), SpelledTokens->back()) 254 .length(); 255 tooling::Replacements R; 256 if (auto Err = R.add(tooling::Replacement( 257 SM, SpelledTokens->front().location(), Length, ""))) { 258 return std::move(Err); 259 } 260 261 auto InsertionPoint = findInsertionPoint(Inputs, QualifierToRemove, Name); 262 if (!InsertionPoint) { 263 return InsertionPoint.takeError(); 264 } 265 266 if (InsertionPoint->Loc.isValid()) { 267 // Add the using statement at appropriate location. 268 std::string UsingText; 269 llvm::raw_string_ostream UsingTextStream(UsingText); 270 UsingTextStream << "using "; 271 QualifierToRemove.getNestedNameSpecifier()->print( 272 UsingTextStream, Inputs.AST->getASTContext().getPrintingPolicy()); 273 UsingTextStream << Name << ";" << InsertionPoint->Suffix; 274 275 assert(SM.getFileID(InsertionPoint->Loc) == SM.getMainFileID()); 276 if (auto Err = R.add(tooling::Replacement(SM, InsertionPoint->Loc, 0, 277 UsingTextStream.str()))) { 278 return std::move(Err); 279 } 280 } 281 282 return Effect::mainFileEdit(Inputs.AST->getASTContext().getSourceManager(), 283 std::move(R)); 284 } 285 286 } // namespace 287 } // namespace clangd 288 } // namespace clang 289