1 | //===-- clang-doc/HTMLGeneratorTest.cpp -----------------------------------===// |
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 "ClangDocTest.h" |
10 | #include "Generators.h" |
11 | #include "Representation.h" |
12 | #include "Serialize.h" |
13 | #include "clang/Basic/Version.h" |
14 | #include "gtest/gtest.h" |
15 | |
16 | namespace clang { |
17 | namespace doc { |
18 | |
19 | static const std::string ClangDocVersion = |
20 | clang::getClangToolFullVersion(ToolName: "clang-doc" ); |
21 | |
22 | std::unique_ptr<Generator> getHTMLGenerator() { |
23 | auto G = doc::findGeneratorByName(Format: "html" ); |
24 | if (!G) |
25 | return nullptr; |
26 | return std::move(G.get()); |
27 | } |
28 | |
29 | ClangDocContext |
30 | getClangDocContext(std::vector<std::string> UserStylesheets = {}, |
31 | StringRef RepositoryUrl = "" ) { |
32 | ClangDocContext CDCtx{ |
33 | {}, "test-project" , {}, {}, {}, RepositoryUrl, UserStylesheets, {}}; |
34 | CDCtx.UserStylesheets.insert( |
35 | position: CDCtx.UserStylesheets.begin(), |
36 | x: "../share/clang/clang-doc-default-stylesheet.css" ); |
37 | CDCtx.JsScripts.emplace_back(args: "index.js" ); |
38 | return CDCtx; |
39 | } |
40 | |
41 | TEST(HTMLGeneratorTest, emitNamespaceHTML) { |
42 | NamespaceInfo I; |
43 | I.Name = "Namespace" ; |
44 | I.Namespace.emplace_back(Args: EmptySID, Args: "A" , Args: InfoType::IT_namespace); |
45 | |
46 | I.Children.Namespaces.emplace_back(args: EmptySID, args: "ChildNamespace" , |
47 | args: InfoType::IT_namespace, |
48 | args: "Namespace::ChildNamespace" , args: "Namespace" ); |
49 | I.Children.Records.emplace_back(args: EmptySID, args: "ChildStruct" , args: InfoType::IT_record, |
50 | args: "Namespace::ChildStruct" , args: "Namespace" ); |
51 | I.Children.Functions.emplace_back(); |
52 | I.Children.Functions.back().Access = AccessSpecifier::AS_none; |
53 | I.Children.Functions.back().Name = "OneFunction" ; |
54 | I.Children.Enums.emplace_back(); |
55 | I.Children.Enums.back().Name = "OneEnum" ; |
56 | |
57 | auto G = getHTMLGenerator(); |
58 | assert(G); |
59 | std::string Buffer; |
60 | llvm::raw_string_ostream Actual(Buffer); |
61 | ClangDocContext CDCtx = getClangDocContext(UserStylesheets: {"user-provided-stylesheet.css" }); |
62 | auto Err = G->generateDocForInfo(I: &I, OS&: Actual, CDCtx); |
63 | assert(!Err); |
64 | std::string Expected = R"raw(<!DOCTYPE html> |
65 | <meta charset="utf-8"/> |
66 | <title>namespace Namespace</title> |
67 | <link rel="stylesheet" href="../clang-doc-default-stylesheet.css"/> |
68 | <link rel="stylesheet" href="../user-provided-stylesheet.css"/> |
69 | <script src="../index.js"></script> |
70 | <header id="project-title">test-project</header> |
71 | <main> |
72 | <div id="sidebar-left" path="Namespace" class="col-xs-6 col-sm-3 col-md-2 sidebar sidebar-offcanvas-left"></div> |
73 | <div id="main-content" class="col-xs-12 col-sm-9 col-md-8 main-content"> |
74 | <h1>namespace Namespace</h1> |
75 | <h2 id="Namespaces">Namespaces</h2> |
76 | <ul> |
77 | <li> |
78 | <a href="ChildNamespace/index.html">ChildNamespace</a> |
79 | </li> |
80 | </ul> |
81 | <h2 id="Records">Records</h2> |
82 | <ul> |
83 | <li> |
84 | <a href="ChildStruct.html">ChildStruct</a> |
85 | </li> |
86 | </ul> |
87 | <h2 id="Functions">Functions</h2> |
88 | <div> |
89 | <h3 id="0000000000000000000000000000000000000000">OneFunction</h3> |
90 | <p>OneFunction()</p> |
91 | </div> |
92 | <h2 id="Enums">Enums</h2> |
93 | <div> |
94 | <h3 id="0000000000000000000000000000000000000000">enum OneEnum</h3> |
95 | </div> |
96 | </div> |
97 | <div id="sidebar-right" class="col-xs-6 col-sm-6 col-md-2 sidebar sidebar-offcanvas-right"> |
98 | <ol> |
99 | <li> |
100 | <span> |
101 | <a href="#Namespaces">Namespaces</a> |
102 | </span> |
103 | </li> |
104 | <li> |
105 | <span> |
106 | <a href="#Records">Records</a> |
107 | </span> |
108 | </li> |
109 | <li> |
110 | <span> |
111 | <a href="#Functions">Functions</a> |
112 | </span> |
113 | <ul> |
114 | <li> |
115 | <span> |
116 | <a href="#0000000000000000000000000000000000000000">OneFunction</a> |
117 | </span> |
118 | </li> |
119 | </ul> |
120 | </li> |
121 | <li> |
122 | <span> |
123 | <a href="#Enums">Enums</a> |
124 | </span> |
125 | <ul> |
126 | <li> |
127 | <span> |
128 | <a href="#0000000000000000000000000000000000000000">OneEnum</a> |
129 | </span> |
130 | </li> |
131 | </ul> |
132 | </li> |
133 | </ol> |
134 | </div> |
135 | </main> |
136 | <footer> |
137 | <span class="no-break">)raw" + |
138 | ClangDocVersion + R"raw(</span> |
139 | </footer> |
140 | )raw" ; |
141 | |
142 | EXPECT_EQ(Expected, Actual.str()); |
143 | } |
144 | |
145 | TEST(HTMLGeneratorTest, emitRecordHTML) { |
146 | RecordInfo I; |
147 | I.Name = "r" ; |
148 | I.Path = "X/Y/Z" ; |
149 | I.Namespace.emplace_back(Args: EmptySID, Args: "A" , Args: InfoType::IT_namespace); |
150 | |
151 | I.DefLoc = Location(10, llvm::SmallString<16>{"dir/test.cpp" }, true); |
152 | I.Loc.emplace_back(Args: 12, Args: llvm::SmallString<16>{"test.cpp" }); |
153 | |
154 | SmallString<16> PathTo; |
155 | llvm::sys::path::native(path: "path/to" , result&: PathTo); |
156 | I.Members.emplace_back(Args: TypeInfo("int" ), Args: "X" , Args: AccessSpecifier::AS_private); |
157 | I.TagType = TagTypeKind::Class; |
158 | I.Parents.emplace_back(Args: EmptySID, Args: "F" , Args: InfoType::IT_record, Args: "F" , Args&: PathTo); |
159 | I.VirtualParents.emplace_back(Args: EmptySID, Args: "G" , Args: InfoType::IT_record); |
160 | |
161 | I.Children.Records.emplace_back(args: EmptySID, args: "ChildStruct" , args: InfoType::IT_record, |
162 | args: "X::Y::Z::r::ChildStruct" , args: "X/Y/Z/r" ); |
163 | I.Children.Functions.emplace_back(); |
164 | I.Children.Functions.back().Name = "OneFunction" ; |
165 | I.Children.Enums.emplace_back(); |
166 | I.Children.Enums.back().Name = "OneEnum" ; |
167 | |
168 | auto G = getHTMLGenerator(); |
169 | assert(G); |
170 | std::string Buffer; |
171 | llvm::raw_string_ostream Actual(Buffer); |
172 | ClangDocContext CDCtx = getClangDocContext(UserStylesheets: {}, RepositoryUrl: "http://www.repository.com" ); |
173 | auto Err = G->generateDocForInfo(I: &I, OS&: Actual, CDCtx); |
174 | assert(!Err); |
175 | std::string Expected = R"raw(<!DOCTYPE html> |
176 | <meta charset="utf-8"/> |
177 | <title>class r</title> |
178 | <link rel="stylesheet" href="../../../clang-doc-default-stylesheet.css"/> |
179 | <script src="../../../index.js"></script> |
180 | <header id="project-title">test-project</header> |
181 | <main> |
182 | <div id="sidebar-left" path="X/Y/Z" class="col-xs-6 col-sm-3 col-md-2 sidebar sidebar-offcanvas-left"></div> |
183 | <div id="main-content" class="col-xs-12 col-sm-9 col-md-8 main-content"> |
184 | <h1>class r</h1> |
185 | <p> |
186 | Defined at line |
187 | <a href="http://www.repository.com/dir/test.cpp#10">10</a> |
188 | of file |
189 | <a href="http://www.repository.com/dir/test.cpp">test.cpp</a> |
190 | </p> |
191 | <p> |
192 | Inherits from |
193 | <a href="../../../path/to/F.html">F</a> |
194 | , G |
195 | </p> |
196 | <h2 id="Members">Members</h2> |
197 | <ul> |
198 | <li>private int X</li> |
199 | </ul> |
200 | <h2 id="Records">Records</h2> |
201 | <ul> |
202 | <li> |
203 | <a href="../../../X/Y/Z/r/ChildStruct.html">ChildStruct</a> |
204 | </li> |
205 | </ul> |
206 | <h2 id="Functions">Functions</h2> |
207 | <div> |
208 | <h3 id="0000000000000000000000000000000000000000">OneFunction</h3> |
209 | <p>public OneFunction()</p> |
210 | </div> |
211 | <h2 id="Enums">Enums</h2> |
212 | <div> |
213 | <h3 id="0000000000000000000000000000000000000000">enum OneEnum</h3> |
214 | </div> |
215 | </div> |
216 | <div id="sidebar-right" class="col-xs-6 col-sm-6 col-md-2 sidebar sidebar-offcanvas-right"> |
217 | <ol> |
218 | <li> |
219 | <span> |
220 | <a href="#Members">Members</a> |
221 | </span> |
222 | </li> |
223 | <li> |
224 | <span> |
225 | <a href="#Records">Records</a> |
226 | </span> |
227 | </li> |
228 | <li> |
229 | <span> |
230 | <a href="#Functions">Functions</a> |
231 | </span> |
232 | <ul> |
233 | <li> |
234 | <span> |
235 | <a href="#0000000000000000000000000000000000000000">OneFunction</a> |
236 | </span> |
237 | </li> |
238 | </ul> |
239 | </li> |
240 | <li> |
241 | <span> |
242 | <a href="#Enums">Enums</a> |
243 | </span> |
244 | <ul> |
245 | <li> |
246 | <span> |
247 | <a href="#0000000000000000000000000000000000000000">OneEnum</a> |
248 | </span> |
249 | </li> |
250 | </ul> |
251 | </li> |
252 | </ol> |
253 | </div> |
254 | </main> |
255 | <footer> |
256 | <span class="no-break">)raw" + |
257 | ClangDocVersion + R"raw(</span> |
258 | </footer> |
259 | )raw" ; |
260 | |
261 | EXPECT_EQ(Expected, Actual.str()); |
262 | } |
263 | |
264 | TEST(HTMLGeneratorTest, emitFunctionHTML) { |
265 | FunctionInfo I; |
266 | I.Name = "f" ; |
267 | I.Namespace.emplace_back(Args: EmptySID, Args: "A" , Args: InfoType::IT_namespace); |
268 | |
269 | I.DefLoc = Location(10, llvm::SmallString<16>{"dir/test.cpp" }, false); |
270 | I.Loc.emplace_back(Args: 12, Args: llvm::SmallString<16>{"test.cpp" }); |
271 | |
272 | I.Access = AccessSpecifier::AS_none; |
273 | |
274 | SmallString<16> PathTo; |
275 | llvm::sys::path::native(path: "path/to" , result&: PathTo); |
276 | I.ReturnType = TypeInfo( |
277 | Reference(EmptySID, "float" , InfoType::IT_default, "float" , PathTo)); |
278 | I.Params.emplace_back(Args: TypeInfo("int" , PathTo), Args: "P" ); |
279 | I.IsMethod = true; |
280 | I.Parent = Reference(EmptySID, "Parent" , InfoType::IT_record); |
281 | |
282 | auto G = getHTMLGenerator(); |
283 | assert(G); |
284 | std::string Buffer; |
285 | llvm::raw_string_ostream Actual(Buffer); |
286 | ClangDocContext CDCtx = getClangDocContext(UserStylesheets: {}, RepositoryUrl: "https://www.repository.com" ); |
287 | auto Err = G->generateDocForInfo(I: &I, OS&: Actual, CDCtx); |
288 | assert(!Err); |
289 | std::string Expected = R"raw(<!DOCTYPE html> |
290 | <meta charset="utf-8"/> |
291 | <title></title> |
292 | <link rel="stylesheet" href="clang-doc-default-stylesheet.css"/> |
293 | <script src="index.js"></script> |
294 | <header id="project-title">test-project</header> |
295 | <main> |
296 | <div id="sidebar-left" path="" class="col-xs-6 col-sm-3 col-md-2 sidebar sidebar-offcanvas-left"></div> |
297 | <div id="main-content" class="col-xs-12 col-sm-9 col-md-8 main-content"> |
298 | <h3 id="0000000000000000000000000000000000000000">f</h3> |
299 | <p> |
300 | <a href="path/to/float.html">float</a> |
301 | f( |
302 | <a href="path/to/int.html">int</a> |
303 | P) |
304 | </p> |
305 | <p>Defined at line 10 of file dir/test.cpp</p> |
306 | </div> |
307 | <div id="sidebar-right" class="col-xs-6 col-sm-6 col-md-2 sidebar sidebar-offcanvas-right"></div> |
308 | </main> |
309 | <footer> |
310 | <span class="no-break">)raw" + |
311 | ClangDocVersion + R"raw(</span> |
312 | </footer> |
313 | )raw" ; |
314 | |
315 | EXPECT_EQ(Expected, Actual.str()); |
316 | } |
317 | |
318 | TEST(HTMLGeneratorTest, emitEnumHTML) { |
319 | EnumInfo I; |
320 | I.Name = "e" ; |
321 | I.Namespace.emplace_back(Args: EmptySID, Args: "A" , Args: InfoType::IT_namespace); |
322 | |
323 | I.DefLoc = Location(10, llvm::SmallString<16>{"test.cpp" }, true); |
324 | I.Loc.emplace_back(Args: 12, Args: llvm::SmallString<16>{"test.cpp" }); |
325 | |
326 | I.Members.emplace_back(Args: "X" ); |
327 | I.Scoped = true; |
328 | |
329 | auto G = getHTMLGenerator(); |
330 | assert(G); |
331 | std::string Buffer; |
332 | llvm::raw_string_ostream Actual(Buffer); |
333 | ClangDocContext CDCtx = getClangDocContext(UserStylesheets: {}, RepositoryUrl: "www.repository.com" ); |
334 | auto Err = G->generateDocForInfo(I: &I, OS&: Actual, CDCtx); |
335 | assert(!Err); |
336 | std::string Expected = R"raw(<!DOCTYPE html> |
337 | <meta charset="utf-8"/> |
338 | <title></title> |
339 | <link rel="stylesheet" href="clang-doc-default-stylesheet.css"/> |
340 | <script src="index.js"></script> |
341 | <header id="project-title">test-project</header> |
342 | <main> |
343 | <div id="sidebar-left" path="" class="col-xs-6 col-sm-3 col-md-2 sidebar sidebar-offcanvas-left"></div> |
344 | <div id="main-content" class="col-xs-12 col-sm-9 col-md-8 main-content"> |
345 | <h3 id="0000000000000000000000000000000000000000">enum class e</h3> |
346 | <ul> |
347 | <li>X</li> |
348 | </ul> |
349 | <p> |
350 | Defined at line |
351 | <a href="https://www.repository.com/test.cpp#10">10</a> |
352 | of file |
353 | <a href="https://www.repository.com/test.cpp">test.cpp</a> |
354 | </p> |
355 | </div> |
356 | <div id="sidebar-right" class="col-xs-6 col-sm-6 col-md-2 sidebar sidebar-offcanvas-right"></div> |
357 | </main> |
358 | <footer> |
359 | <span class="no-break">)raw" + |
360 | ClangDocVersion + R"raw(</span> |
361 | </footer> |
362 | )raw" ; |
363 | |
364 | EXPECT_EQ(Expected, Actual.str()); |
365 | } |
366 | |
367 | TEST(HTMLGeneratorTest, emitCommentHTML) { |
368 | FunctionInfo I; |
369 | I.Name = "f" ; |
370 | I.DefLoc = Location(10, llvm::SmallString<16>{"test.cpp" }); |
371 | I.ReturnType = TypeInfo("void" ); |
372 | I.Params.emplace_back(Args: TypeInfo("int" ), Args: "I" ); |
373 | I.Params.emplace_back(Args: TypeInfo("int" ), Args: "J" ); |
374 | I.Access = AccessSpecifier::AS_none; |
375 | |
376 | CommentInfo Top; |
377 | Top.Kind = "FullComment" ; |
378 | |
379 | Top.Children.emplace_back(args: std::make_unique<CommentInfo>()); |
380 | CommentInfo *BlankLine = Top.Children.back().get(); |
381 | BlankLine->Kind = "ParagraphComment" ; |
382 | BlankLine->Children.emplace_back(args: std::make_unique<CommentInfo>()); |
383 | BlankLine->Children.back()->Kind = "TextComment" ; |
384 | |
385 | Top.Children.emplace_back(args: std::make_unique<CommentInfo>()); |
386 | CommentInfo *Brief = Top.Children.back().get(); |
387 | Brief->Kind = "ParagraphComment" ; |
388 | Brief->Children.emplace_back(args: std::make_unique<CommentInfo>()); |
389 | Brief->Children.back()->Kind = "TextComment" ; |
390 | Brief->Children.back()->Name = "ParagraphComment" ; |
391 | Brief->Children.back()->Text = " Brief description." ; |
392 | |
393 | Top.Children.emplace_back(args: std::make_unique<CommentInfo>()); |
394 | CommentInfo *Extended = Top.Children.back().get(); |
395 | Extended->Kind = "ParagraphComment" ; |
396 | Extended->Children.emplace_back(args: std::make_unique<CommentInfo>()); |
397 | Extended->Children.back()->Kind = "TextComment" ; |
398 | Extended->Children.back()->Text = " Extended description that" ; |
399 | Extended->Children.emplace_back(args: std::make_unique<CommentInfo>()); |
400 | Extended->Children.back()->Kind = "TextComment" ; |
401 | Extended->Children.back()->Text = " continues onto the next line." ; |
402 | |
403 | Top.Children.emplace_back(args: std::make_unique<CommentInfo>()); |
404 | CommentInfo *Entities = Top.Children.back().get(); |
405 | Entities->Kind = "ParagraphComment" ; |
406 | Entities->Children.emplace_back(args: std::make_unique<CommentInfo>()); |
407 | Entities->Children.back()->Kind = "TextComment" ; |
408 | Entities->Children.back()->Name = "ParagraphComment" ; |
409 | Entities->Children.back()->Text = |
410 | " Comment with html entities: &, <, >, \", \'." ; |
411 | |
412 | I.Description.emplace_back(args: std::move(Top)); |
413 | |
414 | auto G = getHTMLGenerator(); |
415 | assert(G); |
416 | std::string Buffer; |
417 | llvm::raw_string_ostream Actual(Buffer); |
418 | ClangDocContext CDCtx = getClangDocContext(); |
419 | auto Err = G->generateDocForInfo(I: &I, OS&: Actual, CDCtx); |
420 | assert(!Err); |
421 | std::string Expected = R"raw(<!DOCTYPE html> |
422 | <meta charset="utf-8"/> |
423 | <title></title> |
424 | <link rel="stylesheet" href="clang-doc-default-stylesheet.css"/> |
425 | <script src="index.js"></script> |
426 | <header id="project-title">test-project</header> |
427 | <main> |
428 | <div id="sidebar-left" path="" class="col-xs-6 col-sm-3 col-md-2 sidebar sidebar-offcanvas-left"></div> |
429 | <div id="main-content" class="col-xs-12 col-sm-9 col-md-8 main-content"> |
430 | <h3 id="0000000000000000000000000000000000000000">f</h3> |
431 | <p>void f(int I, int J)</p> |
432 | <p>Defined at line 10 of file test.cpp</p> |
433 | <div> |
434 | <div> |
435 | <p> Brief description.</p> |
436 | <p> Extended description that continues onto the next line.</p> |
437 | <p> Comment with html entities: &, <, >, ", '.</p> |
438 | </div> |
439 | </div> |
440 | </div> |
441 | <div id="sidebar-right" class="col-xs-6 col-sm-6 col-md-2 sidebar sidebar-offcanvas-right"></div> |
442 | </main> |
443 | <footer> |
444 | <span class="no-break">)raw" + |
445 | ClangDocVersion + R"raw(</span> |
446 | </footer> |
447 | )raw" ; |
448 | |
449 | EXPECT_EQ(Expected, Actual.str()); |
450 | } |
451 | |
452 | } // namespace doc |
453 | } // namespace clang |
454 | |