163696e14SEric Liu //===--- CodeCompletionStrings.cpp -------------------------------*- C++-*-===//
263696e14SEric Liu //
32946cd70SChandler Carruth // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
42946cd70SChandler Carruth // See https://llvm.org/LICENSE.txt for license information.
52946cd70SChandler Carruth // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
663696e14SEric Liu //
78e35f1e7SKirill Bobyrev //===----------------------------------------------------------------------===//
863696e14SEric Liu
963696e14SEric Liu #include "CodeCompletionStrings.h"
10138adb09SYounan Zhang #include "clang-c/Index.h"
1143714504SIlya Biryukov #include "clang/AST/ASTContext.h"
1243714504SIlya Biryukov #include "clang/AST/RawCommentList.h"
13be0eb8f4SIlya Biryukov #include "clang/Basic/SourceManager.h"
148534675cSIlya Biryukov #include "clang/Sema/CodeCompleteConsumer.h"
1523ef8bf9SYounan Zhang #include "llvm/Support/Compiler.h"
16216af81cSSam McCall #include "llvm/Support/JSON.h"
178534675cSIlya Biryukov #include <limits>
1863696e14SEric Liu #include <utility>
1963696e14SEric Liu
2063696e14SEric Liu namespace clang {
2163696e14SEric Liu namespace clangd {
2263696e14SEric Liu namespace {
2363696e14SEric Liu
isInformativeQualifierChunk(CodeCompletionString::Chunk const & Chunk)2463696e14SEric Liu bool isInformativeQualifierChunk(CodeCompletionString::Chunk const &Chunk) {
2563696e14SEric Liu return Chunk.Kind == CodeCompletionString::CK_Informative &&
26d5953e3eSKazu Hirata llvm::StringRef(Chunk.Text).ends_with("::");
2763696e14SEric Liu }
2863696e14SEric Liu
appendEscapeSnippet(const llvm::StringRef Text,std::string * Out)29f2001aa7SIlya Biryukov void appendEscapeSnippet(const llvm::StringRef Text, std::string *Out) {
3063696e14SEric Liu for (const auto Character : Text) {
3163696e14SEric Liu if (Character == '$' || Character == '}' || Character == '\\')
3263696e14SEric Liu Out->push_back('\\');
3363696e14SEric Liu Out->push_back(Character);
3463696e14SEric Liu }
3563696e14SEric Liu }
3663696e14SEric Liu
appendOptionalChunk(const CodeCompletionString & CCS,std::string * Out)370e8dd4a8SSam McCall void appendOptionalChunk(const CodeCompletionString &CCS, std::string *Out) {
380e8dd4a8SSam McCall for (const CodeCompletionString::Chunk &C : CCS) {
390e8dd4a8SSam McCall switch (C.Kind) {
400e8dd4a8SSam McCall case CodeCompletionString::CK_Optional:
410e8dd4a8SSam McCall assert(C.Optional &&
420e8dd4a8SSam McCall "Expected the optional code completion string to be non-null.");
430e8dd4a8SSam McCall appendOptionalChunk(*C.Optional, Out);
440e8dd4a8SSam McCall break;
450e8dd4a8SSam McCall default:
460e8dd4a8SSam McCall *Out += C.Text;
470e8dd4a8SSam McCall break;
480e8dd4a8SSam McCall }
490e8dd4a8SSam McCall }
500e8dd4a8SSam McCall }
510e8dd4a8SSam McCall
looksLikeDocComment(llvm::StringRef CommentText)52f2001aa7SIlya Biryukov bool looksLikeDocComment(llvm::StringRef CommentText) {
5389fcf6b2SIlya Biryukov // We don't report comments that only contain "special" chars.
5489fcf6b2SIlya Biryukov // This avoids reporting various delimiters, like:
5589fcf6b2SIlya Biryukov // =================
5689fcf6b2SIlya Biryukov // -----------------
5789fcf6b2SIlya Biryukov // *****************
58f2001aa7SIlya Biryukov return CommentText.find_first_not_of("/*-= \t\r\n") != llvm::StringRef::npos;
5989fcf6b2SIlya Biryukov }
6089fcf6b2SIlya Biryukov
61138adb09SYounan Zhang // Determine whether the completion string should be patched
62138adb09SYounan Zhang // to replace the last placeholder with $0.
shouldPatchPlaceholder0(CodeCompletionResult::ResultKind ResultKind,CXCursorKind CursorKind)63138adb09SYounan Zhang bool shouldPatchPlaceholder0(CodeCompletionResult::ResultKind ResultKind,
64138adb09SYounan Zhang CXCursorKind CursorKind) {
65138adb09SYounan Zhang bool CompletingPattern = ResultKind == CodeCompletionResult::RK_Pattern;
66138adb09SYounan Zhang
67138adb09SYounan Zhang if (!CompletingPattern)
68138adb09SYounan Zhang return false;
69138adb09SYounan Zhang
70138adb09SYounan Zhang // If the result kind of CodeCompletionResult(CCR) is `RK_Pattern`, it doesn't
71138adb09SYounan Zhang // always mean we're completing a chunk of statements. Constructors defined
72138adb09SYounan Zhang // in base class, for example, are considered as a type of pattern, with the
73138adb09SYounan Zhang // cursor type set to CXCursor_Constructor.
74138adb09SYounan Zhang if (CursorKind == CXCursorKind::CXCursor_Constructor ||
75138adb09SYounan Zhang CursorKind == CXCursorKind::CXCursor_Destructor)
76138adb09SYounan Zhang return false;
77138adb09SYounan Zhang
78138adb09SYounan Zhang return true;
79138adb09SYounan Zhang }
80138adb09SYounan Zhang
8163696e14SEric Liu } // namespace
8263696e14SEric Liu
getDocComment(const ASTContext & Ctx,const CodeCompletionResult & Result,bool CommentsFromHeaders)8343714504SIlya Biryukov std::string getDocComment(const ASTContext &Ctx,
84be0eb8f4SIlya Biryukov const CodeCompletionResult &Result,
85be0eb8f4SIlya Biryukov bool CommentsFromHeaders) {
8643714504SIlya Biryukov // FIXME: clang's completion also returns documentation for RK_Pattern if they
8743714504SIlya Biryukov // contain a pattern for ObjC properties. Unfortunately, there is no API to
8843714504SIlya Biryukov // get this declaration, so we don't show documentation in that case.
8943714504SIlya Biryukov if (Result.Kind != CodeCompletionResult::RK_Declaration)
9043714504SIlya Biryukov return "";
915f4a3513SIlya Biryukov return Result.getDeclaration() ? getDeclComment(Ctx, *Result.getDeclaration())
925f4a3513SIlya Biryukov : "";
935f4a3513SIlya Biryukov }
945f4a3513SIlya Biryukov
getDeclComment(const ASTContext & Ctx,const NamedDecl & Decl)955f4a3513SIlya Biryukov std::string getDeclComment(const ASTContext &Ctx, const NamedDecl &Decl) {
96c008af64SSam McCall if (isa<NamespaceDecl>(Decl)) {
97da8dd8b8SIlya Biryukov // Namespaces often have too many redecls for any particular redecl comment
98da8dd8b8SIlya Biryukov // to be useful. Moreover, we often confuse file headers or generated
99da8dd8b8SIlya Biryukov // comments with namespace comments. Therefore we choose to just ignore
100da8dd8b8SIlya Biryukov // the comments for namespaces.
10143714504SIlya Biryukov return "";
102da8dd8b8SIlya Biryukov }
1035f4a3513SIlya Biryukov const RawComment *RC = getCompletionComment(Ctx, &Decl);
10443714504SIlya Biryukov if (!RC)
10543714504SIlya Biryukov return "";
1066f33b330SIlya Biryukov // Sanity check that the comment does not come from the PCH. We choose to not
1076f33b330SIlya Biryukov // write them into PCH, because they are racy and slow to load.
10843465bf3SStephen Kelly assert(!Ctx.getSourceManager().isLoadedSourceLocation(RC->getBeginLoc()));
10922fa465aSIlya Biryukov std::string Doc =
11022fa465aSIlya Biryukov RC->getFormattedText(Ctx.getSourceManager(), Ctx.getDiagnostics());
111216af81cSSam McCall if (!looksLikeDocComment(Doc))
112216af81cSSam McCall return "";
113216af81cSSam McCall // Clang requires source to be UTF-8, but doesn't enforce this in comments.
114216af81cSSam McCall if (!llvm::json::isUTF8(Doc))
115216af81cSSam McCall Doc = llvm::json::fixUTF8(Doc);
116216af81cSSam McCall return Doc;
11743714504SIlya Biryukov }
11843714504SIlya Biryukov
getSignature(const CodeCompletionString & CCS,std::string * Signature,std::string * Snippet,CodeCompletionResult::ResultKind ResultKind,CXCursorKind CursorKind,bool IncludeFunctionArguments,std::string * RequiredQualifiers)119a68951e3SSam McCall void getSignature(const CodeCompletionString &CCS, std::string *Signature,
120138adb09SYounan Zhang std::string *Snippet,
121138adb09SYounan Zhang CodeCompletionResult::ResultKind ResultKind,
12223ef8bf9SYounan Zhang CXCursorKind CursorKind, bool IncludeFunctionArguments,
12323ef8bf9SYounan Zhang std::string *RequiredQualifiers) {
124138adb09SYounan Zhang // Placeholder with this index will be $0 to mark final cursor position.
1258534675cSIlya Biryukov // Usually we do not add $0, so the cursor is placed at end of completed text.
1268534675cSIlya Biryukov unsigned CursorSnippetArg = std::numeric_limits<unsigned>::max();
127138adb09SYounan Zhang
128138adb09SYounan Zhang // If the snippet contains a group of statements, we replace the
129138adb09SYounan Zhang // last placeholder with $0 to leave the cursor there, e.g.
1308534675cSIlya Biryukov // namespace ${1:name} {
1318534675cSIlya Biryukov // ${0:decls}
1328534675cSIlya Biryukov // }
133138adb09SYounan Zhang // We try to identify such cases using the ResultKind and CursorKind.
134138adb09SYounan Zhang if (shouldPatchPlaceholder0(ResultKind, CursorKind)) {
1358534675cSIlya Biryukov CursorSnippetArg =
1368534675cSIlya Biryukov llvm::count_if(CCS, [](const CodeCompletionString::Chunk &C) {
1378534675cSIlya Biryukov return C.Kind == CodeCompletionString::CK_Placeholder;
1388534675cSIlya Biryukov });
1398534675cSIlya Biryukov }
1408534675cSIlya Biryukov unsigned SnippetArg = 0;
14139e86fa5SSam McCall bool HadObjCArguments = false;
1422a030e68SDavid Goldman bool HadInformativeChunks = false;
14323ef8bf9SYounan Zhang
14423ef8bf9SYounan Zhang std::optional<unsigned> TruncateSnippetAt;
145a68951e3SSam McCall for (const auto &Chunk : CCS) {
146a68951e3SSam McCall // Informative qualifier chunks only clutter completion results, skip
147a68951e3SSam McCall // them.
148a68951e3SSam McCall if (isInformativeQualifierChunk(Chunk))
149a68951e3SSam McCall continue;
150a68951e3SSam McCall
151a68951e3SSam McCall switch (Chunk.Kind) {
152a68951e3SSam McCall case CodeCompletionString::CK_TypedText:
153a68951e3SSam McCall // The typed-text chunk is the actual name. We don't record this chunk.
15439e86fa5SSam McCall // C++:
155a68951e3SSam McCall // In general our string looks like <qualifiers><name><signature>.
156a68951e3SSam McCall // So once we see the name, any text we recorded so far should be
157a68951e3SSam McCall // reclassified as qualifiers.
15839e86fa5SSam McCall //
15939e86fa5SSam McCall // Objective-C:
1602a030e68SDavid Goldman // Objective-C methods expressions may have multiple typed-text chunks,
1612a030e68SDavid Goldman // so we must treat them carefully. For Objective-C methods, all
1622a030e68SDavid Goldman // typed-text and informative chunks will end in ':' (unless there are
1632a030e68SDavid Goldman // no arguments, in which case we can safely treat them as C++).
1642a030e68SDavid Goldman //
1652a030e68SDavid Goldman // Completing a method declaration itself (not a method expression) is
1662a030e68SDavid Goldman // similar except that we use the `RequiredQualifiers` to store the
1672a030e68SDavid Goldman // text before the selector, e.g. `- (void)`.
168d5953e3eSKazu Hirata if (!llvm::StringRef(Chunk.Text).ends_with(":")) { // Treat as C++.
169a68951e3SSam McCall if (RequiredQualifiers)
170a68951e3SSam McCall *RequiredQualifiers = std::move(*Signature);
171a68951e3SSam McCall Signature->clear();
172a68951e3SSam McCall Snippet->clear();
17339e86fa5SSam McCall } else { // Objective-C method with args.
17439e86fa5SSam McCall // If this is the first TypedText to the Objective-C method, discard any
17539e86fa5SSam McCall // text that we've previously seen (such as previous parameter selector,
17639e86fa5SSam McCall // which will be marked as Informative text).
17739e86fa5SSam McCall //
17839e86fa5SSam McCall // TODO: Make previous parameters part of the signature for Objective-C
17939e86fa5SSam McCall // methods.
18039e86fa5SSam McCall if (!HadObjCArguments) {
18139e86fa5SSam McCall HadObjCArguments = true;
1822a030e68SDavid Goldman // If we have no previous informative chunks (informative selector
1832a030e68SDavid Goldman // fragments in practice), we treat any previous chunks as
1842a030e68SDavid Goldman // `RequiredQualifiers` so they will be added as a prefix during the
1852a030e68SDavid Goldman // completion.
1862a030e68SDavid Goldman //
1872a030e68SDavid Goldman // e.g. to complete `- (void)doSomething:(id)argument`:
1882a030e68SDavid Goldman // - Completion name: `doSomething:`
1892a030e68SDavid Goldman // - RequiredQualifiers: `- (void)`
1902a030e68SDavid Goldman // - Snippet/Signature suffix: `(id)argument`
1912a030e68SDavid Goldman //
1922a030e68SDavid Goldman // This differs from the case when we're completing a method
1932a030e68SDavid Goldman // expression with a previous informative selector fragment.
1942a030e68SDavid Goldman //
1952a030e68SDavid Goldman // e.g. to complete `[self doSomething:nil ^somethingElse:(id)]`:
1962a030e68SDavid Goldman // - Previous Informative Chunk: `doSomething:`
1972a030e68SDavid Goldman // - Completion name: `somethingElse:`
1982a030e68SDavid Goldman // - Snippet/Signature suffix: `(id)`
1992a030e68SDavid Goldman if (!HadInformativeChunks) {
2002a030e68SDavid Goldman if (RequiredQualifiers)
2012a030e68SDavid Goldman *RequiredQualifiers = std::move(*Signature);
2022a030e68SDavid Goldman Snippet->clear();
2032a030e68SDavid Goldman }
20439e86fa5SSam McCall Signature->clear();
20539e86fa5SSam McCall } else { // Subsequent argument, considered part of snippet/signature.
20639e86fa5SSam McCall *Signature += Chunk.Text;
20739e86fa5SSam McCall *Snippet += Chunk.Text;
20839e86fa5SSam McCall }
20939e86fa5SSam McCall }
210a68951e3SSam McCall break;
211a68951e3SSam McCall case CodeCompletionString::CK_Text:
212a68951e3SSam McCall *Signature += Chunk.Text;
213a68951e3SSam McCall *Snippet += Chunk.Text;
214a68951e3SSam McCall break;
215a68951e3SSam McCall case CodeCompletionString::CK_Optional:
2160e8dd4a8SSam McCall assert(Chunk.Optional);
2170e8dd4a8SSam McCall // No need to create placeholders for default arguments in Snippet.
2180e8dd4a8SSam McCall appendOptionalChunk(*Chunk.Optional, Signature);
219a68951e3SSam McCall break;
220a68951e3SSam McCall case CodeCompletionString::CK_Placeholder:
221a68951e3SSam McCall *Signature += Chunk.Text;
2228534675cSIlya Biryukov ++SnippetArg;
2232eba08fdSNathan Ridge if (SnippetArg == CursorSnippetArg) {
2242eba08fdSNathan Ridge // We'd like to make $0 a placeholder too, but vscode does not support
2252eba08fdSNathan Ridge // this (https://github.com/microsoft/vscode/issues/152837).
2262eba08fdSNathan Ridge *Snippet += "$0";
2272eba08fdSNathan Ridge } else {
2282eba08fdSNathan Ridge *Snippet += "${" + std::to_string(SnippetArg) + ':';
229a68951e3SSam McCall appendEscapeSnippet(Chunk.Text, Snippet);
230a68951e3SSam McCall *Snippet += '}';
2312eba08fdSNathan Ridge }
232a68951e3SSam McCall break;
233a68951e3SSam McCall case CodeCompletionString::CK_Informative:
2342a030e68SDavid Goldman HadInformativeChunks = true;
235a68951e3SSam McCall // For example, the word "const" for a const method, or the name of
236a68951e3SSam McCall // the base class for methods that are part of the base class.
237a68951e3SSam McCall *Signature += Chunk.Text;
238a68951e3SSam McCall // Don't put the informative chunks in the snippet.
239a68951e3SSam McCall break;
240a68951e3SSam McCall case CodeCompletionString::CK_ResultType:
241a68951e3SSam McCall // This is not part of the signature.
242a68951e3SSam McCall break;
243a68951e3SSam McCall case CodeCompletionString::CK_CurrentParameter:
244a68951e3SSam McCall // This should never be present while collecting completion items,
245a68951e3SSam McCall // only while collecting overload candidates.
246a68951e3SSam McCall llvm_unreachable("Unexpected CK_CurrentParameter while collecting "
247a68951e3SSam McCall "CompletionItems");
248a68951e3SSam McCall break;
249a68951e3SSam McCall case CodeCompletionString::CK_LeftParen:
25023ef8bf9SYounan Zhang // We're assuming that a LeftParen in a declaration starts a function
25123ef8bf9SYounan Zhang // call, and arguments following the parenthesis could be discarded if
25223ef8bf9SYounan Zhang // IncludeFunctionArguments is false.
25323ef8bf9SYounan Zhang if (!IncludeFunctionArguments &&
25423ef8bf9SYounan Zhang ResultKind == CodeCompletionResult::RK_Declaration)
25523ef8bf9SYounan Zhang TruncateSnippetAt.emplace(Snippet->size());
256*5a12f286SFangrui Song [[fallthrough]];
257a68951e3SSam McCall case CodeCompletionString::CK_RightParen:
258a68951e3SSam McCall case CodeCompletionString::CK_LeftBracket:
259a68951e3SSam McCall case CodeCompletionString::CK_RightBracket:
260a68951e3SSam McCall case CodeCompletionString::CK_LeftBrace:
261a68951e3SSam McCall case CodeCompletionString::CK_RightBrace:
262a68951e3SSam McCall case CodeCompletionString::CK_LeftAngle:
263a68951e3SSam McCall case CodeCompletionString::CK_RightAngle:
264a68951e3SSam McCall case CodeCompletionString::CK_Comma:
265a68951e3SSam McCall case CodeCompletionString::CK_Colon:
266a68951e3SSam McCall case CodeCompletionString::CK_SemiColon:
267a68951e3SSam McCall case CodeCompletionString::CK_Equal:
268a68951e3SSam McCall case CodeCompletionString::CK_HorizontalSpace:
269a68951e3SSam McCall *Signature += Chunk.Text;
270a68951e3SSam McCall *Snippet += Chunk.Text;
271a68951e3SSam McCall break;
272a68951e3SSam McCall case CodeCompletionString::CK_VerticalSpace:
273a68951e3SSam McCall *Snippet += Chunk.Text;
274a68951e3SSam McCall // Don't even add a space to the signature.
275a68951e3SSam McCall break;
276a68951e3SSam McCall }
277a68951e3SSam McCall }
27823ef8bf9SYounan Zhang if (TruncateSnippetAt)
27923ef8bf9SYounan Zhang *Snippet = Snippet->substr(0, *TruncateSnippetAt);
28063696e14SEric Liu }
28163696e14SEric Liu
formatDocumentation(const CodeCompletionString & CCS,llvm::StringRef DocComment)28243714504SIlya Biryukov std::string formatDocumentation(const CodeCompletionString &CCS,
283f2001aa7SIlya Biryukov llvm::StringRef DocComment) {
28463696e14SEric Liu // Things like __attribute__((nonnull(1,3))) and [[noreturn]]. Present this
28563696e14SEric Liu // information in the documentation field.
28663696e14SEric Liu std::string Result;
28763696e14SEric Liu const unsigned AnnotationCount = CCS.getAnnotationCount();
28863696e14SEric Liu if (AnnotationCount > 0) {
28963696e14SEric Liu Result += "Annotation";
29063696e14SEric Liu if (AnnotationCount == 1) {
29163696e14SEric Liu Result += ": ";
29263696e14SEric Liu } else /* AnnotationCount > 1 */ {
29363696e14SEric Liu Result += "s: ";
29463696e14SEric Liu }
29563696e14SEric Liu for (unsigned I = 0; I < AnnotationCount; ++I) {
29663696e14SEric Liu Result += CCS.getAnnotation(I);
29763696e14SEric Liu Result.push_back(I == AnnotationCount - 1 ? '\n' : ' ');
29863696e14SEric Liu }
29963696e14SEric Liu }
30063696e14SEric Liu // Add brief documentation (if there is any).
30143714504SIlya Biryukov if (!DocComment.empty()) {
30263696e14SEric Liu if (!Result.empty()) {
30363696e14SEric Liu // This means we previously added annotations. Add an extra newline
30463696e14SEric Liu // character to make the annotations stand out.
30563696e14SEric Liu Result.push_back('\n');
30663696e14SEric Liu }
30743714504SIlya Biryukov Result += DocComment;
30863696e14SEric Liu }
30963696e14SEric Liu return Result;
31063696e14SEric Liu }
31163696e14SEric Liu
getReturnType(const CodeCompletionString & CCS)312a68951e3SSam McCall std::string getReturnType(const CodeCompletionString &CCS) {
313a68951e3SSam McCall for (const auto &Chunk : CCS)
314a68951e3SSam McCall if (Chunk.Kind == CodeCompletionString::CK_ResultType)
31563696e14SEric Liu return Chunk.Text;
31663696e14SEric Liu return "";
31763696e14SEric Liu }
31863696e14SEric Liu
31963696e14SEric Liu } // namespace clangd
32063696e14SEric Liu } // namespace clang
321