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 "refactor/Tweak.h" 12 #include "support/Logger.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 DeclContext *ParentDeclCtx = 145 &Inputs.ASTSelection.commonAncestor()->getDeclContext(); 146 while (ParentDeclCtx && !ParentDeclCtx->isFileContext()) { 147 ParentDeclCtx = ParentDeclCtx->getLexicalParent(); 148 } 149 if (auto *ND = llvm::dyn_cast_or_null<NamespaceDecl>(ParentDeclCtx)) { 150 auto Toks = Inputs.AST->getTokens().expandedTokens(ND->getSourceRange()); 151 const auto *Tok = llvm::find_if(Toks, [](const syntax::Token &Tok) { 152 return Tok.kind() == tok::l_brace; 153 }); 154 if (Tok == Toks.end() || Tok->endLocation().isInvalid()) { 155 return llvm::createStringError(llvm::inconvertibleErrorCode(), 156 "Namespace with no {"); 157 } 158 if (!Tok->endLocation().isMacroID()) { 159 InsertionPointData Out; 160 Out.Loc = Tok->endLocation(); 161 Out.Suffix = "\n"; 162 return Out; 163 } 164 } 165 // No using, no namespace, no idea where to insert. Try above the first 166 // top level decl. 167 auto TLDs = Inputs.AST->getLocalTopLevelDecls(); 168 if (TLDs.empty()) { 169 return llvm::createStringError(llvm::inconvertibleErrorCode(), 170 "Cannot find place to insert \"using\""); 171 } 172 InsertionPointData Out; 173 Out.Loc = SM.getExpansionLoc(TLDs[0]->getBeginLoc()); 174 Out.Suffix = "\n\n"; 175 return Out; 176 } 177 178 bool AddUsing::prepare(const Selection &Inputs) { 179 auto &SM = Inputs.AST->getSourceManager(); 180 181 // Do not suggest "using" in header files. That way madness lies. 182 if (isHeaderFile(SM.getFileEntryForID(SM.getMainFileID())->getName(), 183 Inputs.AST->getLangOpts())) 184 return false; 185 186 auto *Node = Inputs.ASTSelection.commonAncestor(); 187 if (Node == nullptr) 188 return false; 189 190 // If we're looking at a type or NestedNameSpecifier, walk up the tree until 191 // we find the "main" node we care about, which would be ElaboratedTypeLoc or 192 // DeclRefExpr. 193 for (; Node->Parent; Node = Node->Parent) { 194 if (Node->ASTNode.get<NestedNameSpecifierLoc>()) { 195 continue; 196 } else if (auto *T = Node->ASTNode.get<TypeLoc>()) { 197 if (T->getAs<ElaboratedTypeLoc>()) { 198 break; 199 } else if (Node->Parent->ASTNode.get<TypeLoc>() || 200 Node->Parent->ASTNode.get<NestedNameSpecifierLoc>()) { 201 // Node is TypeLoc, but it's parent is either TypeLoc or 202 // NestedNameSpecifier. In both cases, we want to go up, to find 203 // the outermost TypeLoc. 204 continue; 205 } 206 } 207 break; 208 } 209 if (Node == nullptr) 210 return false; 211 212 if (auto *D = Node->ASTNode.get<DeclRefExpr>()) { 213 if (auto *II = D->getDecl()->getIdentifier()) { 214 QualifierToRemove = D->getQualifierLoc(); 215 Name = II->getName(); 216 } 217 } else if (auto *T = Node->ASTNode.get<TypeLoc>()) { 218 if (auto E = T->getAs<ElaboratedTypeLoc>()) { 219 if (auto *BaseTypeIdentifier = 220 E.getType().getUnqualifiedType().getBaseTypeIdentifier()) { 221 Name = BaseTypeIdentifier->getName(); 222 QualifierToRemove = E.getQualifierLoc(); 223 } 224 } 225 } 226 227 // FIXME: This only supports removing qualifiers that are made up of just 228 // namespace names. If qualifier contains a type, we could take the longest 229 // namespace prefix and remove that. 230 if (!QualifierToRemove.hasQualifier() || 231 !QualifierToRemove.getNestedNameSpecifier()->getAsNamespace() || 232 Name.empty()) { 233 return false; 234 } 235 236 // Macros are difficult. We only want to offer code action when what's spelled 237 // under the cursor is a namespace qualifier. If it's a macro that expands to 238 // a qualifier, user would not know what code action will actually change. 239 // On the other hand, if the qualifier is part of the macro argument, we 240 // should still support that. 241 if (SM.isMacroBodyExpansion(QualifierToRemove.getBeginLoc()) || 242 !SM.isWrittenInSameFile(QualifierToRemove.getBeginLoc(), 243 QualifierToRemove.getEndLoc())) { 244 return false; 245 } 246 247 return true; 248 } 249 250 Expected<Tweak::Effect> AddUsing::apply(const Selection &Inputs) { 251 auto &SM = Inputs.AST->getSourceManager(); 252 auto &TB = Inputs.AST->getTokens(); 253 254 // Determine the length of the qualifier under the cursor, then remove it. 255 auto SpelledTokens = TB.spelledForExpanded( 256 TB.expandedTokens(QualifierToRemove.getSourceRange())); 257 if (!SpelledTokens) { 258 return llvm::createStringError( 259 llvm::inconvertibleErrorCode(), 260 "Could not determine length of the qualifier"); 261 } 262 unsigned Length = 263 syntax::Token::range(SM, SpelledTokens->front(), SpelledTokens->back()) 264 .length(); 265 tooling::Replacements R; 266 if (auto Err = R.add(tooling::Replacement( 267 SM, SpelledTokens->front().location(), Length, ""))) { 268 return std::move(Err); 269 } 270 271 auto InsertionPoint = findInsertionPoint(Inputs, QualifierToRemove, Name); 272 if (!InsertionPoint) { 273 return InsertionPoint.takeError(); 274 } 275 276 if (InsertionPoint->Loc.isValid()) { 277 // Add the using statement at appropriate location. 278 std::string UsingText; 279 llvm::raw_string_ostream UsingTextStream(UsingText); 280 UsingTextStream << "using "; 281 QualifierToRemove.getNestedNameSpecifier()->print( 282 UsingTextStream, Inputs.AST->getASTContext().getPrintingPolicy()); 283 UsingTextStream << Name << ";" << InsertionPoint->Suffix; 284 285 assert(SM.getFileID(InsertionPoint->Loc) == SM.getMainFileID()); 286 if (auto Err = R.add(tooling::Replacement(SM, InsertionPoint->Loc, 0, 287 UsingTextStream.str()))) { 288 return std::move(Err); 289 } 290 } 291 292 return Effect::mainFileEdit(Inputs.AST->getASTContext().getSourceManager(), 293 std::move(R)); 294 } 295 296 } // namespace 297 } // namespace clangd 298 } // namespace clang 299