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