clang-tools 22.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/FormatVariadic.h"
14#include "llvm/Support/Path.h"
15#include "llvm/Support/raw_ostream.h"
16#include <string>
17
18using namespace llvm;
19
20namespace clang {
21namespace doc {
22
23// Markdown generation
24
25static std::string genItalic(const Twine &Text) {
26 return "*" + Text.str() + "*";
27}
28
29static std::string genEmphasis(const Twine &Text) {
30 return "**" + Text.str() + "**";
31}
32
33static std::string
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
70static void maybeWriteSourceFileRef(llvm::raw_ostream &OS,
71 const ClangDocContext &CDCtx,
72 const std::optional<Location> &DefLoc) {
73 if (DefLoc)
74 writeSourceFileRef(CDCtx, *DefLoc, OS);
75}
76
77static void writeDescription(const CommentInfo &I, raw_ostream &OS) {
78 switch (I.Kind) {
80 for (const auto &Child : I.Children)
81 writeDescription(*Child, OS);
82 break;
83
85 for (const auto &Child : I.Children)
86 writeDescription(*Child, OS);
87 writeNewLine(OS);
88 break;
89
91 OS << genEmphasis(I.Name);
92 for (const auto &Child : I.Children)
93 writeDescription(*Child, OS);
94 break;
95
97 OS << genEmphasis(I.Name) << " " << I.Text;
98 break;
99
102 std::string Direction = I.Explicit ? (" " + I.Direction).str() : "";
103 OS << genEmphasis(I.ParamName) << I.Text << Direction;
104 for (const auto &Child : I.Children)
105 writeDescription(*Child, OS);
106 break;
107 }
108
110 for (const auto &Child : I.Children)
111 writeDescription(*Child, OS);
112 break;
113
116 OS << I.Text;
117 writeNewLine(OS);
118 break;
119
121 if (I.AttrKeys.size() != I.AttrValues.size())
122 return;
123 std::string Buffer;
124 llvm::raw_string_ostream Attrs(Buffer);
125 for (unsigned Idx = 0; Idx < I.AttrKeys.size(); ++Idx)
126 Attrs << " \"" << I.AttrKeys[Idx] << "=" << I.AttrValues[Idx] << "\"";
127
128 std::string CloseTag = I.SelfClosing ? "/>" : ">";
129 writeLine("<" + I.Name + Attrs.str() + CloseTag, OS);
130 break;
131 }
132
134 writeLine("</" + I.Name + ">", OS);
135 break;
136
138 OS << I.Text;
139 break;
140
142 OS << "Unknown comment kind: " << static_cast<int>(I.Kind) << ".\n\n";
143 break;
144 }
145}
146
147static void writeNameLink(const StringRef &CurrentPath, const Reference &R,
148 llvm::raw_ostream &OS) {
149 llvm::SmallString<64> Path = R.getRelativeFilePath(CurrentPath);
150 // Paths in Markdown use POSIX separators.
151 llvm::sys::path::native(Path, llvm::sys::path::Style::posix);
152 llvm::sys::path::append(Path, llvm::sys::path::Style::posix,
153 R.getFileBaseName() + ".md");
154 OS << "[" << R.Name << "](" << Path << ")";
155}
156
157static void genMarkdown(const ClangDocContext &CDCtx, const EnumInfo &I,
158 llvm::raw_ostream &OS) {
159 if (I.Scoped)
160 writeLine("| enum class " + I.Name + " |", OS);
161 else
162 writeLine("| enum " + I.Name + " |", OS);
163 writeLine("--", OS);
164
165 std::string Buffer;
166 llvm::raw_string_ostream Members(Buffer);
167 if (!I.Members.empty())
168 for (const auto &N : I.Members)
169 Members << "| " << N.Name << " |\n";
170 writeLine(Members.str(), OS);
171
172 maybeWriteSourceFileRef(OS, CDCtx, I.DefLoc);
173
174 for (const auto &C : I.Description)
175 writeDescription(C, OS);
176}
177
178static void genMarkdown(const ClangDocContext &CDCtx, const FunctionInfo &I,
179 llvm::raw_ostream &OS) {
180 std::string Buffer;
181 llvm::raw_string_ostream Stream(Buffer);
182 bool First = true;
183 for (const auto &N : I.Params) {
184 if (!First)
185 Stream << ", ";
186 Stream << N.Type.QualName + " " + N.Name;
187 First = false;
188 }
189 writeHeader(I.Name, 3, OS);
190 StringRef Access = getAccessSpelling(I.Access);
191 writeLine(genItalic(Twine(Access) + (!Access.empty() ? " " : "") +
192 (I.IsStatic ? "static " : "") +
193 I.ReturnType.Type.QualName.str() + " " + I.Name.str() +
194 "(" + Twine(Stream.str()) + ")"),
195 OS);
196
197 maybeWriteSourceFileRef(OS, CDCtx, I.DefLoc);
198
199 for (const auto &C : I.Description)
200 writeDescription(C, OS);
201}
202
203static void genMarkdown(const ClangDocContext &CDCtx, const NamespaceInfo &I,
204 llvm::raw_ostream &OS) {
205 if (I.Name == "")
206 writeHeader("Global Namespace", 1, OS);
207 else
208 writeHeader("namespace " + I.Name, 1, OS);
209 writeNewLine(OS);
210
211 if (!I.Description.empty()) {
212 for (const auto &C : I.Description)
213 writeDescription(C, OS);
214 writeNewLine(OS);
215 }
216
217 llvm::SmallString<64> BasePath = I.getRelativeFilePath("");
218
219 if (!I.Children.Namespaces.empty()) {
220 writeHeader("Namespaces", 2, OS);
221 for (const auto &R : I.Children.Namespaces) {
222 OS << "* ";
223 writeNameLink(BasePath, R, OS);
224 OS << "\n";
225 }
226 writeNewLine(OS);
227 }
228
229 if (!I.Children.Records.empty()) {
230 writeHeader("Records", 2, OS);
231 for (const auto &R : I.Children.Records) {
232 OS << "* ";
233 writeNameLink(BasePath, R, OS);
234 OS << "\n";
235 }
236 writeNewLine(OS);
237 }
238
239 if (!I.Children.Functions.empty()) {
240 writeHeader("Functions", 2, OS);
241 for (const auto &F : I.Children.Functions)
242 genMarkdown(CDCtx, F, OS);
243 writeNewLine(OS);
244 }
245 if (!I.Children.Enums.empty()) {
246 writeHeader("Enums", 2, OS);
247 for (const auto &E : I.Children.Enums)
248 genMarkdown(CDCtx, E, OS);
249 writeNewLine(OS);
250 }
251}
252
253static void genMarkdown(const ClangDocContext &CDCtx, const RecordInfo &I,
254 llvm::raw_ostream &OS) {
255 writeHeader(getTagType(I.TagType) + " " + I.Name, 1, OS);
256
257 maybeWriteSourceFileRef(OS, CDCtx, I.DefLoc);
258
259 if (!I.Description.empty()) {
260 for (const auto &C : I.Description)
261 writeDescription(C, OS);
262 writeNewLine(OS);
263 }
264
265 std::string Parents = genReferenceList(I.Parents);
266 std::string VParents = genReferenceList(I.VirtualParents);
267 if (!Parents.empty() || !VParents.empty()) {
268 if (Parents.empty())
269 writeLine("Inherits from " + VParents, OS);
270 else if (VParents.empty())
271 writeLine("Inherits from " + Parents, OS);
272 else
273 writeLine("Inherits from " + Parents + ", " + VParents, OS);
274 writeNewLine(OS);
275 }
276
277 if (!I.Members.empty()) {
278 writeHeader("Members", 2, OS);
279 for (const auto &Member : I.Members) {
280 StringRef Access = getAccessSpelling(Member.Access);
281 writeLine(Twine(Access) + (Access.empty() ? "" : " ") +
282 (Member.IsStatic ? "static " : "") +
283 Member.Type.Name.str() + " " + Member.Name.str(),
284 OS);
285 }
286 writeNewLine(OS);
287 }
288
289 if (!I.Children.Records.empty()) {
290 writeHeader("Records", 2, OS);
291 for (const auto &R : I.Children.Records)
292 writeLine(R.Name, OS);
293 writeNewLine(OS);
294 }
295 if (!I.Children.Functions.empty()) {
296 writeHeader("Functions", 2, OS);
297 for (const auto &F : I.Children.Functions)
298 genMarkdown(CDCtx, F, OS);
299 writeNewLine(OS);
300 }
301 if (!I.Children.Enums.empty()) {
302 writeHeader("Enums", 2, OS);
303 for (const auto &E : I.Children.Enums)
304 genMarkdown(CDCtx, E, OS);
305 writeNewLine(OS);
306 }
307}
308
309static void genMarkdown(const ClangDocContext &CDCtx, const TypedefInfo &I,
310 llvm::raw_ostream &OS) {
311 // TODO support typedefs in markdown.
312}
313
314static void serializeReference(llvm::raw_fd_ostream &OS, Index &I, int Level) {
315 // Write out the heading level starting at ##
316 OS << "##" << std::string(Level, '#') << " ";
317 writeNameLink("", I, OS);
318 OS << "\n";
319}
320
321static llvm::Error serializeIndex(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, "all_files.md");
326 llvm::raw_fd_ostream OS(FilePath, FileErr, llvm::sys::fs::OF_Text);
327 if (FileErr)
328 return llvm::createStringError(llvm::inconvertibleErrorCode(),
329 "error creating index file: " +
330 FileErr.message());
331
332 CDCtx.Idx.sort();
333 OS << "# All Files";
334 if (!CDCtx.ProjectName.empty())
335 OS << " for " << CDCtx.ProjectName;
336 OS << "\n\n";
337
338 for (auto C : CDCtx.Idx.Children)
339 serializeReference(OS, C, 0);
340
341 return llvm::Error::success();
342}
343
344static llvm::Error genIndex(ClangDocContext &CDCtx) {
345 std::error_code FileErr;
346 llvm::SmallString<128> FilePath;
347 llvm::sys::path::native(CDCtx.OutDirectory, FilePath);
348 llvm::sys::path::append(FilePath, "index.md");
349 llvm::raw_fd_ostream OS(FilePath, FileErr, llvm::sys::fs::OF_Text);
350 if (FileErr)
351 return llvm::createStringError(llvm::inconvertibleErrorCode(),
352 "error creating index file: " +
353 FileErr.message());
354 CDCtx.Idx.sort();
355 OS << "# " << CDCtx.ProjectName << " C/C++ Reference\n\n";
356 for (auto C : CDCtx.Idx.Children) {
357 if (!C.Children.empty()) {
358 const char *Type;
359 switch (C.RefType) {
361 Type = "Namespace";
362 break;
364 Type = "Type";
365 break;
367 Type = "Enum";
368 break;
370 Type = "Function";
371 break;
373 Type = "Typedef";
374 break;
376 Type = "Concept";
377 break;
379 Type = "Variable";
380 break;
382 Type = "Friend";
383 break;
385 Type = "Other";
386 }
387 OS << "* " << Type << ": [" << C.Name << "](";
388 if (!C.Path.empty())
389 OS << C.Path << "/";
390 OS << C.Name << ")\n";
391 }
392 }
393 return llvm::Error::success();
394}
395
396/// Generator for Markdown documentation.
397class MDGenerator : public Generator {
398public:
399 static const char *Format;
400
401 llvm::Error generateDocs(StringRef RootDir,
402 llvm::StringMap<std::unique_ptr<doc::Info>> Infos,
403 const ClangDocContext &CDCtx) override;
404 llvm::Error createResources(ClangDocContext &CDCtx) override;
405 llvm::Error generateDocForInfo(Info *I, llvm::raw_ostream &OS,
406 const ClangDocContext &CDCtx) override;
407};
408
409const char *MDGenerator::Format = "md";
410
411llvm::Error
413 llvm::StringMap<std::unique_ptr<doc::Info>> Infos,
414 const ClangDocContext &CDCtx) {
415 // Track which directories we already tried to create.
416 llvm::StringSet<> CreatedDirs;
417
418 // Collect all output by file name and create the necessary directories.
419 llvm::StringMap<std::vector<doc::Info *>> FileToInfos;
420 for (const auto &Group : Infos) {
421 doc::Info *Info = Group.getValue().get();
422
423 llvm::SmallString<128> Path;
424 llvm::sys::path::native(RootDir, Path);
425 llvm::sys::path::append(Path, Info->getRelativeFilePath(""));
426 if (!CreatedDirs.contains(Path)) {
427 if (std::error_code Err = llvm::sys::fs::create_directories(Path);
428 Err != std::error_code()) {
429 return llvm::createStringError(Err, "Failed to create directory '%s'.",
430 Path.c_str());
431 }
432 CreatedDirs.insert(Path);
433 }
434
435 llvm::sys::path::append(Path, Info->getFileBaseName() + ".md");
436 FileToInfos[Path].push_back(Info);
437 }
438
439 for (const auto &Group : FileToInfos) {
440 std::error_code FileErr;
441 llvm::raw_fd_ostream InfoOS(Group.getKey(), FileErr,
442 llvm::sys::fs::OF_Text);
443 if (FileErr) {
444 return llvm::createStringError(FileErr, "Error opening file '%s'",
445 Group.getKey().str().c_str());
446 }
447
448 for (const auto &Info : Group.getValue()) {
449 if (llvm::Error Err = generateDocForInfo(Info, InfoOS, CDCtx)) {
450 return Err;
451 }
452 }
453 }
454
455 return llvm::Error::success();
456}
457
458llvm::Error MDGenerator::generateDocForInfo(Info *I, llvm::raw_ostream &OS,
459 const ClangDocContext &CDCtx) {
460 switch (I->IT) {
462 genMarkdown(CDCtx, *static_cast<clang::doc::NamespaceInfo *>(I), OS);
463 break;
465 genMarkdown(CDCtx, *static_cast<clang::doc::RecordInfo *>(I), OS);
466 break;
468 genMarkdown(CDCtx, *static_cast<clang::doc::EnumInfo *>(I), OS);
469 break;
471 genMarkdown(CDCtx, *static_cast<clang::doc::FunctionInfo *>(I), OS);
472 break;
474 genMarkdown(CDCtx, *static_cast<clang::doc::TypedefInfo *>(I), OS);
475 break;
479 break;
481 return createStringError(llvm::inconvertibleErrorCode(),
482 "unexpected InfoType");
483 }
484 return llvm::Error::success();
485}
486
488 // Write an all_files.md
489 auto Err = serializeIndex(CDCtx);
490 if (Err)
491 return Err;
492
493 // Generate the index page.
494 Err = genIndex(CDCtx);
495 if (Err)
496 return Err;
497
498 return llvm::Error::success();
499}
500
501static GeneratorRegistry::Add<MDGenerator> MD(MDGenerator::Format,
502 "Generator for MD output.");
503
504// This anchor is used to force the linker to link in the generated object
505// file and thus register the generator.
506volatile int MDGeneratorAnchorSource = 0;
507
508} // namespace doc
509} // namespace clang
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)
static void writeDescription(const CommentInfo &I, raw_ostream &OS)
static void maybeWriteSourceFileRef(std::vector< std::unique_ptr< TagNode > > &Out, const ClangDocContext &CDCtx, const std::optional< Location > &DefLoc)
static std::vector< std::unique_ptr< HTMLNode > > genReferenceList(const llvm::SmallVectorImpl< Reference > &Refs, const StringRef &CurrentDirectory)
static void serializeReference(const Reference &Ref, Object &ReferenceObj)
static llvm::Error serializeIndex(ClangDocContext &CDCtx)
static std::unique_ptr< TagNode > writeSourceFileRef(const ClangDocContext &CDCtx, const Location &L)
static GeneratorRegistry::Add< MDGenerator > MD(MDGenerator::Format, "Generator for MD output.")
static void writeHeader(const Twine &Text, unsigned int Num, raw_ostream &OS)
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)
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:66
std::optional< std::string > RepositoryUrl
std::optional< std::string > RepositoryLinePrefix
SmallString< 8 > Direction
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.
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
std::vector< Reference > Records
std::vector< FunctionInfo > Functions
std::vector< Reference > Namespaces
std::vector< EnumInfo > Enums
std::optional< Location > DefLoc