clang-tools 20.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/StringRef.h"
12#include "llvm/Support/FileSystem.h"
13#include "llvm/Support/Path.h"
14#include <string>
15
16using namespace llvm;
17
18namespace clang {
19namespace doc {
20
21// Markdown generation
22
23static std::string genItalic(const Twine &Text) {
24 return "*" + Text.str() + "*";
25}
26
27static std::string genEmphasis(const Twine &Text) {
28 return "**" + Text.str() + "**";
29}
30
31static std::string
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 writeFileDefinition(const ClangDocContext &CDCtx, const Location &L,
54 raw_ostream &OS) {
55
56 if (!CDCtx.RepositoryUrl) {
57 OS << "*Defined at " << L.Filename << "#" << std::to_string(L.LineNumber)
58 << "*";
59 } else {
60 OS << "*Defined at [" << L.Filename << "#" << std::to_string(L.LineNumber)
61 << "](" << StringRef{*CDCtx.RepositoryUrl}
62 << llvm::sys::path::relative_path(L.Filename) << "#"
63 << std::to_string(L.LineNumber) << ")"
64 << "*";
65 }
66 OS << "\n\n";
67}
68
69static void writeDescription(const CommentInfo &I, raw_ostream &OS) {
70 if (I.Kind == "FullComment") {
71 for (const auto &Child : I.Children)
72 writeDescription(*Child, OS);
73 } else if (I.Kind == "ParagraphComment") {
74 for (const auto &Child : I.Children)
75 writeDescription(*Child, OS);
77 } else if (I.Kind == "BlockCommandComment") {
78 OS << genEmphasis(I.Name);
79 for (const auto &Child : I.Children)
80 writeDescription(*Child, OS);
81 } else if (I.Kind == "InlineCommandComment") {
82 OS << genEmphasis(I.Name) << " " << I.Text;
83 } else if (I.Kind == "ParamCommandComment") {
84 std::string Direction = I.Explicit ? (" " + I.Direction).str() : "";
85 OS << genEmphasis(I.ParamName) << I.Text << Direction;
86 for (const auto &Child : I.Children)
87 writeDescription(*Child, OS);
88 } else if (I.Kind == "TParamCommandComment") {
89 std::string Direction = I.Explicit ? (" " + I.Direction).str() : "";
90 OS << genEmphasis(I.ParamName) << I.Text << Direction;
91 for (const auto &Child : I.Children)
92 writeDescription(*Child, OS);
93 } else if (I.Kind == "VerbatimBlockComment") {
94 for (const auto &Child : I.Children)
95 writeDescription(*Child, OS);
96 } else if (I.Kind == "VerbatimBlockLineComment") {
97 OS << I.Text;
99 } else if (I.Kind == "VerbatimLineComment") {
100 OS << I.Text;
102 } else if (I.Kind == "HTMLStartTagComment") {
103 if (I.AttrKeys.size() != I.AttrValues.size())
104 return;
105 std::string Buffer;
106 llvm::raw_string_ostream Attrs(Buffer);
107 for (unsigned Idx = 0; Idx < I.AttrKeys.size(); ++Idx)
108 Attrs << " \"" << I.AttrKeys[Idx] << "=" << I.AttrValues[Idx] << "\"";
109
110 std::string CloseTag = I.SelfClosing ? "/>" : ">";
111 writeLine("<" + I.Name + Attrs.str() + CloseTag, OS);
112 } else if (I.Kind == "HTMLEndTagComment") {
113 writeLine("</" + I.Name + ">", OS);
114 } else if (I.Kind == "TextComment") {
115 OS << I.Text;
116 } else {
117 OS << "Unknown comment kind: " << I.Kind << ".\n\n";
118 }
119}
120
121static void writeNameLink(const StringRef &CurrentPath, const Reference &R,
122 llvm::raw_ostream &OS) {
123 llvm::SmallString<64> Path = R.getRelativeFilePath(CurrentPath);
124 // Paths in Markdown use POSIX separators.
125 llvm::sys::path::native(Path, llvm::sys::path::Style::posix);
126 llvm::sys::path::append(Path, llvm::sys::path::Style::posix,
127 R.getFileBaseName() + ".md");
128 OS << "[" << R.Name << "](" << Path << ")";
129}
130
131static void genMarkdown(const ClangDocContext &CDCtx, const EnumInfo &I,
132 llvm::raw_ostream &OS) {
133 if (I.Scoped)
134 writeLine("| enum class " + I.Name + " |", OS);
135 else
136 writeLine("| enum " + I.Name + " |", OS);
137 writeLine("--", OS);
138
139 std::string Buffer;
140 llvm::raw_string_ostream Members(Buffer);
141 if (!I.Members.empty())
142 for (const auto &N : I.Members)
143 Members << "| " << N.Name << " |\n";
144 writeLine(Members.str(), OS);
145 if (I.DefLoc)
146 writeFileDefinition(CDCtx, *I.DefLoc, OS);
147
148 for (const auto &C : I.Description)
150}
151
152static void genMarkdown(const ClangDocContext &CDCtx, const FunctionInfo &I,
153 llvm::raw_ostream &OS) {
154 std::string Buffer;
155 llvm::raw_string_ostream Stream(Buffer);
156 bool First = true;
157 for (const auto &N : I.Params) {
158 if (!First)
159 Stream << ", ";
160 Stream << N.Type.QualName + " " + N.Name;
161 First = false;
162 }
163 writeHeader(I.Name, 3, OS);
164 std::string Access = getAccessSpelling(I.Access).str();
165 if (Access != "")
166 writeLine(genItalic(Access + " " + I.ReturnType.Type.QualName + " " +
167 I.Name + "(" + Stream.str() + ")"),
168 OS);
169 else
170 writeLine(genItalic(I.ReturnType.Type.QualName + " " + I.Name + "(" +
171 Stream.str() + ")"),
172 OS);
173 if (I.DefLoc)
174 writeFileDefinition(CDCtx, *I.DefLoc, OS);
175
176 for (const auto &C : I.Description)
178}
179
180static void genMarkdown(const ClangDocContext &CDCtx, const NamespaceInfo &I,
181 llvm::raw_ostream &OS) {
182 if (I.Name == "")
183 writeHeader("Global Namespace", 1, OS);
184 else
185 writeHeader("namespace " + I.Name, 1, OS);
187
188 if (!I.Description.empty()) {
189 for (const auto &C : I.Description)
192 }
193
194 llvm::SmallString<64> BasePath = I.getRelativeFilePath("");
195
196 if (!I.Children.Namespaces.empty()) {
197 writeHeader("Namespaces", 2, OS);
198 for (const auto &R : I.Children.Namespaces) {
199 OS << "* ";
200 writeNameLink(BasePath, R, OS);
201 OS << "\n";
202 }
204 }
205
206 if (!I.Children.Records.empty()) {
207 writeHeader("Records", 2, OS);
208 for (const auto &R : I.Children.Records) {
209 OS << "* ";
210 writeNameLink(BasePath, R, OS);
211 OS << "\n";
212 }
214 }
215
216 if (!I.Children.Functions.empty()) {
217 writeHeader("Functions", 2, OS);
218 for (const auto &F : I.Children.Functions)
219 genMarkdown(CDCtx, F, OS);
221 }
222 if (!I.Children.Enums.empty()) {
223 writeHeader("Enums", 2, OS);
224 for (const auto &E : I.Children.Enums)
225 genMarkdown(CDCtx, E, OS);
227 }
228}
229
230static void genMarkdown(const ClangDocContext &CDCtx, const RecordInfo &I,
231 llvm::raw_ostream &OS) {
232 writeHeader(getTagType(I.TagType) + " " + I.Name, 1, OS);
233 if (I.DefLoc)
234 writeFileDefinition(CDCtx, *I.DefLoc, OS);
235
236 if (!I.Description.empty()) {
237 for (const auto &C : I.Description)
240 }
241
242 std::string Parents = genReferenceList(I.Parents);
243 std::string VParents = genReferenceList(I.VirtualParents);
244 if (!Parents.empty() || !VParents.empty()) {
245 if (Parents.empty())
246 writeLine("Inherits from " + VParents, OS);
247 else if (VParents.empty())
248 writeLine("Inherits from " + Parents, OS);
249 else
250 writeLine("Inherits from " + Parents + ", " + VParents, OS);
252 }
253
254 if (!I.Members.empty()) {
255 writeHeader("Members", 2, OS);
256 for (const auto &Member : I.Members) {
257 std::string Access = getAccessSpelling(Member.Access).str();
258 if (Access != "")
259 writeLine(Access + " " + Member.Type.Name + " " + Member.Name, OS);
260 else
261 writeLine(Member.Type.Name + " " + Member.Name, OS);
262 }
264 }
265
266 if (!I.Children.Records.empty()) {
267 writeHeader("Records", 2, OS);
268 for (const auto &R : I.Children.Records)
269 writeLine(R.Name, OS);
271 }
272 if (!I.Children.Functions.empty()) {
273 writeHeader("Functions", 2, OS);
274 for (const auto &F : I.Children.Functions)
275 genMarkdown(CDCtx, F, OS);
277 }
278 if (!I.Children.Enums.empty()) {
279 writeHeader("Enums", 2, OS);
280 for (const auto &E : I.Children.Enums)
281 genMarkdown(CDCtx, E, OS);
283 }
284}
285
286static void genMarkdown(const ClangDocContext &CDCtx, const TypedefInfo &I,
287 llvm::raw_ostream &OS) {
288 // TODO support typedefs in markdown.
289}
290
291static void serializeReference(llvm::raw_fd_ostream &OS, Index &I, int Level) {
292 // Write out the heading level starting at ##
293 OS << "##" << std::string(Level, '#') << " ";
294 writeNameLink("", I, OS);
295 OS << "\n";
296}
297
298static llvm::Error serializeIndex(ClangDocContext &CDCtx) {
299 std::error_code FileErr;
300 llvm::SmallString<128> FilePath;
301 llvm::sys::path::native(CDCtx.OutDirectory, FilePath);
302 llvm::sys::path::append(FilePath, "all_files.md");
303 llvm::raw_fd_ostream OS(FilePath, FileErr, llvm::sys::fs::OF_None);
304 if (FileErr)
305 return llvm::createStringError(llvm::inconvertibleErrorCode(),
306 "error creating index file: " +
307 FileErr.message());
308
309 CDCtx.Idx.sort();
310 OS << "# All Files";
311 if (!CDCtx.ProjectName.empty())
312 OS << " for " << CDCtx.ProjectName;
313 OS << "\n\n";
314
315 for (auto C : CDCtx.Idx.Children)
317
318 return llvm::Error::success();
319}
320
321static llvm::Error genIndex(ClangDocContext &CDCtx) {
322 std::error_code FileErr;
323 llvm::SmallString<128> FilePath;
324 llvm::sys::path::native(CDCtx.OutDirectory, FilePath);
325 llvm::sys::path::append(FilePath, "index.md");
326 llvm::raw_fd_ostream OS(FilePath, FileErr, llvm::sys::fs::OF_None);
327 if (FileErr)
328 return llvm::createStringError(llvm::inconvertibleErrorCode(),
329 "error creating index file: " +
330 FileErr.message());
331 CDCtx.Idx.sort();
332 OS << "# " << CDCtx.ProjectName << " C/C++ Reference\n\n";
333 for (auto C : CDCtx.Idx.Children) {
334 if (!C.Children.empty()) {
335 const char *Type;
336 switch (C.RefType) {
338 Type = "Namespace";
339 break;
341 Type = "Type";
342 break;
344 Type = "Enum";
345 break;
347 Type = "Function";
348 break;
350 Type = "Typedef";
351 break;
353 Type = "Other";
354 }
355 OS << "* " << Type << ": [" << C.Name << "](";
356 if (!C.Path.empty())
357 OS << C.Path << "/";
358 OS << C.Name << ")\n";
359 }
360 }
361 return llvm::Error::success();
362}
363
364/// Generator for Markdown documentation.
365class MDGenerator : public Generator {
366public:
367 static const char *Format;
368
369 llvm::Error generateDocs(StringRef RootDir,
370 llvm::StringMap<std::unique_ptr<doc::Info>> Infos,
371 const ClangDocContext &CDCtx) override;
372 llvm::Error createResources(ClangDocContext &CDCtx) override;
373 llvm::Error generateDocForInfo(Info *I, llvm::raw_ostream &OS,
374 const ClangDocContext &CDCtx) override;
375};
376
377const char *MDGenerator::Format = "md";
378
379llvm::Error
381 llvm::StringMap<std::unique_ptr<doc::Info>> Infos,
382 const ClangDocContext &CDCtx) {
383 // Track which directories we already tried to create.
384 llvm::StringSet<> CreatedDirs;
385
386 // Collect all output by file name and create the necessary directories.
387 llvm::StringMap<std::vector<doc::Info *>> FileToInfos;
388 for (const auto &Group : Infos) {
389 doc::Info *Info = Group.getValue().get();
390
391 llvm::SmallString<128> Path;
392 llvm::sys::path::native(RootDir, Path);
393 llvm::sys::path::append(Path, Info->getRelativeFilePath(""));
394 if (!CreatedDirs.contains(Path)) {
395 if (std::error_code Err = llvm::sys::fs::create_directories(Path);
396 Err != std::error_code()) {
397 return llvm::createStringError(Err, "Failed to create directory '%s'.",
398 Path.c_str());
399 }
400 CreatedDirs.insert(Path);
401 }
402
403 llvm::sys::path::append(Path, Info->getFileBaseName() + ".md");
404 FileToInfos[Path].push_back(Info);
405 }
406
407 for (const auto &Group : FileToInfos) {
408 std::error_code FileErr;
409 llvm::raw_fd_ostream InfoOS(Group.getKey(), FileErr,
410 llvm::sys::fs::OF_None);
411 if (FileErr) {
412 return llvm::createStringError(FileErr, "Error opening file '%s'",
413 Group.getKey().str().c_str());
414 }
415
416 for (const auto &Info : Group.getValue()) {
417 if (llvm::Error Err = generateDocForInfo(Info, InfoOS, CDCtx)) {
418 return Err;
419 }
420 }
421 }
422
423 return llvm::Error::success();
424}
425
426llvm::Error MDGenerator::generateDocForInfo(Info *I, llvm::raw_ostream &OS,
427 const ClangDocContext &CDCtx) {
428 switch (I->IT) {
430 genMarkdown(CDCtx, *static_cast<clang::doc::NamespaceInfo *>(I), OS);
431 break;
433 genMarkdown(CDCtx, *static_cast<clang::doc::RecordInfo *>(I), OS);
434 break;
436 genMarkdown(CDCtx, *static_cast<clang::doc::EnumInfo *>(I), OS);
437 break;
439 genMarkdown(CDCtx, *static_cast<clang::doc::FunctionInfo *>(I), OS);
440 break;
442 genMarkdown(CDCtx, *static_cast<clang::doc::TypedefInfo *>(I), OS);
443 break;
445 return createStringError(llvm::inconvertibleErrorCode(),
446 "unexpected InfoType");
447 }
448 return llvm::Error::success();
449}
450
452 // Write an all_files.md
453 auto Err = serializeIndex(CDCtx);
454 if (Err)
455 return Err;
456
457 // Generate the index page.
458 Err = genIndex(CDCtx);
459 if (Err)
460 return Err;
461
462 return llvm::Error::success();
463}
464
465static GeneratorRegistry::Add<MDGenerator> MD(MDGenerator::Format,
466 "Generator for MD output.");
467
468// This anchor is used to force the linker to link in the generated object
469// file and thus register the generator.
470volatile int MDGeneratorAnchorSource = 0;
471
472} // namespace doc
473} // namespace clang
const Expr * E
llvm::raw_ostream & OS
const Criteria C
NodeType Type
std::string Text
std::vector< HeaderHandle > Path
Generator for Markdown documentation.
llvm::Error generateDocForInfo(Info *I, llvm::raw_ostream &OS, const ClangDocContext &CDCtx) override
llvm::Error createResources(ClangDocContext &CDCtx) override
llvm::Error generateDocs(StringRef RootDir, llvm::StringMap< std::unique_ptr< doc::Info > > Infos, const ClangDocContext &CDCtx) override
static const char * Format
std::string getTagType(TagTypeKind AS)
Definition: Generators.cpp:29
static void writeDescription(const CommentInfo &I, raw_ostream &OS)
Definition: MDGenerator.cpp:69
static std::vector< std::unique_ptr< HTMLNode > > genReferenceList(const llvm::SmallVectorImpl< Reference > &Refs, const StringRef &CurrentDirectory)
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)
Definition: MDGenerator.cpp:49
static void writeNameLink(const StringRef &CurrentPath, const Reference &R, llvm::raw_ostream &OS)
static llvm::Error genIndex(const ClangDocContext &CDCtx)
static void writeLine(const Twine &Text, raw_ostream &OS)
Definition: MDGenerator.cpp:43
static void serializeReference(llvm::raw_fd_ostream &OS, Index &I, int Level)
static std::unique_ptr< TagNode > writeFileDefinition(const Location &L, std::optional< StringRef > RepositoryUrl=std::nullopt)
static std::string genItalic(const Twine &Text)
Definition: MDGenerator.cpp:23
volatile int MDGeneratorAnchorSource
static std::string genEmphasis(const Twine &Text)
Definition: MDGenerator.cpp:27
static void genMarkdown(const ClangDocContext &CDCtx, const EnumInfo &I, llvm::raw_ostream &OS)
static void writeNewLine(raw_ostream &OS)
Definition: MDGenerator.cpp:47
===– Representation.cpp - ClangDoc Representation --------—*- C++ -*-===//
Some operations such as code completion produce a set of candidates.
Definition: Generators.h:58
std::optional< std::string > RepositoryUrl
SmallString< 8 > Direction
SmallString< 16 > Kind
std::vector< std::unique_ptr< CommentInfo > > Children
llvm::SmallVector< SmallString< 16 >, 4 > AttrValues
SmallString< 16 > Name
SmallString< 64 > Text
llvm::SmallVector< SmallString< 16 >, 4 > AttrKeys
SmallString< 16 > ParamName
llvm::SmallVector< EnumValueInfo, 4 > Members
llvm::SmallVector< FieldTypeInfo, 4 > Params
std::vector< Index > Children
A base struct for Infos.
SmallString< 16 > Name
llvm::SmallString< 16 > getFileBaseName() const
Returns the basename that should be used for this Info.
std::vector< CommentInfo > Description
llvm::SmallString< 64 > getRelativeFilePath(const StringRef &CurrentPath) const
Returns the file path for this Info relative to CurrentPath.
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
std::vector< Reference > Records
std::vector< FunctionInfo > Functions
std::vector< Reference > Namespaces
std::vector< EnumInfo > Enums
std::optional< Location > DefLoc