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 bool isFullyQualified(const NestedNameSpecifier *NNS) { 90 if (!NNS) 91 return false; 92 return NNS->getKind() == NestedNameSpecifier::Global || 93 isFullyQualified(NNS->getPrefix()); 94 } 95 96 struct InsertionPointData { 97 // Location to insert the "using" statement. If invalid then the statement 98 // should not be inserted at all (it already exists). 99 SourceLocation Loc; 100 // Extra suffix to place after the "using" statement. Depending on what the 101 // insertion point is anchored to, we may need one or more \n to ensure 102 // proper formatting. 103 std::string Suffix; 104 // Whether using should be fully qualified, even if what the user typed was 105 // not. This is based on our detection of the local style. 106 bool AlwaysFullyQualify = false; 107 }; 108 109 // Finds the best place to insert the "using" statement. Returns invalid 110 // SourceLocation if the "using" statement already exists. 111 // 112 // The insertion point might be a little awkward if the decl we're anchoring to 113 // has a comment in an unfortunate place (e.g. directly above function or using 114 // decl, or immediately following "namespace {". We should add some helpers for 115 // dealing with that and use them in other code modifications as well. 116 llvm::Expected<InsertionPointData> 117 findInsertionPoint(const Tweak::Selection &Inputs, 118 const NestedNameSpecifierLoc &QualifierToRemove, 119 const llvm::StringRef Name) { 120 auto &SM = Inputs.AST->getSourceManager(); 121 122 // Search for all using decls that affect this point in file. We need this for 123 // two reasons: to skip adding "using" if one already exists and to find best 124 // place to add it, if it doesn't exist. 125 SourceLocation LastUsingLoc; 126 std::vector<const UsingDecl *> Usings; 127 UsingFinder(Usings, &Inputs.ASTSelection.commonAncestor()->getDeclContext(), 128 SM) 129 .TraverseAST(Inputs.AST->getASTContext()); 130 131 bool AlwaysFullyQualify = true; 132 for (auto &U : Usings) { 133 // Only "upgrade" to fully qualified is all relevant using decls are fully 134 // qualified. Otherwise trust what the user typed. 135 if (!isFullyQualified(U->getQualifier())) 136 AlwaysFullyQualify = false; 137 138 if (SM.isBeforeInTranslationUnit(Inputs.Cursor, U->getUsingLoc())) 139 // "Usings" is sorted, so we're done. 140 break; 141 if (U->getQualifier()->getAsNamespace()->getCanonicalDecl() == 142 QualifierToRemove.getNestedNameSpecifier() 143 ->getAsNamespace() 144 ->getCanonicalDecl() && 145 U->getName() == Name) { 146 return InsertionPointData(); 147 } 148 // Insertion point will be before last UsingDecl that affects cursor 149 // position. For most cases this should stick with the local convention of 150 // add using inside or outside namespace. 151 LastUsingLoc = U->getUsingLoc(); 152 } 153 if (LastUsingLoc.isValid()) { 154 InsertionPointData Out; 155 Out.Loc = LastUsingLoc; 156 Out.AlwaysFullyQualify = AlwaysFullyQualify; 157 return Out; 158 } 159 160 // No relevant "using" statements. Try the nearest namespace level. 161 const DeclContext *ParentDeclCtx = 162 &Inputs.ASTSelection.commonAncestor()->getDeclContext(); 163 while (ParentDeclCtx && !ParentDeclCtx->isFileContext()) { 164 ParentDeclCtx = ParentDeclCtx->getLexicalParent(); 165 } 166 if (auto *ND = llvm::dyn_cast_or_null<NamespaceDecl>(ParentDeclCtx)) { 167 auto Toks = Inputs.AST->getTokens().expandedTokens(ND->getSourceRange()); 168 const auto *Tok = llvm::find_if(Toks, [](const syntax::Token &Tok) { 169 return Tok.kind() == tok::l_brace; 170 }); 171 if (Tok == Toks.end() || Tok->endLocation().isInvalid()) { 172 return llvm::createStringError(llvm::inconvertibleErrorCode(), 173 "Namespace with no {"); 174 } 175 if (!Tok->endLocation().isMacroID()) { 176 InsertionPointData Out; 177 Out.Loc = Tok->endLocation(); 178 Out.Suffix = "\n"; 179 return Out; 180 } 181 } 182 // No using, no namespace, no idea where to insert. Try above the first 183 // top level decl. 184 auto TLDs = Inputs.AST->getLocalTopLevelDecls(); 185 if (TLDs.empty()) { 186 return llvm::createStringError(llvm::inconvertibleErrorCode(), 187 "Cannot find place to insert \"using\""); 188 } 189 InsertionPointData Out; 190 Out.Loc = SM.getExpansionLoc(TLDs[0]->getBeginLoc()); 191 Out.Suffix = "\n\n"; 192 return Out; 193 } 194 195 bool AddUsing::prepare(const Selection &Inputs) { 196 auto &SM = Inputs.AST->getSourceManager(); 197 198 // Do not suggest "using" in header files. That way madness lies. 199 if (isHeaderFile(SM.getFileEntryForID(SM.getMainFileID())->getName(), 200 Inputs.AST->getLangOpts())) 201 return false; 202 203 auto *Node = Inputs.ASTSelection.commonAncestor(); 204 if (Node == nullptr) 205 return false; 206 207 // If we're looking at a type or NestedNameSpecifier, walk up the tree until 208 // we find the "main" node we care about, which would be ElaboratedTypeLoc or 209 // DeclRefExpr. 210 for (; Node->Parent; Node = Node->Parent) { 211 if (Node->ASTNode.get<NestedNameSpecifierLoc>()) { 212 continue; 213 } else if (auto *T = Node->ASTNode.get<TypeLoc>()) { 214 if (T->getAs<ElaboratedTypeLoc>()) { 215 break; 216 } else if (Node->Parent->ASTNode.get<TypeLoc>() || 217 Node->Parent->ASTNode.get<NestedNameSpecifierLoc>()) { 218 // Node is TypeLoc, but it's parent is either TypeLoc or 219 // NestedNameSpecifier. In both cases, we want to go up, to find 220 // the outermost TypeLoc. 221 continue; 222 } 223 } 224 break; 225 } 226 if (Node == nullptr) 227 return false; 228 229 if (auto *D = Node->ASTNode.get<DeclRefExpr>()) { 230 if (auto *II = D->getDecl()->getIdentifier()) { 231 QualifierToRemove = D->getQualifierLoc(); 232 Name = II->getName(); 233 } 234 } else if (auto *T = Node->ASTNode.get<TypeLoc>()) { 235 if (auto E = T->getAs<ElaboratedTypeLoc>()) { 236 if (auto *BaseTypeIdentifier = 237 E.getType().getUnqualifiedType().getBaseTypeIdentifier()) { 238 Name = BaseTypeIdentifier->getName(); 239 QualifierToRemove = E.getQualifierLoc(); 240 } 241 } 242 } 243 244 // FIXME: This only supports removing qualifiers that are made up of just 245 // namespace names. If qualifier contains a type, we could take the longest 246 // namespace prefix and remove that. 247 if (!QualifierToRemove.hasQualifier() || 248 !QualifierToRemove.getNestedNameSpecifier()->getAsNamespace() || 249 Name.empty()) { 250 return false; 251 } 252 253 // Macros are difficult. We only want to offer code action when what's spelled 254 // under the cursor is a namespace qualifier. If it's a macro that expands to 255 // a qualifier, user would not know what code action will actually change. 256 // On the other hand, if the qualifier is part of the macro argument, we 257 // should still support that. 258 if (SM.isMacroBodyExpansion(QualifierToRemove.getBeginLoc()) || 259 !SM.isWrittenInSameFile(QualifierToRemove.getBeginLoc(), 260 QualifierToRemove.getEndLoc())) { 261 return false; 262 } 263 264 return true; 265 } 266 267 Expected<Tweak::Effect> AddUsing::apply(const Selection &Inputs) { 268 auto &SM = Inputs.AST->getSourceManager(); 269 auto &TB = Inputs.AST->getTokens(); 270 271 // Determine the length of the qualifier under the cursor, then remove it. 272 auto SpelledTokens = TB.spelledForExpanded( 273 TB.expandedTokens(QualifierToRemove.getSourceRange())); 274 if (!SpelledTokens) { 275 return llvm::createStringError( 276 llvm::inconvertibleErrorCode(), 277 "Could not determine length of the qualifier"); 278 } 279 unsigned Length = 280 syntax::Token::range(SM, SpelledTokens->front(), SpelledTokens->back()) 281 .length(); 282 tooling::Replacements R; 283 if (auto Err = R.add(tooling::Replacement( 284 SM, SpelledTokens->front().location(), Length, ""))) { 285 return std::move(Err); 286 } 287 288 auto InsertionPoint = findInsertionPoint(Inputs, QualifierToRemove, Name); 289 if (!InsertionPoint) { 290 return InsertionPoint.takeError(); 291 } 292 293 if (InsertionPoint->Loc.isValid()) { 294 // Add the using statement at appropriate location. 295 std::string UsingText; 296 llvm::raw_string_ostream UsingTextStream(UsingText); 297 UsingTextStream << "using "; 298 if (InsertionPoint->AlwaysFullyQualify && 299 !isFullyQualified(QualifierToRemove.getNestedNameSpecifier())) 300 UsingTextStream << "::"; 301 QualifierToRemove.getNestedNameSpecifier()->print( 302 UsingTextStream, Inputs.AST->getASTContext().getPrintingPolicy()); 303 UsingTextStream << Name << ";" << InsertionPoint->Suffix; 304 305 assert(SM.getFileID(InsertionPoint->Loc) == SM.getMainFileID()); 306 if (auto Err = R.add(tooling::Replacement(SM, InsertionPoint->Loc, 0, 307 UsingTextStream.str()))) { 308 return std::move(Err); 309 } 310 } 311 312 return Effect::mainFileEdit(Inputs.AST->getASTContext().getSourceManager(), 313 std::move(R)); 314 } 315 316 } // namespace 317 } // namespace clangd 318 } // namespace clang 319