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 | |
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 (llvm::StringRef ) { |
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. |
62 | bool 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 | |
82 | std::string (const ASTContext &Ctx, |
83 | const CodeCompletionResult &Result, |
84 | bool ) { |
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 | |
94 | std::string (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 | |
118 | void 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 | |
269 | std::string formatDocumentation(const CodeCompletionString &CCS, |
270 | llvm::StringRef ) { |
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 | |
299 | std::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 | |