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