1 //===-- HTMLGenerator.cpp - HTML Generator ----------------------*- C++ -*-===//
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
7 //===----------------------------------------------------------------------===//
9 #include "Generators.h"
10 #include "Representation.h"
11 #include "clang/Basic/Version.h"
12 #include "llvm/ADT/StringExtras.h"
13 #include "llvm/ADT/StringRef.h"
14 #include "llvm/ADT/StringSet.h"
15 #include "llvm/Support/FileSystem.h"
16 #include "llvm/Support/JSON.h"
17 #include "llvm/Support/Path.h"
18 #include "llvm/Support/raw_ostream.h"
32 // Any other tag can be added if required
60 constexpr HTMLTag(TagType Value
) : Value(Value
) {}
62 operator TagType() const { return Value
; }
63 operator bool() = delete;
65 bool isSelfClosing() const;
66 StringRef
toString() const;
78 HTMLNode(NodeType Type
) : Type(Type
) {}
79 virtual ~HTMLNode() = default;
81 virtual void render(llvm::raw_ostream
&OS
, int IndentationLevel
) = 0;
82 NodeType Type
; // Type of node
85 struct TextNode
: public HTMLNode
{
86 TextNode(const Twine
&Text
)
87 : HTMLNode(NodeType::NODE_TEXT
), Text(Text
.str()) {}
89 std::string Text
; // Content of node
90 void render(llvm::raw_ostream
&OS
, int IndentationLevel
) override
;
93 struct TagNode
: public HTMLNode
{
94 TagNode(HTMLTag Tag
) : HTMLNode(NodeType::NODE_TAG
), Tag(Tag
) {}
95 TagNode(HTMLTag Tag
, const Twine
&Text
) : TagNode(Tag
) {
96 Children
.emplace_back(std::make_unique
<TextNode
>(Text
.str()));
99 HTMLTag Tag
; // Name of HTML Tag (p, div, h1)
100 std::vector
<std::unique_ptr
<HTMLNode
>> Children
; // List of child nodes
101 std::vector
<std::pair
<std::string
, std::string
>>
102 Attributes
; // List of key-value attributes for tag
104 void render(llvm::raw_ostream
&OS
, int IndentationLevel
) override
;
107 constexpr const char *kDoctypeDecl
= "<!DOCTYPE html>";
110 std::vector
<std::unique_ptr
<HTMLNode
>> Children
; // List of child nodes
111 void render(llvm::raw_ostream
&OS
) {
112 OS
<< kDoctypeDecl
<< "\n";
113 for (const auto &C
: Children
) {
122 bool HTMLTag::isSelfClosing() const {
124 case HTMLTag::TAG_META
:
125 case HTMLTag::TAG_LINK
:
128 case HTMLTag::TAG_DIV
:
129 case HTMLTag::TAG_FOOTER
:
130 case HTMLTag::TAG_H1
:
131 case HTMLTag::TAG_H2
:
132 case HTMLTag::TAG_H3
:
133 case HTMLTag::TAG_HEADER
:
134 case HTMLTag::TAG_LI
:
135 case HTMLTag::TAG_MAIN
:
136 case HTMLTag::TAG_OL
:
138 case HTMLTag::TAG_SCRIPT
:
139 case HTMLTag::TAG_SPAN
:
140 case HTMLTag::TAG_TITLE
:
141 case HTMLTag::TAG_UL
:
142 case HTMLTag::TAG_TABLE
:
143 case HTMLTag::TAG_THEAD
:
144 case HTMLTag::TAG_TBODY
:
145 case HTMLTag::TAG_TR
:
146 case HTMLTag::TAG_TD
:
147 case HTMLTag::TAG_TH
:
150 llvm_unreachable("Unhandled HTMLTag::TagType");
153 StringRef
HTMLTag::toString() const {
157 case HTMLTag::TAG_DIV
:
159 case HTMLTag::TAG_FOOTER
:
161 case HTMLTag::TAG_H1
:
163 case HTMLTag::TAG_H2
:
165 case HTMLTag::TAG_H3
:
167 case HTMLTag::TAG_HEADER
:
169 case HTMLTag::TAG_LI
:
171 case HTMLTag::TAG_LINK
:
173 case HTMLTag::TAG_MAIN
:
175 case HTMLTag::TAG_META
:
177 case HTMLTag::TAG_OL
:
181 case HTMLTag::TAG_SCRIPT
:
183 case HTMLTag::TAG_SPAN
:
185 case HTMLTag::TAG_TITLE
:
187 case HTMLTag::TAG_UL
:
189 case HTMLTag::TAG_TABLE
:
191 case HTMLTag::TAG_THEAD
:
193 case HTMLTag::TAG_TBODY
:
195 case HTMLTag::TAG_TR
:
197 case HTMLTag::TAG_TD
:
199 case HTMLTag::TAG_TH
:
202 llvm_unreachable("Unhandled HTMLTag::TagType");
205 void TextNode::render(llvm::raw_ostream
&OS
, int IndentationLevel
) {
206 OS
.indent(IndentationLevel
* 2);
207 printHTMLEscaped(Text
, OS
);
210 void TagNode::render(llvm::raw_ostream
&OS
, int IndentationLevel
) {
211 // Children nodes are rendered in the same line if all of them are text nodes
212 bool InlineChildren
= true;
213 for (const auto &C
: Children
)
214 if (C
->Type
== NodeType::NODE_TAG
) {
215 InlineChildren
= false;
218 OS
.indent(IndentationLevel
* 2);
219 OS
<< "<" << Tag
.toString();
220 for (const auto &A
: Attributes
)
221 OS
<< " " << A
.first
<< "=\"" << A
.second
<< "\"";
222 if (Tag
.isSelfClosing()) {
229 bool NewLineRendered
= true;
230 for (const auto &C
: Children
) {
231 int ChildrenIndentation
=
232 InlineChildren
|| !NewLineRendered
? 0 : IndentationLevel
+ 1;
233 C
->render(OS
, ChildrenIndentation
);
234 if (!InlineChildren
&& (C
== Children
.back() ||
235 (C
->Type
!= NodeType::NODE_TEXT
||
236 (&C
+ 1)->get()->Type
!= NodeType::NODE_TEXT
))) {
238 NewLineRendered
= true;
240 NewLineRendered
= false;
243 OS
.indent(IndentationLevel
* 2);
244 OS
<< "</" << Tag
.toString() << ">";
247 template <typename Derived
, typename Base
,
248 typename
= std::enable_if
<std::is_base_of
<Derived
, Base
>::value
>>
249 static void appendVector(std::vector
<Derived
> &&New
,
250 std::vector
<Base
> &Original
) {
251 std::move(New
.begin(), New
.end(), std::back_inserter(Original
));
254 // Compute the relative path from an Origin directory to a Destination directory
255 static SmallString
<128> computeRelativePath(StringRef Destination
,
257 // If Origin is empty, the relative path to the Destination is its complete
262 // The relative path is an empty path if both directories are the same.
263 if (Destination
== Origin
)
266 // These iterators iterate through each of their parent directories
267 llvm::sys::path::const_iterator FileI
= llvm::sys::path::begin(Destination
);
268 llvm::sys::path::const_iterator FileE
= llvm::sys::path::end(Destination
);
269 llvm::sys::path::const_iterator DirI
= llvm::sys::path::begin(Origin
);
270 llvm::sys::path::const_iterator DirE
= llvm::sys::path::end(Origin
);
271 // Advance both iterators until the paths differ. Example:
272 // Destination = A/B/C/D
274 // FileI will point to C and DirI to E. The directories behind them is the
275 // directory they share (A/B).
276 while (FileI
!= FileE
&& DirI
!= DirE
&& *FileI
== *DirI
) {
280 SmallString
<128> Result
; // This will hold the resulting path.
281 // Result has to go up one directory for each of the remaining directories in
283 while (DirI
!= DirE
) {
284 llvm::sys::path::append(Result
, "..");
287 // Result has to append each of the remaining directories in Destination
288 while (FileI
!= FileE
) {
289 llvm::sys::path::append(Result
, *FileI
);
297 static std::vector
<std::unique_ptr
<TagNode
>>
298 genStylesheetsHTML(StringRef InfoPath
, const ClangDocContext
&CDCtx
) {
299 std::vector
<std::unique_ptr
<TagNode
>> Out
;
300 for (const auto &FilePath
: CDCtx
.UserStylesheets
) {
301 auto LinkNode
= std::make_unique
<TagNode
>(HTMLTag::TAG_LINK
);
302 LinkNode
->Attributes
.emplace_back("rel", "stylesheet");
303 SmallString
<128> StylesheetPath
= computeRelativePath("", InfoPath
);
304 llvm::sys::path::append(StylesheetPath
,
305 llvm::sys::path::filename(FilePath
));
306 // Paths in HTML must be in posix-style
307 llvm::sys::path::native(StylesheetPath
, llvm::sys::path::Style::posix
);
308 LinkNode
->Attributes
.emplace_back("href", std::string(StylesheetPath
));
309 Out
.emplace_back(std::move(LinkNode
));
314 static std::vector
<std::unique_ptr
<TagNode
>>
315 genJsScriptsHTML(StringRef InfoPath
, const ClangDocContext
&CDCtx
) {
316 std::vector
<std::unique_ptr
<TagNode
>> Out
;
318 // index_json.js is part of every generated HTML file
319 SmallString
<128> IndexJSONPath
= computeRelativePath("", InfoPath
);
320 auto IndexJSONNode
= std::make_unique
<TagNode
>(HTMLTag::TAG_SCRIPT
);
321 llvm::sys::path::append(IndexJSONPath
, "index_json.js");
322 llvm::sys::path::native(IndexJSONPath
, llvm::sys::path::Style::posix
);
323 IndexJSONNode
->Attributes
.emplace_back("src", std::string(IndexJSONPath
));
324 Out
.emplace_back(std::move(IndexJSONNode
));
326 for (const auto &FilePath
: CDCtx
.JsScripts
) {
327 SmallString
<128> ScriptPath
= computeRelativePath("", InfoPath
);
328 auto ScriptNode
= std::make_unique
<TagNode
>(HTMLTag::TAG_SCRIPT
);
329 llvm::sys::path::append(ScriptPath
, llvm::sys::path::filename(FilePath
));
330 // Paths in HTML must be in posix-style
331 llvm::sys::path::native(ScriptPath
, llvm::sys::path::Style::posix
);
332 ScriptNode
->Attributes
.emplace_back("src", std::string(ScriptPath
));
333 Out
.emplace_back(std::move(ScriptNode
));
338 static std::unique_ptr
<TagNode
> genLink(const Twine
&Text
, const Twine
&Link
) {
339 auto LinkNode
= std::make_unique
<TagNode
>(HTMLTag::TAG_A
, Text
);
340 LinkNode
->Attributes
.emplace_back("href", Link
.str());
344 static std::unique_ptr
<HTMLNode
>
345 genReference(const Reference
&Type
, StringRef CurrentDirectory
,
346 std::optional
<StringRef
> JumpToSection
= std::nullopt
) {
347 if (Type
.Path
.empty()) {
349 return std::make_unique
<TextNode
>(Type
.Name
);
350 return genLink(Type
.Name
, "#" + *JumpToSection
);
352 llvm::SmallString
<64> Path
= Type
.getRelativeFilePath(CurrentDirectory
);
353 llvm::sys::path::append(Path
, Type
.getFileBaseName() + ".html");
355 // Paths in HTML must be in posix-style
356 llvm::sys::path::native(Path
, llvm::sys::path::Style::posix
);
358 Path
+= ("#" + *JumpToSection
).str();
359 return genLink(Type
.Name
, Path
);
362 static std::vector
<std::unique_ptr
<HTMLNode
>>
363 genReferenceList(const llvm::SmallVectorImpl
<Reference
> &Refs
,
364 const StringRef
&CurrentDirectory
) {
365 std::vector
<std::unique_ptr
<HTMLNode
>> Out
;
366 for (const auto &R
: Refs
) {
367 if (&R
!= Refs
.begin())
368 Out
.emplace_back(std::make_unique
<TextNode
>(", "));
369 Out
.emplace_back(genReference(R
, CurrentDirectory
));
374 static std::vector
<std::unique_ptr
<TagNode
>>
375 genHTML(const EnumInfo
&I
, const ClangDocContext
&CDCtx
);
376 static std::vector
<std::unique_ptr
<TagNode
>>
377 genHTML(const FunctionInfo
&I
, const ClangDocContext
&CDCtx
,
378 StringRef ParentInfoDir
);
379 static std::unique_ptr
<TagNode
> genHTML(const std::vector
<CommentInfo
> &C
);
381 static std::vector
<std::unique_ptr
<TagNode
>>
382 genEnumsBlock(const std::vector
<EnumInfo
> &Enums
,
383 const ClangDocContext
&CDCtx
) {
387 std::vector
<std::unique_ptr
<TagNode
>> Out
;
388 Out
.emplace_back(std::make_unique
<TagNode
>(HTMLTag::TAG_H2
, "Enums"));
389 Out
.back()->Attributes
.emplace_back("id", "Enums");
390 Out
.emplace_back(std::make_unique
<TagNode
>(HTMLTag::TAG_DIV
));
391 auto &DivBody
= Out
.back();
392 for (const auto &E
: Enums
) {
393 std::vector
<std::unique_ptr
<TagNode
>> Nodes
= genHTML(E
, CDCtx
);
394 appendVector(std::move(Nodes
), DivBody
->Children
);
399 static std::unique_ptr
<TagNode
>
400 genEnumMembersBlock(const llvm::SmallVector
<EnumValueInfo
, 4> &Members
) {
404 auto List
= std::make_unique
<TagNode
>(HTMLTag::TAG_TBODY
);
406 for (const auto &M
: Members
) {
407 auto TRNode
= std::make_unique
<TagNode
>(HTMLTag::TAG_TR
);
408 TRNode
->Children
.emplace_back(
409 std::make_unique
<TagNode
>(HTMLTag::TAG_TD
, M
.Name
));
410 // Use user supplied value if it exists, otherwise use the value
411 if (!M
.ValueExpr
.empty()) {
412 TRNode
->Children
.emplace_back(
413 std::make_unique
<TagNode
>(HTMLTag::TAG_TD
, M
.ValueExpr
));
415 TRNode
->Children
.emplace_back(
416 std::make_unique
<TagNode
>(HTMLTag::TAG_TD
, M
.Value
));
418 if (!M
.Description
.empty()) {
419 auto TD
= std::make_unique
<TagNode
>(HTMLTag::TAG_TD
);
420 TD
->Children
.emplace_back(genHTML(M
.Description
));
421 TRNode
->Children
.emplace_back(std::move(TD
));
423 List
->Children
.emplace_back(std::move(TRNode
));
428 static std::vector
<std::unique_ptr
<TagNode
>>
429 genFunctionsBlock(const std::vector
<FunctionInfo
> &Functions
,
430 const ClangDocContext
&CDCtx
, StringRef ParentInfoDir
) {
431 if (Functions
.empty())
434 std::vector
<std::unique_ptr
<TagNode
>> Out
;
435 Out
.emplace_back(std::make_unique
<TagNode
>(HTMLTag::TAG_H2
, "Functions"));
436 Out
.back()->Attributes
.emplace_back("id", "Functions");
437 Out
.emplace_back(std::make_unique
<TagNode
>(HTMLTag::TAG_DIV
));
438 auto &DivBody
= Out
.back();
439 for (const auto &F
: Functions
) {
440 std::vector
<std::unique_ptr
<TagNode
>> Nodes
=
441 genHTML(F
, CDCtx
, ParentInfoDir
);
442 appendVector(std::move(Nodes
), DivBody
->Children
);
447 static std::vector
<std::unique_ptr
<TagNode
>>
448 genRecordMembersBlock(const llvm::SmallVector
<MemberTypeInfo
, 4> &Members
,
449 StringRef ParentInfoDir
) {
453 std::vector
<std::unique_ptr
<TagNode
>> Out
;
454 Out
.emplace_back(std::make_unique
<TagNode
>(HTMLTag::TAG_H2
, "Members"));
455 Out
.back()->Attributes
.emplace_back("id", "Members");
456 Out
.emplace_back(std::make_unique
<TagNode
>(HTMLTag::TAG_UL
));
457 auto &ULBody
= Out
.back();
458 for (const auto &M
: Members
) {
459 std::string Access
= getAccessSpelling(M
.Access
).str();
461 Access
= Access
+ " ";
462 auto LIBody
= std::make_unique
<TagNode
>(HTMLTag::TAG_LI
);
463 auto MemberDecl
= std::make_unique
<TagNode
>(HTMLTag::TAG_DIV
);
464 MemberDecl
->Children
.emplace_back(std::make_unique
<TextNode
>(Access
));
465 MemberDecl
->Children
.emplace_back(genReference(M
.Type
, ParentInfoDir
));
466 MemberDecl
->Children
.emplace_back(std::make_unique
<TextNode
>(" " + M
.Name
));
467 if (!M
.Description
.empty())
468 LIBody
->Children
.emplace_back(genHTML(M
.Description
));
469 LIBody
->Children
.emplace_back(std::move(MemberDecl
));
470 ULBody
->Children
.emplace_back(std::move(LIBody
));
475 static std::vector
<std::unique_ptr
<TagNode
>>
476 genReferencesBlock(const std::vector
<Reference
> &References
,
477 llvm::StringRef Title
, StringRef ParentPath
) {
478 if (References
.empty())
481 std::vector
<std::unique_ptr
<TagNode
>> Out
;
482 Out
.emplace_back(std::make_unique
<TagNode
>(HTMLTag::TAG_H2
, Title
));
483 Out
.back()->Attributes
.emplace_back("id", std::string(Title
));
484 Out
.emplace_back(std::make_unique
<TagNode
>(HTMLTag::TAG_UL
));
485 auto &ULBody
= Out
.back();
486 for (const auto &R
: References
) {
487 auto LiNode
= std::make_unique
<TagNode
>(HTMLTag::TAG_LI
);
488 LiNode
->Children
.emplace_back(genReference(R
, ParentPath
));
489 ULBody
->Children
.emplace_back(std::move(LiNode
));
494 static std::unique_ptr
<TagNode
>
495 writeFileDefinition(const Location
&L
,
496 std::optional
<StringRef
> RepositoryUrl
= std::nullopt
) {
497 if (!L
.IsFileInRootDir
|| !RepositoryUrl
)
498 return std::make_unique
<TagNode
>(
499 HTMLTag::TAG_P
, "Defined at line " + std::to_string(L
.LineNumber
) +
500 " of file " + L
.Filename
);
501 SmallString
<128> FileURL(*RepositoryUrl
);
502 llvm::sys::path::append(FileURL
, llvm::sys::path::Style::posix
, L
.Filename
);
503 auto Node
= std::make_unique
<TagNode
>(HTMLTag::TAG_P
);
504 Node
->Children
.emplace_back(std::make_unique
<TextNode
>("Defined at line "));
506 std::make_unique
<TagNode
>(HTMLTag::TAG_A
, std::to_string(L
.LineNumber
));
507 // The links to a specific line in the source code use the github /
508 // googlesource notation so it won't work for all hosting pages.
509 LocNumberNode
->Attributes
.emplace_back(
510 "href", (FileURL
+ "#" + std::to_string(L
.LineNumber
)).str());
511 Node
->Children
.emplace_back(std::move(LocNumberNode
));
512 Node
->Children
.emplace_back(std::make_unique
<TextNode
>(" of file "));
513 auto LocFileNode
= std::make_unique
<TagNode
>(
514 HTMLTag::TAG_A
, llvm::sys::path::filename(FileURL
));
515 LocFileNode
->Attributes
.emplace_back("href", std::string(FileURL
));
516 Node
->Children
.emplace_back(std::move(LocFileNode
));
520 static std::vector
<std::unique_ptr
<TagNode
>>
521 genHTML(const Index
&Index
, StringRef InfoPath
, bool IsOutermostList
);
523 // Generates a list of child nodes for the HTML head tag
524 // It contains a meta node, link nodes to import CSS files, and script nodes to
526 static std::vector
<std::unique_ptr
<TagNode
>>
527 genFileHeadNodes(StringRef Title
, StringRef InfoPath
,
528 const ClangDocContext
&CDCtx
) {
529 std::vector
<std::unique_ptr
<TagNode
>> Out
;
530 auto MetaNode
= std::make_unique
<TagNode
>(HTMLTag::TAG_META
);
531 MetaNode
->Attributes
.emplace_back("charset", "utf-8");
532 Out
.emplace_back(std::move(MetaNode
));
533 Out
.emplace_back(std::make_unique
<TagNode
>(HTMLTag::TAG_TITLE
, Title
));
534 std::vector
<std::unique_ptr
<TagNode
>> StylesheetsNodes
=
535 genStylesheetsHTML(InfoPath
, CDCtx
);
536 appendVector(std::move(StylesheetsNodes
), Out
);
537 std::vector
<std::unique_ptr
<TagNode
>> JsNodes
=
538 genJsScriptsHTML(InfoPath
, CDCtx
);
539 appendVector(std::move(JsNodes
), Out
);
543 // Generates a header HTML node that can be used for any file
544 // It contains the project name
545 static std::unique_ptr
<TagNode
> genFileHeaderNode(StringRef ProjectName
) {
546 auto HeaderNode
= std::make_unique
<TagNode
>(HTMLTag::TAG_HEADER
, ProjectName
);
547 HeaderNode
->Attributes
.emplace_back("id", "project-title");
551 // Generates a main HTML node that has all the main content of an info file
552 // It contains both indexes and the info's documented information
553 // This function should only be used for the info files (not for the file that
554 // only has the general index)
555 static std::unique_ptr
<TagNode
> genInfoFileMainNode(
557 std::vector
<std::unique_ptr
<TagNode
>> &MainContentInnerNodes
,
558 const Index
&InfoIndex
) {
559 auto MainNode
= std::make_unique
<TagNode
>(HTMLTag::TAG_MAIN
);
561 auto LeftSidebarNode
= std::make_unique
<TagNode
>(HTMLTag::TAG_DIV
);
562 LeftSidebarNode
->Attributes
.emplace_back("id", "sidebar-left");
563 LeftSidebarNode
->Attributes
.emplace_back("path", std::string(InfoPath
));
564 LeftSidebarNode
->Attributes
.emplace_back(
565 "class", "col-xs-6 col-sm-3 col-md-2 sidebar sidebar-offcanvas-left");
567 auto MainContentNode
= std::make_unique
<TagNode
>(HTMLTag::TAG_DIV
);
568 MainContentNode
->Attributes
.emplace_back("id", "main-content");
569 MainContentNode
->Attributes
.emplace_back(
570 "class", "col-xs-12 col-sm-9 col-md-8 main-content");
571 appendVector(std::move(MainContentInnerNodes
), MainContentNode
->Children
);
573 auto RightSidebarNode
= std::make_unique
<TagNode
>(HTMLTag::TAG_DIV
);
574 RightSidebarNode
->Attributes
.emplace_back("id", "sidebar-right");
575 RightSidebarNode
->Attributes
.emplace_back(
576 "class", "col-xs-6 col-sm-6 col-md-2 sidebar sidebar-offcanvas-right");
577 std::vector
<std::unique_ptr
<TagNode
>> InfoIndexHTML
=
578 genHTML(InfoIndex
, InfoPath
, true);
579 appendVector(std::move(InfoIndexHTML
), RightSidebarNode
->Children
);
581 MainNode
->Children
.emplace_back(std::move(LeftSidebarNode
));
582 MainNode
->Children
.emplace_back(std::move(MainContentNode
));
583 MainNode
->Children
.emplace_back(std::move(RightSidebarNode
));
588 // Generates a footer HTML node that can be used for any file
589 // It contains clang-doc's version
590 static std::unique_ptr
<TagNode
> genFileFooterNode() {
591 auto FooterNode
= std::make_unique
<TagNode
>(HTMLTag::TAG_FOOTER
);
592 auto SpanNode
= std::make_unique
<TagNode
>(
593 HTMLTag::TAG_SPAN
, clang::getClangToolFullVersion("clang-doc"));
594 SpanNode
->Attributes
.emplace_back("class", "no-break");
595 FooterNode
->Children
.emplace_back(std::move(SpanNode
));
599 // Generates a complete HTMLFile for an Info
601 genInfoFile(StringRef Title
, StringRef InfoPath
,
602 std::vector
<std::unique_ptr
<TagNode
>> &MainContentNodes
,
603 const Index
&InfoIndex
, const ClangDocContext
&CDCtx
) {
606 std::vector
<std::unique_ptr
<TagNode
>> HeadNodes
=
607 genFileHeadNodes(Title
, InfoPath
, CDCtx
);
608 std::unique_ptr
<TagNode
> HeaderNode
= genFileHeaderNode(CDCtx
.ProjectName
);
609 std::unique_ptr
<TagNode
> MainNode
=
610 genInfoFileMainNode(InfoPath
, MainContentNodes
, InfoIndex
);
611 std::unique_ptr
<TagNode
> FooterNode
= genFileFooterNode();
613 appendVector(std::move(HeadNodes
), F
.Children
);
614 F
.Children
.emplace_back(std::move(HeaderNode
));
615 F
.Children
.emplace_back(std::move(MainNode
));
616 F
.Children
.emplace_back(std::move(FooterNode
));
621 template <typename T
,
622 typename
= std::enable_if
<std::is_base_of
<T
, Info
>::value
>>
623 static Index
genInfoIndexItem(const std::vector
<T
> &Infos
, StringRef Title
) {
624 Index
Idx(Title
, Title
);
625 for (const auto &C
: Infos
)
626 Idx
.Children
.emplace_back(C
.extractName(),
627 llvm::toHex(llvm::toStringRef(C
.USR
)));
631 static std::vector
<std::unique_ptr
<TagNode
>>
632 genHTML(const Index
&Index
, StringRef InfoPath
, bool IsOutermostList
) {
633 std::vector
<std::unique_ptr
<TagNode
>> Out
;
634 if (!Index
.Name
.empty()) {
635 Out
.emplace_back(std::make_unique
<TagNode
>(HTMLTag::TAG_SPAN
));
636 auto &SpanBody
= Out
.back();
637 if (!Index
.JumpToSection
)
638 SpanBody
->Children
.emplace_back(genReference(Index
, InfoPath
));
640 SpanBody
->Children
.emplace_back(
641 genReference(Index
, InfoPath
, Index
.JumpToSection
->str()));
643 if (Index
.Children
.empty())
645 // Only the outermost list should use ol, the others should use ul
646 HTMLTag ListHTMLTag
= IsOutermostList
? HTMLTag::TAG_OL
: HTMLTag::TAG_UL
;
647 Out
.emplace_back(std::make_unique
<TagNode
>(ListHTMLTag
));
648 const auto &UlBody
= Out
.back();
649 for (const auto &C
: Index
.Children
) {
650 auto LiBody
= std::make_unique
<TagNode
>(HTMLTag::TAG_LI
);
651 std::vector
<std::unique_ptr
<TagNode
>> Nodes
= genHTML(C
, InfoPath
, false);
652 appendVector(std::move(Nodes
), LiBody
->Children
);
653 UlBody
->Children
.emplace_back(std::move(LiBody
));
658 static std::unique_ptr
<HTMLNode
> genHTML(const CommentInfo
&I
) {
659 if (I
.Kind
== "FullComment") {
660 auto FullComment
= std::make_unique
<TagNode
>(HTMLTag::TAG_DIV
);
661 for (const auto &Child
: I
.Children
) {
662 std::unique_ptr
<HTMLNode
> Node
= genHTML(*Child
);
664 FullComment
->Children
.emplace_back(std::move(Node
));
666 return std::move(FullComment
);
669 if (I
.Kind
== "ParagraphComment") {
670 auto ParagraphComment
= std::make_unique
<TagNode
>(HTMLTag::TAG_P
);
671 for (const auto &Child
: I
.Children
) {
672 std::unique_ptr
<HTMLNode
> Node
= genHTML(*Child
);
674 ParagraphComment
->Children
.emplace_back(std::move(Node
));
676 if (ParagraphComment
->Children
.empty())
678 return std::move(ParagraphComment
);
681 if (I
.Kind
== "BlockCommandComment") {
682 auto BlockComment
= std::make_unique
<TagNode
>(HTMLTag::TAG_DIV
);
683 BlockComment
->Children
.emplace_back(
684 std::make_unique
<TagNode
>(HTMLTag::TAG_DIV
, I
.Name
));
685 for (const auto &Child
: I
.Children
) {
686 std::unique_ptr
<HTMLNode
> Node
= genHTML(*Child
);
688 BlockComment
->Children
.emplace_back(std::move(Node
));
690 if (BlockComment
->Children
.empty())
692 return std::move(BlockComment
);
694 if (I
.Kind
== "TextComment") {
697 return std::make_unique
<TextNode
>(I
.Text
);
702 static std::unique_ptr
<TagNode
> genHTML(const std::vector
<CommentInfo
> &C
) {
703 auto CommentBlock
= std::make_unique
<TagNode
>(HTMLTag::TAG_DIV
);
704 for (const auto &Child
: C
) {
705 if (std::unique_ptr
<HTMLNode
> Node
= genHTML(Child
))
706 CommentBlock
->Children
.emplace_back(std::move(Node
));
711 static std::vector
<std::unique_ptr
<TagNode
>>
712 genHTML(const EnumInfo
&I
, const ClangDocContext
&CDCtx
) {
713 std::vector
<std::unique_ptr
<TagNode
>> Out
;
714 std::string EnumType
= I
.Scoped
? "enum class " : "enum ";
715 // Determine if enum members have comments attached
716 bool HasComments
= std::any_of(
717 I
.Members
.begin(), I
.Members
.end(),
718 [](const EnumValueInfo
&M
) { return !M
.Description
.empty(); });
719 std::unique_ptr
<TagNode
> Table
=
720 std::make_unique
<TagNode
>(HTMLTag::TAG_TABLE
);
721 std::unique_ptr
<TagNode
> THead
=
722 std::make_unique
<TagNode
>(HTMLTag::TAG_THEAD
);
723 std::unique_ptr
<TagNode
> TRow
= std::make_unique
<TagNode
>(HTMLTag::TAG_TR
);
724 std::unique_ptr
<TagNode
> TD
=
725 std::make_unique
<TagNode
>(HTMLTag::TAG_TH
, EnumType
+ I
.Name
);
726 // Span 3 columns if enum has comments
727 TD
->Attributes
.emplace_back("colspan", HasComments
? "3" : "2");
729 Table
->Attributes
.emplace_back("id", llvm::toHex(llvm::toStringRef(I
.USR
)));
730 TRow
->Children
.emplace_back(std::move(TD
));
731 THead
->Children
.emplace_back(std::move(TRow
));
732 Table
->Children
.emplace_back(std::move(THead
));
734 if (std::unique_ptr
<TagNode
> Node
= genEnumMembersBlock(I
.Members
))
735 Table
->Children
.emplace_back(std::move(Node
));
737 Out
.emplace_back(std::move(Table
));
740 if (!CDCtx
.RepositoryUrl
)
741 Out
.emplace_back(writeFileDefinition(*I
.DefLoc
));
744 writeFileDefinition(*I
.DefLoc
, StringRef
{*CDCtx
.RepositoryUrl
}));
747 std::string Description
;
748 if (!I
.Description
.empty())
749 Out
.emplace_back(genHTML(I
.Description
));
754 static std::vector
<std::unique_ptr
<TagNode
>>
755 genHTML(const FunctionInfo
&I
, const ClangDocContext
&CDCtx
,
756 StringRef ParentInfoDir
) {
757 std::vector
<std::unique_ptr
<TagNode
>> Out
;
758 Out
.emplace_back(std::make_unique
<TagNode
>(HTMLTag::TAG_H3
, I
.Name
));
759 // USR is used as id for functions instead of name to disambiguate function
761 Out
.back()->Attributes
.emplace_back("id",
762 llvm::toHex(llvm::toStringRef(I
.USR
)));
764 Out
.emplace_back(std::make_unique
<TagNode
>(HTMLTag::TAG_P
));
765 auto &FunctionHeader
= Out
.back();
767 std::string Access
= getAccessSpelling(I
.Access
).str();
769 FunctionHeader
->Children
.emplace_back(
770 std::make_unique
<TextNode
>(Access
+ " "));
771 if (I
.ReturnType
.Type
.Name
!= "") {
772 FunctionHeader
->Children
.emplace_back(
773 genReference(I
.ReturnType
.Type
, ParentInfoDir
));
774 FunctionHeader
->Children
.emplace_back(std::make_unique
<TextNode
>(" "));
776 FunctionHeader
->Children
.emplace_back(
777 std::make_unique
<TextNode
>(I
.Name
+ "("));
779 for (const auto &P
: I
.Params
) {
780 if (&P
!= I
.Params
.begin())
781 FunctionHeader
->Children
.emplace_back(std::make_unique
<TextNode
>(", "));
782 FunctionHeader
->Children
.emplace_back(genReference(P
.Type
, ParentInfoDir
));
783 FunctionHeader
->Children
.emplace_back(
784 std::make_unique
<TextNode
>(" " + P
.Name
));
786 FunctionHeader
->Children
.emplace_back(std::make_unique
<TextNode
>(")"));
789 if (!CDCtx
.RepositoryUrl
)
790 Out
.emplace_back(writeFileDefinition(*I
.DefLoc
));
792 Out
.emplace_back(writeFileDefinition(
793 *I
.DefLoc
, StringRef
{*CDCtx
.RepositoryUrl
}));
796 std::string Description
;
797 if (!I
.Description
.empty())
798 Out
.emplace_back(genHTML(I
.Description
));
803 static std::vector
<std::unique_ptr
<TagNode
>>
804 genHTML(const NamespaceInfo
&I
, Index
&InfoIndex
, const ClangDocContext
&CDCtx
,
805 std::string
&InfoTitle
) {
806 std::vector
<std::unique_ptr
<TagNode
>> Out
;
807 if (I
.Name
.str() == "")
808 InfoTitle
= "Global Namespace";
810 InfoTitle
= ("namespace " + I
.Name
).str();
812 Out
.emplace_back(std::make_unique
<TagNode
>(HTMLTag::TAG_H1
, InfoTitle
));
814 std::string Description
;
815 if (!I
.Description
.empty())
816 Out
.emplace_back(genHTML(I
.Description
));
818 llvm::SmallString
<64> BasePath
= I
.getRelativeFilePath("");
820 std::vector
<std::unique_ptr
<TagNode
>> ChildNamespaces
=
821 genReferencesBlock(I
.Children
.Namespaces
, "Namespaces", BasePath
);
822 appendVector(std::move(ChildNamespaces
), Out
);
823 std::vector
<std::unique_ptr
<TagNode
>> ChildRecords
=
824 genReferencesBlock(I
.Children
.Records
, "Records", BasePath
);
825 appendVector(std::move(ChildRecords
), Out
);
827 std::vector
<std::unique_ptr
<TagNode
>> ChildFunctions
=
828 genFunctionsBlock(I
.Children
.Functions
, CDCtx
, BasePath
);
829 appendVector(std::move(ChildFunctions
), Out
);
830 std::vector
<std::unique_ptr
<TagNode
>> ChildEnums
=
831 genEnumsBlock(I
.Children
.Enums
, CDCtx
);
832 appendVector(std::move(ChildEnums
), Out
);
834 if (!I
.Children
.Namespaces
.empty())
835 InfoIndex
.Children
.emplace_back("Namespaces", "Namespaces");
836 if (!I
.Children
.Records
.empty())
837 InfoIndex
.Children
.emplace_back("Records", "Records");
838 if (!I
.Children
.Functions
.empty())
839 InfoIndex
.Children
.emplace_back(
840 genInfoIndexItem(I
.Children
.Functions
, "Functions"));
841 if (!I
.Children
.Enums
.empty())
842 InfoIndex
.Children
.emplace_back(
843 genInfoIndexItem(I
.Children
.Enums
, "Enums"));
848 static std::vector
<std::unique_ptr
<TagNode
>>
849 genHTML(const RecordInfo
&I
, Index
&InfoIndex
, const ClangDocContext
&CDCtx
,
850 std::string
&InfoTitle
) {
851 std::vector
<std::unique_ptr
<TagNode
>> Out
;
852 InfoTitle
= (getTagType(I
.TagType
) + " " + I
.Name
).str();
853 Out
.emplace_back(std::make_unique
<TagNode
>(HTMLTag::TAG_H1
, InfoTitle
));
856 if (!CDCtx
.RepositoryUrl
)
857 Out
.emplace_back(writeFileDefinition(*I
.DefLoc
));
859 Out
.emplace_back(writeFileDefinition(
860 *I
.DefLoc
, StringRef
{*CDCtx
.RepositoryUrl
}));
863 std::string Description
;
864 if (!I
.Description
.empty())
865 Out
.emplace_back(genHTML(I
.Description
));
867 std::vector
<std::unique_ptr
<HTMLNode
>> Parents
=
868 genReferenceList(I
.Parents
, I
.Path
);
869 std::vector
<std::unique_ptr
<HTMLNode
>> VParents
=
870 genReferenceList(I
.VirtualParents
, I
.Path
);
871 if (!Parents
.empty() || !VParents
.empty()) {
872 Out
.emplace_back(std::make_unique
<TagNode
>(HTMLTag::TAG_P
));
873 auto &PBody
= Out
.back();
874 PBody
->Children
.emplace_back(std::make_unique
<TextNode
>("Inherits from "));
876 appendVector(std::move(VParents
), PBody
->Children
);
877 else if (VParents
.empty())
878 appendVector(std::move(Parents
), PBody
->Children
);
880 appendVector(std::move(Parents
), PBody
->Children
);
881 PBody
->Children
.emplace_back(std::make_unique
<TextNode
>(", "));
882 appendVector(std::move(VParents
), PBody
->Children
);
886 std::vector
<std::unique_ptr
<TagNode
>> Members
=
887 genRecordMembersBlock(I
.Members
, I
.Path
);
888 appendVector(std::move(Members
), Out
);
889 std::vector
<std::unique_ptr
<TagNode
>> ChildRecords
=
890 genReferencesBlock(I
.Children
.Records
, "Records", I
.Path
);
891 appendVector(std::move(ChildRecords
), Out
);
893 std::vector
<std::unique_ptr
<TagNode
>> ChildFunctions
=
894 genFunctionsBlock(I
.Children
.Functions
, CDCtx
, I
.Path
);
895 appendVector(std::move(ChildFunctions
), Out
);
896 std::vector
<std::unique_ptr
<TagNode
>> ChildEnums
=
897 genEnumsBlock(I
.Children
.Enums
, CDCtx
);
898 appendVector(std::move(ChildEnums
), Out
);
900 if (!I
.Members
.empty())
901 InfoIndex
.Children
.emplace_back("Members", "Members");
902 if (!I
.Children
.Records
.empty())
903 InfoIndex
.Children
.emplace_back("Records", "Records");
904 if (!I
.Children
.Functions
.empty())
905 InfoIndex
.Children
.emplace_back(
906 genInfoIndexItem(I
.Children
.Functions
, "Functions"));
907 if (!I
.Children
.Enums
.empty())
908 InfoIndex
.Children
.emplace_back(
909 genInfoIndexItem(I
.Children
.Enums
, "Enums"));
914 static std::vector
<std::unique_ptr
<TagNode
>>
915 genHTML(const TypedefInfo
&I
, const ClangDocContext
&CDCtx
,
916 std::string
&InfoTitle
) {
917 // TODO support typedefs in HTML.
921 /// Generator for HTML documentation.
922 class HTMLGenerator
: public Generator
{
924 static const char *Format
;
926 llvm::Error
generateDocs(StringRef RootDir
,
927 llvm::StringMap
<std::unique_ptr
<doc::Info
>> Infos
,
928 const ClangDocContext
&CDCtx
) override
;
929 llvm::Error
createResources(ClangDocContext
&CDCtx
) override
;
930 llvm::Error
generateDocForInfo(Info
*I
, llvm::raw_ostream
&OS
,
931 const ClangDocContext
&CDCtx
) override
;
934 const char *HTMLGenerator::Format
= "html";
937 HTMLGenerator::generateDocs(StringRef RootDir
,
938 llvm::StringMap
<std::unique_ptr
<doc::Info
>> Infos
,
939 const ClangDocContext
&CDCtx
) {
940 // Track which directories we already tried to create.
941 llvm::StringSet
<> CreatedDirs
;
943 // Collect all output by file name and create the nexessary directories.
944 llvm::StringMap
<std::vector
<doc::Info
*>> FileToInfos
;
945 for (const auto &Group
: Infos
) {
946 doc::Info
*Info
= Group
.getValue().get();
948 llvm::SmallString
<128> Path
;
949 llvm::sys::path::native(RootDir
, Path
);
950 llvm::sys::path::append(Path
, Info
->getRelativeFilePath(""));
951 if (!CreatedDirs
.contains(Path
)) {
952 if (std::error_code Err
= llvm::sys::fs::create_directories(Path
);
953 Err
!= std::error_code()) {
954 return llvm::createStringError(Err
, "Failed to create directory '%s'.",
957 CreatedDirs
.insert(Path
);
960 llvm::sys::path::append(Path
, Info
->getFileBaseName() + ".html");
961 FileToInfos
[Path
].push_back(Info
);
964 for (const auto &Group
: FileToInfos
) {
965 std::error_code FileErr
;
966 llvm::raw_fd_ostream
InfoOS(Group
.getKey(), FileErr
,
967 llvm::sys::fs::OF_None
);
969 return llvm::createStringError(FileErr
, "Error opening file '%s'",
970 Group
.getKey().str().c_str());
973 // TODO: https://github.com/llvm/llvm-project/issues/59073
974 // If there are multiple Infos for this file name (for example, template
975 // specializations), this will generate multiple complete web pages (with
976 // <DOCTYPE> and <title>, etc.) concatenated together. This generator needs
977 // some refactoring to be able to output the headers separately from the
979 for (const auto &Info
: Group
.getValue()) {
980 if (llvm::Error Err
= generateDocForInfo(Info
, InfoOS
, CDCtx
)) {
986 return llvm::Error::success();
989 llvm::Error
HTMLGenerator::generateDocForInfo(Info
*I
, llvm::raw_ostream
&OS
,
990 const ClangDocContext
&CDCtx
) {
991 std::string InfoTitle
;
992 std::vector
<std::unique_ptr
<TagNode
>> MainContentNodes
;
995 case InfoType::IT_namespace
:
996 MainContentNodes
= genHTML(*static_cast<clang::doc::NamespaceInfo
*>(I
),
997 InfoIndex
, CDCtx
, InfoTitle
);
999 case InfoType::IT_record
:
1000 MainContentNodes
= genHTML(*static_cast<clang::doc::RecordInfo
*>(I
),
1001 InfoIndex
, CDCtx
, InfoTitle
);
1003 case InfoType::IT_enum
:
1004 MainContentNodes
= genHTML(*static_cast<clang::doc::EnumInfo
*>(I
), CDCtx
);
1006 case InfoType::IT_function
:
1008 genHTML(*static_cast<clang::doc::FunctionInfo
*>(I
), CDCtx
, "");
1010 case InfoType::IT_typedef
:
1012 genHTML(*static_cast<clang::doc::TypedefInfo
*>(I
), CDCtx
, InfoTitle
);
1014 case InfoType::IT_default
:
1015 return llvm::createStringError(llvm::inconvertibleErrorCode(),
1016 "unexpected info type");
1019 HTMLFile F
= genInfoFile(InfoTitle
, I
->getRelativeFilePath(""),
1020 MainContentNodes
, InfoIndex
, CDCtx
);
1023 return llvm::Error::success();
1026 static std::string
getRefType(InfoType IT
) {
1028 case InfoType::IT_default
:
1030 case InfoType::IT_namespace
:
1032 case InfoType::IT_record
:
1034 case InfoType::IT_function
:
1036 case InfoType::IT_enum
:
1038 case InfoType::IT_typedef
:
1041 llvm_unreachable("Unknown InfoType");
1044 static llvm::Error
serializeIndex(ClangDocContext
&CDCtx
) {
1046 std::error_code FileErr
;
1047 llvm::SmallString
<128> FilePath
;
1048 llvm::sys::path::native(CDCtx
.OutDirectory
, FilePath
);
1049 llvm::sys::path::append(FilePath
, "index_json.js");
1050 llvm::raw_fd_ostream
OS(FilePath
, FileErr
, llvm::sys::fs::OF_None
);
1051 if (FileErr
!= OK
) {
1052 return llvm::createStringError(llvm::inconvertibleErrorCode(),
1053 "error creating index file: " +
1056 llvm::SmallString
<128> RootPath(CDCtx
.OutDirectory
);
1057 if (llvm::sys::path::is_relative(RootPath
)) {
1058 llvm::sys::fs::make_absolute(RootPath
);
1060 // Replace the escaped characters with a forward slash. It shouldn't matter
1061 // when rendering the webpage in a web browser. This helps to prevent the
1062 // JavaScript from escaping characters incorrectly, and introducing bad paths
1064 std::string RootPathEscaped
= RootPath
.str().str();
1065 std::replace(RootPathEscaped
.begin(), RootPathEscaped
.end(), '\\', '/');
1066 OS
<< "var RootPath = \"" << RootPathEscaped
<< "\";\n";
1069 llvm::json::OStream
J(OS
, 2);
1070 std::function
<void(Index
)> IndexToJSON
= [&](const Index
&I
) {
1072 J
.attribute("USR", toHex(llvm::toStringRef(I
.USR
)));
1073 J
.attribute("Name", I
.Name
);
1074 J
.attribute("RefType", getRefType(I
.RefType
));
1075 J
.attribute("Path", I
.getRelativeFilePath(""));
1076 J
.attributeArray("Children", [&] {
1077 for (const Index
&C
: I
.Children
)
1082 OS
<< "async function LoadIndex() {\nreturn";
1083 IndexToJSON(CDCtx
.Idx
);
1085 return llvm::Error::success();
1088 // Generates a main HTML node that has the main content of the file that shows
1089 // only the general index
1090 // It contains the general index with links to all the generated files
1091 static std::unique_ptr
<TagNode
> genIndexFileMainNode() {
1092 auto MainNode
= std::make_unique
<TagNode
>(HTMLTag::TAG_MAIN
);
1094 auto LeftSidebarNode
= std::make_unique
<TagNode
>(HTMLTag::TAG_DIV
);
1095 LeftSidebarNode
->Attributes
.emplace_back("id", "sidebar-left");
1096 LeftSidebarNode
->Attributes
.emplace_back("path", "");
1097 LeftSidebarNode
->Attributes
.emplace_back(
1098 "class", "col-xs-6 col-sm-3 col-md-2 sidebar sidebar-offcanvas-left");
1099 LeftSidebarNode
->Attributes
.emplace_back("style", "flex: 0 100%;");
1101 MainNode
->Children
.emplace_back(std::move(LeftSidebarNode
));
1106 static llvm::Error
genIndex(const ClangDocContext
&CDCtx
) {
1107 std::error_code FileErr
, OK
;
1108 llvm::SmallString
<128> IndexPath
;
1109 llvm::sys::path::native(CDCtx
.OutDirectory
, IndexPath
);
1110 llvm::sys::path::append(IndexPath
, "index.html");
1111 llvm::raw_fd_ostream
IndexOS(IndexPath
, FileErr
, llvm::sys::fs::OF_None
);
1112 if (FileErr
!= OK
) {
1113 return llvm::createStringError(llvm::inconvertibleErrorCode(),
1114 "error creating main index: " +
1120 std::vector
<std::unique_ptr
<TagNode
>> HeadNodes
=
1121 genFileHeadNodes("Index", "", CDCtx
);
1122 std::unique_ptr
<TagNode
> HeaderNode
= genFileHeaderNode(CDCtx
.ProjectName
);
1123 std::unique_ptr
<TagNode
> MainNode
= genIndexFileMainNode();
1124 std::unique_ptr
<TagNode
> FooterNode
= genFileFooterNode();
1126 appendVector(std::move(HeadNodes
), F
.Children
);
1127 F
.Children
.emplace_back(std::move(HeaderNode
));
1128 F
.Children
.emplace_back(std::move(MainNode
));
1129 F
.Children
.emplace_back(std::move(FooterNode
));
1133 return llvm::Error::success();
1136 static llvm::Error
copyFile(StringRef FilePath
, StringRef OutDirectory
) {
1137 llvm::SmallString
<128> PathWrite
;
1138 llvm::sys::path::native(OutDirectory
, PathWrite
);
1139 llvm::sys::path::append(PathWrite
, llvm::sys::path::filename(FilePath
));
1140 llvm::SmallString
<128> PathRead
;
1141 llvm::sys::path::native(FilePath
, PathRead
);
1143 std::error_code FileErr
= llvm::sys::fs::copy_file(PathRead
, PathWrite
);
1144 if (FileErr
!= OK
) {
1145 return llvm::createStringError(llvm::inconvertibleErrorCode(),
1146 "error creating file " +
1147 llvm::sys::path::filename(FilePath
) +
1148 ": " + FileErr
.message() + "\n");
1150 return llvm::Error::success();
1153 llvm::Error
HTMLGenerator::createResources(ClangDocContext
&CDCtx
) {
1154 auto Err
= serializeIndex(CDCtx
);
1157 Err
= genIndex(CDCtx
);
1161 for (const auto &FilePath
: CDCtx
.UserStylesheets
) {
1162 Err
= copyFile(FilePath
, CDCtx
.OutDirectory
);
1166 for (const auto &FilePath
: CDCtx
.JsScripts
) {
1167 Err
= copyFile(FilePath
, CDCtx
.OutDirectory
);
1171 return llvm::Error::success();
1174 static GeneratorRegistry::Add
<HTMLGenerator
> HTML(HTMLGenerator::Format
,
1175 "Generator for HTML output.");
1177 // This anchor is used to force the linker to link in the generated object
1178 // file and thus register the generator.
1179 volatile int HTMLGeneratorAnchorSource
= 0;
1182 } // namespace clang