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