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