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 
155 bool LooksLikeDocComment(llvm::StringRef CommentText) {
156   // We don't report comments that only contain "special" chars.
157   // This avoids reporting various delimiters, like:
158   //   =================
159   //   -----------------
160   //   *****************
161   return CommentText.find_first_not_of("/*-= \t\r\n") != llvm::StringRef::npos;
162 }
163 
164 } // namespace
165 
166 std::string getDocComment(const ASTContext &Ctx,
167                           const CodeCompletionResult &Result,
168                           bool CommentsFromHeaders) {
169   // FIXME: clang's completion also returns documentation for RK_Pattern if they
170   // contain a pattern for ObjC properties. Unfortunately, there is no API to
171   // get this declaration, so we don't show documentation in that case.
172   if (Result.Kind != CodeCompletionResult::RK_Declaration)
173     return "";
174   auto *Decl = Result.getDeclaration();
175   if (!Decl || !canRequestComment(Ctx, *Decl, CommentsFromHeaders))
176     return "";
177   const RawComment *RC = getCompletionComment(Ctx, Decl);
178   if (!RC)
179     return "";
180   std::string Doc = RC->getFormattedText(Ctx.getSourceManager(), Ctx.getDiagnostics());
181   if (!LooksLikeDocComment(Doc))
182     return "";
183   return Doc;
184 }
185 
186 std::string
187 getParameterDocComment(const ASTContext &Ctx,
188                        const CodeCompleteConsumer::OverloadCandidate &Result,
189                        unsigned ArgIndex, bool CommentsFromHeaders) {
190   auto *Func = Result.getFunction();
191   if (!Func || !canRequestComment(Ctx, *Func, CommentsFromHeaders))
192     return "";
193   const RawComment *RC = getParameterComment(Ctx, Result, ArgIndex);
194   if (!RC)
195     return "";
196   std::string Doc = RC->getFormattedText(Ctx.getSourceManager(), Ctx.getDiagnostics());
197   if (!LooksLikeDocComment(Doc))
198     return "";
199   return Doc;
200 }
201 
202 void getLabelAndInsertText(const CodeCompletionString &CCS, std::string *Label,
203                            std::string *InsertText, bool EnableSnippets) {
204   return EnableSnippets ? processSnippetChunks(CCS, Label, InsertText)
205                         : processPlainTextChunks(CCS, Label, InsertText);
206 }
207 
208 std::string formatDocumentation(const CodeCompletionString &CCS,
209                                 llvm::StringRef DocComment) {
210   // Things like __attribute__((nonnull(1,3))) and [[noreturn]]. Present this
211   // information in the documentation field.
212   std::string Result;
213   const unsigned AnnotationCount = CCS.getAnnotationCount();
214   if (AnnotationCount > 0) {
215     Result += "Annotation";
216     if (AnnotationCount == 1) {
217       Result += ": ";
218     } else /* AnnotationCount > 1 */ {
219       Result += "s: ";
220     }
221     for (unsigned I = 0; I < AnnotationCount; ++I) {
222       Result += CCS.getAnnotation(I);
223       Result.push_back(I == AnnotationCount - 1 ? '\n' : ' ');
224     }
225   }
226   // Add brief documentation (if there is any).
227   if (!DocComment.empty()) {
228     if (!Result.empty()) {
229       // This means we previously added annotations. Add an extra newline
230       // character to make the annotations stand out.
231       Result.push_back('\n');
232     }
233     Result += DocComment;
234   }
235   return Result;
236 }
237 
238 std::string getDetail(const CodeCompletionString &CCS) {
239   for (const auto &Chunk : CCS) {
240     // Informative qualifier chunks only clutter completion results, skip
241     // them.
242     switch (Chunk.Kind) {
243     case CodeCompletionString::CK_ResultType:
244       return Chunk.Text;
245     default:
246       break;
247     }
248   }
249   return "";
250 }
251 
252 std::string getFilterText(const CodeCompletionString &CCS) {
253   for (const auto &Chunk : CCS) {
254     switch (Chunk.Kind) {
255     case CodeCompletionString::CK_TypedText:
256       // There's always exactly one CK_TypedText chunk.
257       return Chunk.Text;
258     default:
259       break;
260     }
261   }
262   return "";
263 }
264 
265 } // namespace clangd
266 } // namespace clang
267