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