clang-tools 23.0.0git
MDGenerator.cpp
Go to the documentation of this file.
1//===-- MDGenerator.cpp - Markdown Generator --------------------*- 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 "Generators.h"
10#include "Representation.h"
11#include "llvm/ADT/ArrayRef.h"
12#include "llvm/ADT/StringRef.h"
13#include "llvm/Support/FileSystem.h"
14#include "llvm/Support/FormatVariadic.h"
15#include "llvm/Support/Path.h"
16#include "llvm/Support/raw_ostream.h"
17#include <string>
18
19using namespace llvm;
20using namespace clang::doc;
21
22// Markdown generation
23
24static std::string genItalic(const Twine &Text) {
25 return "*" + Text.str() + "*";
26}
27
28static std::string genEmphasis(const Twine &Text) {
29 return "**" + Text.str() + "**";
30}
31
32static std::string genReferenceList(llvm::ArrayRef<Reference> Refs) {
33 std::string Buffer;
34 llvm::raw_string_ostream Stream(Buffer);
35 for (const auto &R : Refs) {
36 if (&R != Refs.begin())
37 Stream << ", ";
38 Stream << R.Name;
39 }
40 return Stream.str();
41}
42
43static void writeLine(const Twine &Text, raw_ostream &OS) {
44 OS << Text << "\n\n";
45}
46
47static void writeNewLine(raw_ostream &OS) { OS << "\n\n"; }
48
49static void writeHeader(const Twine &Text, unsigned int Num, raw_ostream &OS) {
50 OS << std::string(Num, '#') + " " + Text << "\n\n";
51}
52
53static void writeSourceFileRef(const ClangDocContext &CDCtx, const Location &L,
54 raw_ostream &OS) {
55
56 if (!CDCtx.RepositoryUrl) {
57 OS << "*Defined at " << L.Filename << "#"
58 << std::to_string(L.StartLineNumber) << "*";
59 } else {
60
61 OS << formatv("*Defined at [#{0}{1}{2}](#{0}{1}{3})*",
62 CDCtx.RepositoryLinePrefix.value_or(""), L.StartLineNumber,
63 L.Filename, *CDCtx.RepositoryUrl);
64 }
65 OS << "\n\n";
66}
67
68/// Writer for writing comments to a table cell in MD.
69///
70/// The writer traverses the comments recursively and outputs the
71/// comments into a stream.
72/// The formatter inserts single/double line breaks to retain the comment
73/// structure.
74///
75/// Usage :
76/// Initialize an object with a llvm::raw_ostream to output into.
77/// Call the write(C) function with an array of Comments 'C'.
79public:
80 explicit TableCommentWriter(llvm::raw_ostream &OS) : OS(OS) {}
81
82 void write(const DocList<CommentInfo> &Comments) {
83 for (const auto &C : Comments)
84 writeTableSafeComment(C);
85
86 if (!Started)
87 OS << "--";
88 }
89
90private:
91 /// This function inserts breaks into the stream.
92 ///
93 /// We add a double break in between paragraphs.
94 /// Inside a paragraph, a single break between lines is maintained.
95 void insertSeparator() {
96 if (!Started)
97 return;
98 if (NeedsParagraphBreak) {
99 OS << "<br><br>";
100 NeedsParagraphBreak = false;
101 } else {
102 OS << "<br>";
103 }
104 }
105
106 /// This function processes every comment and its children recursively.
107 void writeTableSafeComment(const CommentInfo &I) {
108 switch (I.Kind) {
109 case CommentKind::CK_FullComment:
110 for (const auto &Child : I.Children)
111 writeTableSafeComment(Child);
112 break;
113
114 case CommentKind::CK_ParagraphComment:
115 for (const auto &Child : I.Children)
116 writeTableSafeComment(Child);
117 // Next content after a paragraph needs a break
118 NeedsParagraphBreak = true;
119 break;
120
121 case CommentKind::CK_TextComment:
122 if (!I.Text.empty()) {
123 insertSeparator();
124 OS << I.Text;
125 Started = true;
126 }
127 break;
128
129 // Handle other comment types (BlockCommand, InlineCommand, etc.)
130 default:
131 for (const auto &Child : I.Children)
132 writeTableSafeComment(Child);
133 break;
134 }
135 }
136
137 llvm::raw_ostream &OS;
138 bool Started = false;
139 bool NeedsParagraphBreak = false;
140};
141
142static void maybeWriteSourceFileRef(llvm::raw_ostream &OS,
143 const ClangDocContext &CDCtx,
144 const std::optional<Location> &DefLoc) {
145 if (DefLoc)
146 writeSourceFileRef(CDCtx, *DefLoc, OS);
147}
148
149static void writeDescription(const CommentInfo &I, raw_ostream &OS) {
150 switch (I.Kind) {
152 for (const auto &Child : I.Children)
153 writeDescription(Child, OS);
154 break;
155
157 for (const auto &Child : I.Children)
158 writeDescription(Child, OS);
159 writeNewLine(OS);
160 break;
161
163 OS << genEmphasis(I.Name) << " ";
164 for (const auto &Child : I.Children)
165 writeDescription(Child, OS);
166 break;
167
169 OS << genEmphasis(I.Name) << " " << I.Text;
170 break;
171
174 std::string Direction = I.Explicit ? (" " + I.Direction).str() : "";
175 OS << genEmphasis(I.ParamName) << I.Text << Direction << " ";
176 for (const auto &Child : I.Children)
177 writeDescription(Child, OS);
178 break;
179 }
180
182 for (const auto &Child : I.Children)
183 writeDescription(Child, OS);
184 break;
185
188 OS << I.Text;
189 writeNewLine(OS);
190 break;
191
193 if (I.AttrKeys.size() != I.AttrValues.size())
194 return;
195 std::string Buffer;
196 llvm::raw_string_ostream Attrs(Buffer);
197 for (unsigned Idx = 0; Idx < I.AttrKeys.size(); ++Idx)
198 Attrs << " \"" << I.AttrKeys[Idx] << "=" << I.AttrValues[Idx] << "\"";
199
200 std::string CloseTag = I.SelfClosing ? "/>" : ">";
201 writeLine("<" + I.Name + Attrs.str() + CloseTag, OS);
202 break;
203 }
204
206 writeLine("</" + I.Name + ">", OS);
207 break;
208
210 OS << I.Text;
211 break;
212
214 OS << "Unknown comment kind: " << static_cast<int>(I.Kind) << ".\n\n";
215 break;
216 }
217}
218
219static void writeNameLink(const StringRef &CurrentPath, const Reference &R,
220 llvm::raw_ostream &OS) {
221 llvm::SmallString<64> Path = R.getRelativeFilePath(CurrentPath);
222 // Paths in Markdown use POSIX separators.
223 llvm::sys::path::native(Path, llvm::sys::path::Style::posix);
224 llvm::sys::path::append(Path, llvm::sys::path::Style::posix,
225 R.getFileBaseName() + ".md");
226 OS << "[" << R.Name << "](" << Path << ")";
227}
228
229static void genMarkdown(const ClangDocContext &CDCtx, const EnumInfo &I,
230 llvm::raw_ostream &OS) {
231 OS << "| enum ";
232 if (I.Scoped)
233 OS << "class ";
234 OS << (I.Name.empty() ? "(unnamed)" : StringRef(I.Name)) << " ";
235 if (I.BaseType && !I.BaseType->Type.QualName.empty()) {
236 OS << ": " << I.BaseType->Type.QualName << " ";
237 }
238 OS << "|\n\n";
239
240 OS << "| Name | Value |";
241 if (!I.Members.empty()) {
242 bool HasComments = false;
243 for (const auto &Member : I.Members) {
244 if (!Member.Description.empty()) {
245 HasComments = true;
246 OS << " Comments |";
247 break;
248 }
249 }
250 OS << "\n|---|---|";
251 if (HasComments)
252 OS << "---|";
253 OS << "\n";
254 for (const auto &N : I.Members) {
255 OS << "| " << N.Name << " ";
256 if (!N.Value.empty())
257 OS << "| " << N.Value << " ";
258 if (HasComments) {
259 OS << "| ";
260 TableCommentWriter CommentWriter(OS);
261 CommentWriter.write(N.Description);
262 OS << " ";
263 }
264 OS << "|\n";
265 }
266 }
267 OS << "\n";
268
269 maybeWriteSourceFileRef(OS, CDCtx, I.DefLoc);
270
271 for (const auto &C : I.Description)
272 writeDescription(C, OS);
273}
274
275static void genMarkdown(const ClangDocContext &CDCtx, const FunctionInfo &I,
276 llvm::raw_ostream &OS) {
277 std::string Buffer;
278 llvm::raw_string_ostream Stream(Buffer);
279 bool First = true;
280 for (const auto &N : I.Params) {
281 if (!First)
282 Stream << ", ";
283 Stream << N.Type.QualName + " " + N.Name;
284 First = false;
285 }
286 writeHeader(I.Name, 3, OS);
287 StringRef Access = getAccessSpelling(I.Access);
288 writeLine(genItalic(Twine(Access) + (!Access.empty() ? " " : "") +
289 (I.IsStatic ? "static " : "") +
290 I.ReturnType.Type.QualName.str() + " " + I.Name.str() +
291 "(" + Twine(Stream.str()) + ")"),
292 OS);
293
294 maybeWriteSourceFileRef(OS, CDCtx, I.DefLoc);
295
296 for (const auto &C : I.Description)
297 writeDescription(C, OS);
298}
299
300static void genMarkdown(const ClangDocContext &CDCtx, const NamespaceInfo &I,
301 llvm::raw_ostream &OS) {
302 if (I.Name == "")
303 writeHeader("Global Namespace", 1, OS);
304 else
305 writeHeader("namespace " + I.Name, 1, OS);
306 writeNewLine(OS);
307
308 if (!I.Description.empty()) {
309 for (const auto &C : I.Description)
310 writeDescription(C, OS);
311 writeNewLine(OS);
312 }
313
314 llvm::SmallString<64> BasePath = I.getRelativeFilePath("");
315
316 if (!I.Children.Namespaces.empty()) {
317 writeHeader("Namespaces", 2, OS);
318 for (const auto &R : I.Children.Namespaces) {
319 OS << "* ";
320 writeNameLink(BasePath, R, OS);
321 OS << "\n";
322 }
323 writeNewLine(OS);
324 }
325
326 if (!I.Children.Records.empty()) {
327 writeHeader("Records", 2, OS);
328 for (const auto &R : I.Children.Records) {
329 OS << "* ";
330 writeNameLink(BasePath, R, OS);
331 OS << "\n";
332 }
333 writeNewLine(OS);
334 }
335
336 if (!I.Children.Functions.empty()) {
337 writeHeader("Functions", 2, OS);
338 for (const auto &F : I.Children.Functions)
339 genMarkdown(CDCtx, F, OS);
340 writeNewLine(OS);
341 }
342 if (!I.Children.Enums.empty()) {
343 writeHeader("Enums", 2, OS);
344 for (const auto &E : I.Children.Enums)
345 genMarkdown(CDCtx, E, OS);
346 writeNewLine(OS);
347 }
348}
349
350static void genMarkdown(const ClangDocContext &CDCtx, const RecordInfo &I,
351 llvm::raw_ostream &OS) {
352 writeHeader(getTagType(I.TagType) + " " + I.Name, 1, OS);
353
354 maybeWriteSourceFileRef(OS, CDCtx, I.DefLoc);
355
356 if (!I.Description.empty()) {
357 for (const auto &C : I.Description)
358 writeDescription(C, OS);
359 writeNewLine(OS);
360 }
361
362 std::string Parents = genReferenceList(I.Parents);
363 std::string VParents = genReferenceList(I.VirtualParents);
364 if (!Parents.empty() || !VParents.empty()) {
365 if (Parents.empty())
366 writeLine("Inherits from " + VParents, OS);
367 else if (VParents.empty())
368 writeLine("Inherits from " + Parents, OS);
369 else
370 writeLine("Inherits from " + Parents + ", " + VParents, OS);
371 writeNewLine(OS);
372 }
373
374 if (!I.Members.empty()) {
375 writeHeader("Members", 2, OS);
376 for (const auto &Member : I.Members) {
377 StringRef Access = getAccessSpelling(Member.Access);
378 writeLine(Twine(Access) + (Access.empty() ? "" : " ") +
379 (Member.IsStatic ? "static " : "") +
380 Member.Type.Name.str() + " " + Member.Name.str(),
381 OS);
382 }
383 writeNewLine(OS);
384 }
385
386 if (!I.Children.Records.empty()) {
387 writeHeader("Records", 2, OS);
388 for (const auto &R : I.Children.Records)
389 writeLine(R->Name, OS);
390 writeNewLine(OS);
391 }
392 if (!I.Children.Functions.empty()) {
393 writeHeader("Functions", 2, OS);
394 for (const auto &F : I.Children.Functions)
395 genMarkdown(CDCtx, F, OS);
396 writeNewLine(OS);
397 }
398 if (!I.Children.Enums.empty()) {
399 writeHeader("Enums", 2, OS);
400 for (const auto &E : I.Children.Enums)
401 genMarkdown(CDCtx, E, OS);
402 writeNewLine(OS);
403 }
404}
405
406static void genMarkdown(const ClangDocContext &CDCtx, const TypedefInfo &I,
407 llvm::raw_ostream &OS) {
408 // TODO support typedefs in markdown.
409}
410
411static void serializeReference(llvm::raw_fd_ostream &OS, const Index &I,
412 int Level) {
413 // Write out the heading level starting at ##
414 OS << "##" << std::string(Level, '#') << " ";
415 writeNameLink("", I, OS);
416 OS << "\n";
417}
418
419static llvm::Error serializeIndex(ClangDocContext &CDCtx) {
420 std::error_code FileErr;
421 llvm::SmallString<128> FilePath;
422 llvm::sys::path::native(CDCtx.OutDirectory, FilePath);
423 llvm::sys::path::append(FilePath, "all_files.md");
424 llvm::raw_fd_ostream OS(FilePath, FileErr, llvm::sys::fs::OF_Text);
425 if (FileErr)
426 return llvm::createStringError(llvm::inconvertibleErrorCode(),
427 "error creating index file: " +
428 FileErr.message());
429
430 CDCtx.Idx.sort();
431 OS << "# All Files";
432 if (!CDCtx.ProjectName.empty())
433 OS << " for " << CDCtx.ProjectName;
434 OS << "\n\n";
435
436 std::vector<const Index *> Children = CDCtx.Idx.getSortedChildren();
437 for (const auto *C : Children)
438 serializeReference(OS, *C, 0);
439
440 return llvm::Error::success();
441}
442
443static llvm::Error genIndex(ClangDocContext &CDCtx) {
444 std::error_code FileErr;
445 llvm::SmallString<128> FilePath;
446 llvm::sys::path::native(CDCtx.OutDirectory, FilePath);
447 llvm::sys::path::append(FilePath, "index.md");
448 llvm::raw_fd_ostream OS(FilePath, FileErr, llvm::sys::fs::OF_Text);
449 if (FileErr)
450 return llvm::createStringError(llvm::inconvertibleErrorCode(),
451 "error creating index file: " +
452 FileErr.message());
453 CDCtx.Idx.sort();
454 OS << "# " << CDCtx.ProjectName << " C/C++ Reference\n\n";
455 std::vector<const Index *> Children = CDCtx.Idx.getSortedChildren();
456 for (const auto *C : Children) {
457 if (!C->Children.empty()) {
458 const char *Type;
459 switch (C->RefType) {
461 Type = "Namespace";
462 break;
464 Type = "Type";
465 break;
467 Type = "Enum";
468 break;
470 Type = "Function";
471 break;
473 Type = "Typedef";
474 break;
476 Type = "Concept";
477 break;
479 Type = "Variable";
480 break;
482 Type = "Friend";
483 break;
485 Type = "Other";
486 }
487 OS << "* " << Type << ": [" << C->Name << "](";
488 if (!C->Path.empty())
489 OS << C->Path << "/";
490 OS << C->Name << ")\n";
491 }
492 }
493 return llvm::Error::success();
494}
495
496namespace {
497/// Generator for Markdown documentation.
498class MDGenerator : public Generator {
499public:
500 static StringRef Format;
501
502 llvm::Error generateDocumentation(StringRef RootDir,
503 llvm::StringMap<Info *> Infos,
504 const ClangDocContext &CDCtx,
505 std::string DirName) override;
506 llvm::Error createResources(ClangDocContext &CDCtx) override;
507 llvm::Error generateDocForInfo(Info *I, llvm::raw_ostream &OS,
508 const ClangDocContext &CDCtx) override;
509};
510} // namespace
511
512StringRef MDGenerator::Format = "md";
513
514llvm::Error MDGenerator::generateDocumentation(StringRef RootDir,
515 llvm::StringMap<Info *> Infos,
516 const ClangDocContext &CDCtx,
517 std::string DirName) {
518 // Track which directories we already tried to create.
519 llvm::StringSet<> CreatedDirs;
520
521 // Collect all output by file name and create the necessary directories.
522 llvm::StringMap<std::vector<Info *>> FileToInfos;
523 for (const auto &Group : Infos) {
524 Info *Info = Group.getValue();
525
526 llvm::SmallString<128> Path;
527 llvm::sys::path::native(RootDir, Path);
528 llvm::sys::path::append(Path, Info->getRelativeFilePath(""));
529 if (!CreatedDirs.contains(Path)) {
530 if (std::error_code Err = llvm::sys::fs::create_directories(Path);
531 Err != std::error_code()) {
532 return llvm::createStringError(Err, "Failed to create directory '%s'.",
533 Path.c_str());
534 }
535 CreatedDirs.insert(Path);
536 }
537
538 llvm::sys::path::append(Path, Info->getFileBaseName() + ".md");
539 FileToInfos[Path].push_back(Info);
540 }
541
542 for (const auto &Group : FileToInfos) {
543 std::error_code FileErr;
544 llvm::raw_fd_ostream InfoOS(Group.getKey(), FileErr,
545 llvm::sys::fs::OF_Text);
546 if (FileErr) {
547 return llvm::createStringError(FileErr, "Error opening file '%s'",
548 Group.getKey().str().c_str());
549 }
550
551 for (const auto &Info : Group.getValue()) {
552 if (llvm::Error Err = generateDocForInfo(Info, InfoOS, CDCtx)) {
553 return Err;
554 }
555 }
556 }
557
558 return llvm::Error::success();
559}
560
561llvm::Error MDGenerator::generateDocForInfo(Info *I, llvm::raw_ostream &OS,
562 const ClangDocContext &CDCtx) {
563 switch (I->IT) {
564 case InfoType::IT_namespace:
565 genMarkdown(CDCtx, *cast<NamespaceInfo>(I), OS);
566 break;
567 case InfoType::IT_record:
568 genMarkdown(CDCtx, *cast<RecordInfo>(I), OS);
569 break;
570 case InfoType::IT_enum:
571 genMarkdown(CDCtx, *cast<EnumInfo>(I), OS);
572 break;
573 case InfoType::IT_function:
574 genMarkdown(CDCtx, *cast<FunctionInfo>(I), OS);
575 break;
576 case InfoType::IT_typedef:
577 genMarkdown(CDCtx, *cast<TypedefInfo>(I), OS);
578 break;
579 case InfoType::IT_concept:
580 case InfoType::IT_variable:
581 case InfoType::IT_friend:
582 break;
583 case InfoType::IT_default:
584 return createStringError(llvm::inconvertibleErrorCode(),
585 "unexpected InfoType");
586 }
587 return llvm::Error::success();
588}
589
590llvm::Error MDGenerator::createResources(ClangDocContext &CDCtx) {
591 // Write an all_files.md
592 auto Err = serializeIndex(CDCtx);
593 if (Err)
594 return Err;
595
596 // Generate the index page.
597 Err = genIndex(CDCtx);
598 if (Err)
599 return Err;
600
601 return llvm::Error::success();
602}
603
604static GeneratorRegistry::Add<MDGenerator> MD(MDGenerator::Format,
605 "Generator for MD output.");
606
607namespace clang {
608namespace doc {
609
610// This anchor is used to force the linker to link in the generated object
611// file and thus register the generator.
612volatile int MDGeneratorAnchorSource = 0;
613
614} // namespace doc
615} // namespace clang
static std::string genItalic(const Twine &Text)
static std::string genEmphasis(const Twine &Text)
static void writeSourceFileRef(const ClangDocContext &CDCtx, const Location &L, raw_ostream &OS)
static llvm::Error genIndex(ClangDocContext &CDCtx)
static void writeLine(const Twine &Text, raw_ostream &OS)
static std::string genReferenceList(llvm::ArrayRef< Reference > Refs)
static void writeNameLink(const StringRef &CurrentPath, const Reference &R, llvm::raw_ostream &OS)
static void genMarkdown(const ClangDocContext &CDCtx, const EnumInfo &I, llvm::raw_ostream &OS)
static void serializeReference(llvm::raw_fd_ostream &OS, const Index &I, int Level)
static llvm::Error serializeIndex(ClangDocContext &CDCtx)
static void writeDescription(const CommentInfo &I, raw_ostream &OS)
static void writeNewLine(raw_ostream &OS)
static void writeHeader(const Twine &Text, unsigned int Num, raw_ostream &OS)
static GeneratorRegistry::Add< MDGenerator > MD(MDGenerator::Format, "Generator for MD output.")
static void maybeWriteSourceFileRef(llvm::raw_ostream &OS, const ClangDocContext &CDCtx, const std::optional< Location > &DefLoc)
Writer for writing comments to a table cell in MD.
TableCommentWriter(llvm::raw_ostream &OS)
void write(const DocList< CommentInfo > &Comments)
@ Info
An information message.
Definition Protocol.h:755
std::string Path
A typedef to represent a file path.
Definition Path.h:26
llvm::simple_ilist< InfoNode< T > > DocList
llvm::StringRef getTagType(TagTypeKind AS)
volatile int MDGeneratorAnchorSource
===– Representation.cpp - ClangDoc Representation --------—*- C++ -*-===//
Some operations such as code completion produce a set of candidates.
Definition Generators.h:152
std::optional< std::string > RepositoryUrl
std::optional< std::string > RepositoryLinePrefix
ArrayRef< CommentInfo > Children
ArrayRef< StringRef > AttrKeys
ArrayRef< StringRef > AttrValues
llvm::ArrayRef< EnumValueInfo > Members
std::optional< TypeInfo > BaseType
llvm::ArrayRef< FieldTypeInfo > Params
std::vector< const Index * > getSortedChildren() const
StringRef getRelativeFilePath(const StringRef &CurrentPath) const
Returns the file path for this Info relative to CurrentPath.
DocList< CommentInfo > Description
llvm::ArrayRef< Reference > VirtualParents
llvm::ArrayRef< Reference > Parents
llvm::ArrayRef< MemberTypeInfo > Members
StringRef getFileBaseName() const
Returns the basename that should be used for this Reference.
StringRef getRelativeFilePath(const StringRef &CurrentPath) const
Returns the path for this Reference relative to CurrentPath.
DocList< Reference > Records
DocList< EnumInfo > Enums
DocList< FunctionInfo > Functions
DocList< Reference > Namespaces
std::optional< Location > DefLoc