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