1 | //===-- CodeCompletionStringsTests.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 "TestTU.h" |
11 | #include "clang/Sema/CodeCompleteConsumer.h" |
12 | #include "gmock/gmock.h" |
13 | #include "gtest/gtest.h" |
14 | |
15 | namespace clang { |
16 | namespace clangd { |
17 | namespace { |
18 | |
19 | class CompletionStringTest : public ::testing::Test { |
20 | public: |
21 | CompletionStringTest() |
22 | : Allocator(std::make_shared<clang::GlobalCodeCompletionAllocator>()), |
23 | CCTUInfo(Allocator), Builder(*Allocator, CCTUInfo) {} |
24 | |
25 | protected: |
26 | void computeSignature(const CodeCompletionString &CCS, |
27 | CodeCompletionResult::ResultKind ResultKind = |
28 | CodeCompletionResult::ResultKind::RK_Declaration, |
29 | bool IncludeFunctionArguments = true) { |
30 | Signature.clear(); |
31 | Snippet.clear(); |
32 | getSignature(CCS, Signature: &Signature, Snippet: &Snippet, ResultKind, |
33 | /*CursorKind=*/CXCursorKind::CXCursor_NotImplemented, |
34 | /*IncludeFunctionArguments=*/IncludeFunctionArguments, |
35 | /*RequiredQualifiers=*/nullptr); |
36 | } |
37 | |
38 | std::shared_ptr<clang::GlobalCodeCompletionAllocator> Allocator; |
39 | CodeCompletionTUInfo CCTUInfo; |
40 | CodeCompletionBuilder Builder; |
41 | std::string Signature; |
42 | std::string Snippet; |
43 | }; |
44 | |
45 | TEST_F(CompletionStringTest, ReturnType) { |
46 | Builder.AddResultTypeChunk(ResultType: "result" ); |
47 | Builder.AddResultTypeChunk(ResultType: "redundant result no no" ); |
48 | EXPECT_EQ(getReturnType(*Builder.TakeString()), "result" ); |
49 | } |
50 | |
51 | TEST_F(CompletionStringTest, Documentation) { |
52 | Builder.addBriefComment(Comment: "This is ignored" ); |
53 | EXPECT_EQ(formatDocumentation(*Builder.TakeString(), "Is this brief?" ), |
54 | "Is this brief?" ); |
55 | } |
56 | |
57 | TEST_F(CompletionStringTest, DocumentationWithAnnotation) { |
58 | Builder.addBriefComment(Comment: "This is ignored" ); |
59 | Builder.AddAnnotation(A: "Ano" ); |
60 | EXPECT_EQ(formatDocumentation(*Builder.TakeString(), "Is this brief?" ), |
61 | "Annotation: Ano\n\nIs this brief?" ); |
62 | } |
63 | |
64 | TEST_F(CompletionStringTest, GetDeclCommentBadUTF8) { |
65 | // <ff> is not a valid byte here, should be replaced by encoded <U+FFFD>. |
66 | auto TU = TestTU::withCode(Code: "/*x\xffy*/ struct X;" ); |
67 | auto AST = TU.build(); |
68 | EXPECT_EQ("x\xef\xbf\xbdy" , |
69 | getDeclComment(AST.getASTContext(), findDecl(AST, "X" ))); |
70 | } |
71 | |
72 | TEST_F(CompletionStringTest, MultipleAnnotations) { |
73 | Builder.AddAnnotation(A: "Ano1" ); |
74 | Builder.AddAnnotation(A: "Ano2" ); |
75 | Builder.AddAnnotation(A: "Ano3" ); |
76 | |
77 | EXPECT_EQ(formatDocumentation(*Builder.TakeString(), "" ), |
78 | "Annotations: Ano1 Ano2 Ano3\n" ); |
79 | } |
80 | |
81 | TEST_F(CompletionStringTest, EmptySignature) { |
82 | Builder.AddTypedTextChunk(Text: "X" ); |
83 | Builder.AddResultTypeChunk(ResultType: "result no no" ); |
84 | computeSignature(CCS: *Builder.TakeString()); |
85 | EXPECT_EQ(Signature, "" ); |
86 | EXPECT_EQ(Snippet, "" ); |
87 | } |
88 | |
89 | TEST_F(CompletionStringTest, Function) { |
90 | Builder.AddResultTypeChunk(ResultType: "result no no" ); |
91 | Builder.addBriefComment(Comment: "This comment is ignored" ); |
92 | Builder.AddTypedTextChunk(Text: "Foo" ); |
93 | Builder.AddChunk(CK: CodeCompletionString::CK_LeftParen); |
94 | Builder.AddPlaceholderChunk(Placeholder: "p1" ); |
95 | Builder.AddChunk(CK: CodeCompletionString::CK_Comma); |
96 | Builder.AddPlaceholderChunk(Placeholder: "p2" ); |
97 | Builder.AddChunk(CK: CodeCompletionString::CK_RightParen); |
98 | |
99 | auto *CCS = Builder.TakeString(); |
100 | computeSignature(CCS: *CCS); |
101 | EXPECT_EQ(Signature, "(p1, p2)" ); |
102 | EXPECT_EQ(Snippet, "(${1:p1}, ${2:p2})" ); |
103 | EXPECT_EQ(formatDocumentation(*CCS, "Foo's comment" ), "Foo's comment" ); |
104 | } |
105 | |
106 | TEST_F(CompletionStringTest, FunctionWithDefaultParams) { |
107 | // return_type foo(p1, p2 = 0, p3 = 0) |
108 | Builder.AddChunk(CK: CodeCompletionString::CK_Comma); |
109 | Builder.AddTypedTextChunk(Text: "p3 = 0" ); |
110 | auto *DefaultParam2 = Builder.TakeString(); |
111 | |
112 | Builder.AddChunk(CK: CodeCompletionString::CK_Comma); |
113 | Builder.AddTypedTextChunk(Text: "p2 = 0" ); |
114 | Builder.AddOptionalChunk(Optional: DefaultParam2); |
115 | auto *DefaultParam1 = Builder.TakeString(); |
116 | |
117 | Builder.AddResultTypeChunk(ResultType: "return_type" ); |
118 | Builder.AddTypedTextChunk(Text: "Foo" ); |
119 | Builder.AddChunk(CK: CodeCompletionString::CK_LeftParen); |
120 | Builder.AddPlaceholderChunk(Placeholder: "p1" ); |
121 | Builder.AddOptionalChunk(Optional: DefaultParam1); |
122 | Builder.AddChunk(CK: CodeCompletionString::CK_RightParen); |
123 | |
124 | auto *CCS = Builder.TakeString(); |
125 | computeSignature(CCS: *CCS); |
126 | EXPECT_EQ(Signature, "(p1, p2 = 0, p3 = 0)" ); |
127 | EXPECT_EQ(Snippet, "(${1:p1})" ); |
128 | } |
129 | |
130 | TEST_F(CompletionStringTest, EscapeSnippet) { |
131 | Builder.AddTypedTextChunk(Text: "Foo" ); |
132 | Builder.AddChunk(CK: CodeCompletionString::CK_LeftParen); |
133 | Builder.AddPlaceholderChunk(Placeholder: "$p}1\\" ); |
134 | Builder.AddChunk(CK: CodeCompletionString::CK_RightParen); |
135 | |
136 | computeSignature(CCS: *Builder.TakeString()); |
137 | EXPECT_EQ(Signature, "($p}1\\)" ); |
138 | EXPECT_EQ(Snippet, "(${1:\\$p\\}1\\\\})" ); |
139 | } |
140 | |
141 | TEST_F(CompletionStringTest, SnippetsInPatterns) { |
142 | auto MakeCCS = [this]() -> const CodeCompletionString & { |
143 | CodeCompletionBuilder Builder(*Allocator, CCTUInfo); |
144 | Builder.AddTypedTextChunk(Text: "namespace" ); |
145 | Builder.AddChunk(CK: CodeCompletionString::CK_HorizontalSpace); |
146 | Builder.AddPlaceholderChunk(Placeholder: "name" ); |
147 | Builder.AddChunk(CK: CodeCompletionString::CK_Equal); |
148 | Builder.AddPlaceholderChunk(Placeholder: "target" ); |
149 | Builder.AddChunk(CK: CodeCompletionString::CK_SemiColon); |
150 | return *Builder.TakeString(); |
151 | }; |
152 | computeSignature(CCS: MakeCCS()); |
153 | EXPECT_EQ(Snippet, " ${1:name} = ${2:target};" ); |
154 | |
155 | // When completing a pattern, the last placeholder holds the cursor position. |
156 | computeSignature(CCS: MakeCCS(), |
157 | /*ResultKind=*/CodeCompletionResult::ResultKind::RK_Pattern); |
158 | EXPECT_EQ(Snippet, " ${1:name} = $0;" ); |
159 | } |
160 | |
161 | TEST_F(CompletionStringTest, DropFunctionArguments) { |
162 | Builder.AddTypedTextChunk(Text: "foo" ); |
163 | Builder.AddChunk(CK: CodeCompletionString::CK_LeftAngle); |
164 | Builder.AddPlaceholderChunk(Placeholder: "typename T" ); |
165 | Builder.AddChunk(CK: CodeCompletionString::CK_Comma); |
166 | Builder.AddPlaceholderChunk(Placeholder: "int U" ); |
167 | Builder.AddChunk(CK: CodeCompletionString::CK_RightAngle); |
168 | Builder.AddChunk(CK: CodeCompletionString::CK_LeftParen); |
169 | Builder.AddPlaceholderChunk(Placeholder: "arg1" ); |
170 | Builder.AddChunk(CK: CodeCompletionString::CK_Comma); |
171 | Builder.AddPlaceholderChunk(Placeholder: "arg2" ); |
172 | Builder.AddChunk(CK: CodeCompletionString::CK_RightParen); |
173 | |
174 | computeSignature( |
175 | CCS: *Builder.TakeString(), |
176 | /*ResultKind=*/CodeCompletionResult::ResultKind::RK_Declaration, |
177 | /*IncludeFunctionArguments=*/false); |
178 | // Arguments dropped from snippet, kept in signature. |
179 | EXPECT_EQ(Signature, "<typename T, int U>(arg1, arg2)" ); |
180 | EXPECT_EQ(Snippet, "<${1:typename T}, ${2:int U}>" ); |
181 | } |
182 | |
183 | TEST_F(CompletionStringTest, IgnoreInformativeQualifier) { |
184 | Builder.AddTypedTextChunk(Text: "X" ); |
185 | Builder.AddInformativeChunk(Text: "info ok" ); |
186 | Builder.AddInformativeChunk(Text: "info no no::" ); |
187 | computeSignature(CCS: *Builder.TakeString()); |
188 | EXPECT_EQ(Signature, "info ok" ); |
189 | EXPECT_EQ(Snippet, "" ); |
190 | } |
191 | |
192 | TEST_F(CompletionStringTest, ObjectiveCMethodNoArguments) { |
193 | Builder.AddResultTypeChunk(ResultType: "void" ); |
194 | Builder.AddTypedTextChunk(Text: "methodName" ); |
195 | |
196 | auto *CCS = Builder.TakeString(); |
197 | computeSignature(CCS: *CCS); |
198 | EXPECT_EQ(Signature, "" ); |
199 | EXPECT_EQ(Snippet, "" ); |
200 | } |
201 | |
202 | TEST_F(CompletionStringTest, ObjectiveCMethodOneArgument) { |
203 | Builder.AddResultTypeChunk(ResultType: "void" ); |
204 | Builder.AddTypedTextChunk(Text: "methodWithArg:" ); |
205 | Builder.AddPlaceholderChunk(Placeholder: "(type)" ); |
206 | |
207 | auto *CCS = Builder.TakeString(); |
208 | computeSignature(CCS: *CCS); |
209 | EXPECT_EQ(Signature, "(type)" ); |
210 | EXPECT_EQ(Snippet, "${1:(type)}" ); |
211 | } |
212 | |
213 | TEST_F(CompletionStringTest, ObjectiveCMethodTwoArgumentsFromBeginning) { |
214 | Builder.AddResultTypeChunk(ResultType: "int" ); |
215 | Builder.AddTypedTextChunk(Text: "withFoo:" ); |
216 | Builder.AddPlaceholderChunk(Placeholder: "(type)" ); |
217 | Builder.AddChunk(CK: CodeCompletionString::CK_HorizontalSpace); |
218 | Builder.AddTypedTextChunk(Text: "bar:" ); |
219 | Builder.AddPlaceholderChunk(Placeholder: "(type2)" ); |
220 | |
221 | auto *CCS = Builder.TakeString(); |
222 | computeSignature(CCS: *CCS); |
223 | EXPECT_EQ(Signature, "(type) bar:(type2)" ); |
224 | EXPECT_EQ(Snippet, "${1:(type)} bar:${2:(type2)}" ); |
225 | } |
226 | |
227 | TEST_F(CompletionStringTest, ObjectiveCMethodTwoArgumentsFromMiddle) { |
228 | Builder.AddResultTypeChunk(ResultType: "int" ); |
229 | Builder.AddInformativeChunk(Text: "withFoo:" ); |
230 | Builder.AddTypedTextChunk(Text: "bar:" ); |
231 | Builder.AddPlaceholderChunk(Placeholder: "(type2)" ); |
232 | |
233 | auto *CCS = Builder.TakeString(); |
234 | computeSignature(CCS: *CCS); |
235 | EXPECT_EQ(Signature, "(type2)" ); |
236 | EXPECT_EQ(Snippet, "${1:(type2)}" ); |
237 | } |
238 | |
239 | } // namespace |
240 | } // namespace clangd |
241 | } // namespace clang |
242 | |