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