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