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