1 //===--- CodeCompletionStrings.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 "CodeCompletionStrings.h" 10 #include "clang/AST/ASTContext.h" 11 #include "clang/AST/DeclObjC.h" 12 #include "clang/AST/RawCommentList.h" 13 #include "clang/Basic/SourceManager.h" 14 #include "clang/Sema/CodeCompleteConsumer.h" 15 #include "llvm/Support/JSON.h" 16 #include <limits> 17 #include <utility> 18 19 namespace clang { 20 namespace clangd { 21 namespace { 22 23 bool isInformativeQualifierChunk(CodeCompletionString::Chunk const &Chunk) { 24 return Chunk.Kind == CodeCompletionString::CK_Informative && 25 llvm::StringRef(Chunk.Text).endswith("::"); 26 } 27 28 void appendEscapeSnippet(const llvm::StringRef Text, std::string *Out) { 29 for (const auto Character : Text) { 30 if (Character == '$' || Character == '}' || Character == '\\') 31 Out->push_back('\\'); 32 Out->push_back(Character); 33 } 34 } 35 36 void appendOptionalChunk(const CodeCompletionString &CCS, std::string *Out) { 37 for (const CodeCompletionString::Chunk &C : CCS) { 38 switch (C.Kind) { 39 case CodeCompletionString::CK_Optional: 40 assert(C.Optional && 41 "Expected the optional code completion string to be non-null."); 42 appendOptionalChunk(*C.Optional, Out); 43 break; 44 default: 45 *Out += C.Text; 46 break; 47 } 48 } 49 } 50 51 bool looksLikeDocComment(llvm::StringRef CommentText) { 52 // We don't report comments that only contain "special" chars. 53 // This avoids reporting various delimiters, like: 54 // ================= 55 // ----------------- 56 // ***************** 57 return CommentText.find_first_not_of("/*-= \t\r\n") != llvm::StringRef::npos; 58 } 59 60 } // namespace 61 62 std::string getDocComment(const ASTContext &Ctx, 63 const CodeCompletionResult &Result, 64 bool CommentsFromHeaders) { 65 // FIXME: clang's completion also returns documentation for RK_Pattern if they 66 // contain a pattern for ObjC properties. Unfortunately, there is no API to 67 // get this declaration, so we don't show documentation in that case. 68 if (Result.Kind != CodeCompletionResult::RK_Declaration) 69 return ""; 70 return Result.getDeclaration() ? getDeclComment(Ctx, *Result.getDeclaration()) 71 : ""; 72 } 73 74 std::string getDeclComment(const ASTContext &Ctx, const NamedDecl &Decl) { 75 if (isa<NamespaceDecl>(Decl)) { 76 // Namespaces often have too many redecls for any particular redecl comment 77 // to be useful. Moreover, we often confuse file headers or generated 78 // comments with namespace comments. Therefore we choose to just ignore 79 // the comments for namespaces. 80 return ""; 81 } 82 const RawComment *RC = getCompletionComment(Ctx, &Decl); 83 if (!RC) 84 return ""; 85 // Sanity check that the comment does not come from the PCH. We choose to not 86 // write them into PCH, because they are racy and slow to load. 87 assert(!Ctx.getSourceManager().isLoadedSourceLocation(RC->getBeginLoc())); 88 std::string Doc = 89 RC->getFormattedText(Ctx.getSourceManager(), Ctx.getDiagnostics()); 90 if (!looksLikeDocComment(Doc)) 91 return ""; 92 // Clang requires source to be UTF-8, but doesn't enforce this in comments. 93 if (!llvm::json::isUTF8(Doc)) 94 Doc = llvm::json::fixUTF8(Doc); 95 return Doc; 96 } 97 98 void getSignature(const CodeCompletionString &CCS, std::string *Signature, 99 std::string *Snippet, std::string *RequiredQualifiers, 100 bool CompletingPattern) { 101 // Placeholder with this index will be ${0:…} to mark final cursor position. 102 // Usually we do not add $0, so the cursor is placed at end of completed text. 103 unsigned CursorSnippetArg = std::numeric_limits<unsigned>::max(); 104 if (CompletingPattern) { 105 // In patterns, it's best to place the cursor at the last placeholder, to 106 // handle cases like 107 // namespace ${1:name} { 108 // ${0:decls} 109 // } 110 CursorSnippetArg = 111 llvm::count_if(CCS, [](const CodeCompletionString::Chunk &C) { 112 return C.Kind == CodeCompletionString::CK_Placeholder; 113 }); 114 } 115 unsigned SnippetArg = 0; 116 bool HadObjCArguments = false; 117 bool HadInformativeChunks = false; 118 for (const auto &Chunk : CCS) { 119 // Informative qualifier chunks only clutter completion results, skip 120 // them. 121 if (isInformativeQualifierChunk(Chunk)) 122 continue; 123 124 switch (Chunk.Kind) { 125 case CodeCompletionString::CK_TypedText: 126 // The typed-text chunk is the actual name. We don't record this chunk. 127 // C++: 128 // In general our string looks like <qualifiers><name><signature>. 129 // So once we see the name, any text we recorded so far should be 130 // reclassified as qualifiers. 131 // 132 // Objective-C: 133 // Objective-C methods expressions may have multiple typed-text chunks, 134 // so we must treat them carefully. For Objective-C methods, all 135 // typed-text and informative chunks will end in ':' (unless there are 136 // no arguments, in which case we can safely treat them as C++). 137 // 138 // Completing a method declaration itself (not a method expression) is 139 // similar except that we use the `RequiredQualifiers` to store the 140 // text before the selector, e.g. `- (void)`. 141 if (!llvm::StringRef(Chunk.Text).endswith(":")) { // Treat as C++. 142 if (RequiredQualifiers) 143 *RequiredQualifiers = std::move(*Signature); 144 Signature->clear(); 145 Snippet->clear(); 146 } else { // Objective-C method with args. 147 // If this is the first TypedText to the Objective-C method, discard any 148 // text that we've previously seen (such as previous parameter selector, 149 // which will be marked as Informative text). 150 // 151 // TODO: Make previous parameters part of the signature for Objective-C 152 // methods. 153 if (!HadObjCArguments) { 154 HadObjCArguments = true; 155 // If we have no previous informative chunks (informative selector 156 // fragments in practice), we treat any previous chunks as 157 // `RequiredQualifiers` so they will be added as a prefix during the 158 // completion. 159 // 160 // e.g. to complete `- (void)doSomething:(id)argument`: 161 // - Completion name: `doSomething:` 162 // - RequiredQualifiers: `- (void)` 163 // - Snippet/Signature suffix: `(id)argument` 164 // 165 // This differs from the case when we're completing a method 166 // expression with a previous informative selector fragment. 167 // 168 // e.g. to complete `[self doSomething:nil ^somethingElse:(id)]`: 169 // - Previous Informative Chunk: `doSomething:` 170 // - Completion name: `somethingElse:` 171 // - Snippet/Signature suffix: `(id)` 172 if (!HadInformativeChunks) { 173 if (RequiredQualifiers) 174 *RequiredQualifiers = std::move(*Signature); 175 Snippet->clear(); 176 } 177 Signature->clear(); 178 } else { // Subsequent argument, considered part of snippet/signature. 179 *Signature += Chunk.Text; 180 *Snippet += Chunk.Text; 181 } 182 } 183 break; 184 case CodeCompletionString::CK_Text: 185 *Signature += Chunk.Text; 186 *Snippet += Chunk.Text; 187 break; 188 case CodeCompletionString::CK_Optional: 189 assert(Chunk.Optional); 190 // No need to create placeholders for default arguments in Snippet. 191 appendOptionalChunk(*Chunk.Optional, Signature); 192 break; 193 case CodeCompletionString::CK_Placeholder: 194 *Signature += Chunk.Text; 195 ++SnippetArg; 196 *Snippet += 197 "${" + 198 std::to_string(SnippetArg == CursorSnippetArg ? 0 : SnippetArg) + ':'; 199 appendEscapeSnippet(Chunk.Text, Snippet); 200 *Snippet += '}'; 201 break; 202 case CodeCompletionString::CK_Informative: 203 HadInformativeChunks = true; 204 // For example, the word "const" for a const method, or the name of 205 // the base class for methods that are part of the base class. 206 *Signature += Chunk.Text; 207 // Don't put the informative chunks in the snippet. 208 break; 209 case CodeCompletionString::CK_ResultType: 210 // This is not part of the signature. 211 break; 212 case CodeCompletionString::CK_CurrentParameter: 213 // This should never be present while collecting completion items, 214 // only while collecting overload candidates. 215 llvm_unreachable("Unexpected CK_CurrentParameter while collecting " 216 "CompletionItems"); 217 break; 218 case CodeCompletionString::CK_LeftParen: 219 case CodeCompletionString::CK_RightParen: 220 case CodeCompletionString::CK_LeftBracket: 221 case CodeCompletionString::CK_RightBracket: 222 case CodeCompletionString::CK_LeftBrace: 223 case CodeCompletionString::CK_RightBrace: 224 case CodeCompletionString::CK_LeftAngle: 225 case CodeCompletionString::CK_RightAngle: 226 case CodeCompletionString::CK_Comma: 227 case CodeCompletionString::CK_Colon: 228 case CodeCompletionString::CK_SemiColon: 229 case CodeCompletionString::CK_Equal: 230 case CodeCompletionString::CK_HorizontalSpace: 231 *Signature += Chunk.Text; 232 *Snippet += Chunk.Text; 233 break; 234 case CodeCompletionString::CK_VerticalSpace: 235 *Snippet += Chunk.Text; 236 // Don't even add a space to the signature. 237 break; 238 } 239 } 240 } 241 242 std::string formatDocumentation(const CodeCompletionString &CCS, 243 llvm::StringRef DocComment) { 244 // Things like __attribute__((nonnull(1,3))) and [[noreturn]]. Present this 245 // information in the documentation field. 246 std::string Result; 247 const unsigned AnnotationCount = CCS.getAnnotationCount(); 248 if (AnnotationCount > 0) { 249 Result += "Annotation"; 250 if (AnnotationCount == 1) { 251 Result += ": "; 252 } else /* AnnotationCount > 1 */ { 253 Result += "s: "; 254 } 255 for (unsigned I = 0; I < AnnotationCount; ++I) { 256 Result += CCS.getAnnotation(I); 257 Result.push_back(I == AnnotationCount - 1 ? '\n' : ' '); 258 } 259 } 260 // Add brief documentation (if there is any). 261 if (!DocComment.empty()) { 262 if (!Result.empty()) { 263 // This means we previously added annotations. Add an extra newline 264 // character to make the annotations stand out. 265 Result.push_back('\n'); 266 } 267 Result += DocComment; 268 } 269 return Result; 270 } 271 272 std::string getReturnType(const CodeCompletionString &CCS) { 273 for (const auto &Chunk : CCS) 274 if (Chunk.Kind == CodeCompletionString::CK_ResultType) 275 return Chunk.Text; 276 return ""; 277 } 278 279 } // namespace clangd 280 } // namespace clang 281