1 //===--- CodeCompletionStrings.cpp -------------------------------*- C++-*-===//
2 //
3 //                     The LLVM Compiler Infrastructure
4 //
5 // This file is distributed under the University of Illinois Open Source
6 // License. See LICENSE.TXT for details.
7 //
8 //===---------------------------------------------------------------------===//
9 
10 #include "CodeCompletionStrings.h"
11 #include "clang/AST/ASTContext.h"
12 #include "clang/AST/DeclObjC.h"
13 #include "clang/AST/RawCommentList.h"
14 #include "clang/Basic/SourceManager.h"
15 #include <utility>
16 
17 namespace clang {
18 namespace clangd {
19 
20 namespace {
21 
22 bool isInformativeQualifierChunk(CodeCompletionString::Chunk const &Chunk) {
23   return Chunk.Kind == CodeCompletionString::CK_Informative &&
24          StringRef(Chunk.Text).endswith("::");
25 }
26 
27 void processPlainTextChunks(const CodeCompletionString &CCS,
28                             std::string *LabelOut, std::string *InsertTextOut) {
29   std::string &Label = *LabelOut;
30   std::string &InsertText = *InsertTextOut;
31   for (const auto &Chunk : CCS) {
32     // Informative qualifier chunks only clutter completion results, skip
33     // them.
34     if (isInformativeQualifierChunk(Chunk))
35       continue;
36 
37     switch (Chunk.Kind) {
38     case CodeCompletionString::CK_ResultType:
39     case CodeCompletionString::CK_Optional:
40       break;
41     case CodeCompletionString::CK_TypedText:
42       InsertText += Chunk.Text;
43       Label += Chunk.Text;
44       break;
45     default:
46       Label += Chunk.Text;
47       break;
48     }
49   }
50 }
51 
52 void appendEscapeSnippet(const llvm::StringRef Text, std::string *Out) {
53   for (const auto Character : Text) {
54     if (Character == '$' || Character == '}' || Character == '\\')
55       Out->push_back('\\');
56     Out->push_back(Character);
57   }
58 }
59 
60 void processSnippetChunks(const CodeCompletionString &CCS,
61                           std::string *LabelOut, std::string *InsertTextOut) {
62   std::string &Label = *LabelOut;
63   std::string &InsertText = *InsertTextOut;
64 
65   unsigned ArgCount = 0;
66   for (const auto &Chunk : CCS) {
67     // Informative qualifier chunks only clutter completion results, skip
68     // them.
69     if (isInformativeQualifierChunk(Chunk))
70       continue;
71 
72     switch (Chunk.Kind) {
73     case CodeCompletionString::CK_TypedText:
74     case CodeCompletionString::CK_Text:
75       Label += Chunk.Text;
76       InsertText += Chunk.Text;
77       break;
78     case CodeCompletionString::CK_Optional:
79       // FIXME: Maybe add an option to allow presenting the optional chunks?
80       break;
81     case CodeCompletionString::CK_Placeholder:
82       ++ArgCount;
83       InsertText += "${" + std::to_string(ArgCount) + ':';
84       appendEscapeSnippet(Chunk.Text, &InsertText);
85       InsertText += '}';
86       Label += Chunk.Text;
87       break;
88     case CodeCompletionString::CK_Informative:
89       // For example, the word "const" for a const method, or the name of
90       // the base class for methods that are part of the base class.
91       Label += Chunk.Text;
92       // Don't put the informative chunks in the insertText.
93       break;
94     case CodeCompletionString::CK_ResultType:
95       // This is retrieved as detail.
96       break;
97     case CodeCompletionString::CK_CurrentParameter:
98       // This should never be present while collecting completion items,
99       // only while collecting overload candidates.
100       llvm_unreachable("Unexpected CK_CurrentParameter while collecting "
101                        "CompletionItems");
102       break;
103     case CodeCompletionString::CK_LeftParen:
104     case CodeCompletionString::CK_RightParen:
105     case CodeCompletionString::CK_LeftBracket:
106     case CodeCompletionString::CK_RightBracket:
107     case CodeCompletionString::CK_LeftBrace:
108     case CodeCompletionString::CK_RightBrace:
109     case CodeCompletionString::CK_LeftAngle:
110     case CodeCompletionString::CK_RightAngle:
111     case CodeCompletionString::CK_Comma:
112     case CodeCompletionString::CK_Colon:
113     case CodeCompletionString::CK_SemiColon:
114     case CodeCompletionString::CK_Equal:
115     case CodeCompletionString::CK_HorizontalSpace:
116       InsertText += Chunk.Text;
117       Label += Chunk.Text;
118       break;
119     case CodeCompletionString::CK_VerticalSpace:
120       InsertText += Chunk.Text;
121       // Don't even add a space to the label.
122       break;
123     }
124   }
125 }
126 
127 bool canRequestComment(const ASTContext &Ctx, const NamedDecl &D,
128                        bool CommentsFromHeaders) {
129   if (CommentsFromHeaders)
130     return true;
131   auto &SourceMgr = Ctx.getSourceManager();
132   // Accessing comments for decls from  invalid preamble can lead to crashes.
133   // So we only return comments from the main file when doing code completion.
134   // For indexing, we still read all the comments.
135   // FIXME: find a better fix, e.g. store file contents in the preamble or get
136   // doc comments from the index.
137   auto canRequestForDecl = [&](const NamedDecl &D) -> bool {
138     for (auto *Redecl : D.redecls()) {
139       auto Loc = SourceMgr.getSpellingLoc(Redecl->getLocation());
140       if (!SourceMgr.isWrittenInMainFile(Loc))
141         return false;
142     }
143     return true;
144   };
145   // First, check the decl itself.
146   if (!canRequestForDecl(D))
147     return false;
148   // Completion also returns comments for properties, corresponding to ObjC
149   // methods.
150   const ObjCMethodDecl *M = dyn_cast<ObjCMethodDecl>(&D);
151   const ObjCPropertyDecl *PDecl = M ? M->findPropertyDecl() : nullptr;
152   return !PDecl || canRequestForDecl(*PDecl);
153 }
154 } // namespace
155 
156 std::string getDocComment(const ASTContext &Ctx,
157                           const CodeCompletionResult &Result,
158                           bool CommentsFromHeaders) {
159   // FIXME: clang's completion also returns documentation for RK_Pattern if they
160   // contain a pattern for ObjC properties. Unfortunately, there is no API to
161   // get this declaration, so we don't show documentation in that case.
162   if (Result.Kind != CodeCompletionResult::RK_Declaration)
163     return "";
164   auto *Decl = Result.getDeclaration();
165   if (!Decl || !canRequestComment(Ctx, *Decl, CommentsFromHeaders))
166     return "";
167   const RawComment *RC = getCompletionComment(Ctx, Decl);
168   if (!RC)
169     return "";
170   return RC->getFormattedText(Ctx.getSourceManager(), Ctx.getDiagnostics());
171 }
172 
173 std::string
174 getParameterDocComment(const ASTContext &Ctx,
175                        const CodeCompleteConsumer::OverloadCandidate &Result,
176                        unsigned ArgIndex, bool CommentsFromHeaders) {
177   auto *Func = Result.getFunction();
178   if (!Func || !canRequestComment(Ctx, *Func, CommentsFromHeaders))
179     return "";
180   const RawComment *RC = getParameterComment(Ctx, Result, ArgIndex);
181   if (!RC)
182     return "";
183   return RC->getFormattedText(Ctx.getSourceManager(), Ctx.getDiagnostics());
184 }
185 
186 void getLabelAndInsertText(const CodeCompletionString &CCS, std::string *Label,
187                            std::string *InsertText, bool EnableSnippets) {
188   return EnableSnippets ? processSnippetChunks(CCS, Label, InsertText)
189                         : processPlainTextChunks(CCS, Label, InsertText);
190 }
191 
192 std::string formatDocumentation(const CodeCompletionString &CCS,
193                                 llvm::StringRef DocComment) {
194   // Things like __attribute__((nonnull(1,3))) and [[noreturn]]. Present this
195   // information in the documentation field.
196   std::string Result;
197   const unsigned AnnotationCount = CCS.getAnnotationCount();
198   if (AnnotationCount > 0) {
199     Result += "Annotation";
200     if (AnnotationCount == 1) {
201       Result += ": ";
202     } else /* AnnotationCount > 1 */ {
203       Result += "s: ";
204     }
205     for (unsigned I = 0; I < AnnotationCount; ++I) {
206       Result += CCS.getAnnotation(I);
207       Result.push_back(I == AnnotationCount - 1 ? '\n' : ' ');
208     }
209   }
210   // Add brief documentation (if there is any).
211   if (!DocComment.empty()) {
212     if (!Result.empty()) {
213       // This means we previously added annotations. Add an extra newline
214       // character to make the annotations stand out.
215       Result.push_back('\n');
216     }
217     Result += DocComment;
218   }
219   return Result;
220 }
221 
222 std::string getDetail(const CodeCompletionString &CCS) {
223   for (const auto &Chunk : CCS) {
224     // Informative qualifier chunks only clutter completion results, skip
225     // them.
226     switch (Chunk.Kind) {
227     case CodeCompletionString::CK_ResultType:
228       return Chunk.Text;
229     default:
230       break;
231     }
232   }
233   return "";
234 }
235 
236 std::string getFilterText(const CodeCompletionString &CCS) {
237   for (const auto &Chunk : CCS) {
238     switch (Chunk.Kind) {
239     case CodeCompletionString::CK_TypedText:
240       // There's always exactly one CK_TypedText chunk.
241       return Chunk.Text;
242     default:
243       break;
244     }
245   }
246   return "";
247 }
248 
249 } // namespace clangd
250 } // namespace clang
251