1 //===--- CodeCompletionStrings.cpp -------------------------------*- C++-*-===//
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
7 //===----------------------------------------------------------------------===//
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"
24 bool isInformativeQualifierChunk(CodeCompletionString::Chunk
const &Chunk
) {
25 return Chunk
.Kind
== CodeCompletionString::CK_Informative
&&
26 llvm::StringRef(Chunk
.Text
).endswith("::");
29 void appendEscapeSnippet(const llvm::StringRef Text
, std::string
*Out
) {
30 for (const auto Character
: Text
) {
31 if (Character
== '$' || Character
== '}' || Character
== '\\')
33 Out
->push_back(Character
);
37 void appendOptionalChunk(const CodeCompletionString
&CCS
, std::string
*Out
) {
38 for (const CodeCompletionString::Chunk
&C
: CCS
) {
40 case CodeCompletionString::CK_Optional
:
42 "Expected the optional code completion string to be non-null.");
43 appendOptionalChunk(*C
.Optional
, Out
);
52 bool looksLikeDocComment(llvm::StringRef CommentText
) {
53 // We don't report comments that only contain "special" chars.
54 // This avoids reporting various delimiters, like:
58 return CommentText
.find_first_not_of("/*-= \t\r\n") != llvm::StringRef::npos
;
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
;
67 if (!CompletingPattern
)
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
)
83 std::string
getDocComment(const ASTContext
&Ctx
,
84 const CodeCompletionResult
&Result
,
85 bool CommentsFromHeaders
) {
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
)
91 return Result
.getDeclaration() ? getDeclComment(Ctx
, *Result
.getDeclaration())
95 std::string
getDeclComment(const ASTContext
&Ctx
, const NamedDecl
&Decl
) {
96 if (isa
<NamespaceDecl
>(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.
103 const RawComment
*RC
= getCompletionComment(Ctx
, &Decl
);
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()));
110 RC
->getFormattedText(Ctx
.getSourceManager(), Ctx
.getDiagnostics());
111 if (!looksLikeDocComment(Doc
))
113 // Clang requires source to be UTF-8, but doesn't enforce this in comments.
114 if (!llvm::json::isUTF8(Doc
))
115 Doc
= llvm::json::fixUTF8(Doc
);
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();
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} {
133 // We try to identify such cases using the ResultKind and CursorKind.
134 if (shouldPatchPlaceholder0(ResultKind
, CursorKind
)) {
136 llvm::count_if(CCS
, [](const CodeCompletionString::Chunk
&C
) {
137 return C
.Kind
== CodeCompletionString::CK_Placeholder
;
140 unsigned SnippetArg
= 0;
141 bool HadObjCArguments
= false;
142 bool HadInformativeChunks
= false;
144 std::optional
<unsigned> TruncateSnippetAt
;
145 for (const auto &Chunk
: CCS
) {
146 // Informative qualifier chunks only clutter completion results, skip
148 if (isInformativeQualifierChunk(Chunk
))
151 switch (Chunk
.Kind
) {
152 case CodeCompletionString::CK_TypedText
:
153 // The typed-text chunk is the actual name. We don't record this chunk.
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.
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++).
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
).endswith(":")) { // Treat as C++.
169 if (RequiredQualifiers
)
170 *RequiredQualifiers
= std::move(*Signature
);
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).
178 // TODO: Make previous parameters part of the signature for Objective-C
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
187 // e.g. to complete `- (void)doSomething:(id)argument`:
188 // - Completion name: `doSomething:`
189 // - RequiredQualifiers: `- (void)`
190 // - Snippet/Signature suffix: `(id)argument`
192 // This differs from the case when we're completing a method
193 // expression with a previous informative selector fragment.
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
);
205 } else { // Subsequent argument, considered part of snippet/signature.
206 *Signature
+= Chunk
.Text
;
207 *Snippet
+= Chunk
.Text
;
211 case CodeCompletionString::CK_Text
:
212 *Signature
+= Chunk
.Text
;
213 *Snippet
+= Chunk
.Text
;
215 case CodeCompletionString::CK_Optional
:
216 assert(Chunk
.Optional
);
217 // No need to create placeholders for default arguments in Snippet.
218 appendOptionalChunk(*Chunk
.Optional
, Signature
);
220 case CodeCompletionString::CK_Placeholder
:
221 *Signature
+= Chunk
.Text
;
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).
228 *Snippet
+= "${" + std::to_string(SnippetArg
) + ':';
229 appendEscapeSnippet(Chunk
.Text
, Snippet
);
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.
240 case CodeCompletionString::CK_ResultType
:
241 // This is not part of the signature.
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 "
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(Snippet
->size());
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
;
272 case CodeCompletionString::CK_VerticalSpace
:
273 *Snippet
+= Chunk
.Text
;
274 // Don't even add a space to the signature.
278 if (TruncateSnippetAt
)
279 *Snippet
= Snippet
->substr(0, *TruncateSnippetAt
);
282 std::string
formatDocumentation(const CodeCompletionString
&CCS
,
283 llvm::StringRef DocComment
) {
284 // Things like __attribute__((nonnull(1,3))) and [[noreturn]]. Present this
285 // information in the documentation field.
287 const unsigned AnnotationCount
= CCS
.getAnnotationCount();
288 if (AnnotationCount
> 0) {
289 Result
+= "Annotation";
290 if (AnnotationCount
== 1) {
292 } else /* AnnotationCount > 1 */ {
295 for (unsigned I
= 0; I
< AnnotationCount
; ++I
) {
296 Result
+= CCS
.getAnnotation(I
);
297 Result
.push_back(I
== AnnotationCount
- 1 ? '\n' : ' ');
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('\n');
307 Result
+= DocComment
;
312 std::string
getReturnType(const CodeCompletionString
&CCS
) {
313 for (const auto &Chunk
: CCS
)
314 if (Chunk
.Kind
== CodeCompletionString::CK_ResultType
)
319 } // namespace clangd