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 | LLVM_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 | |