xref: /llvm-project/clang-tools-extra/clang-doc/MDGenerator.cpp (revision 229d78de31467f623e33716a30cb0c6d285d7683)
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 
16 using namespace llvm;
17 
18 namespace clang {
19 namespace doc {
20 
21 // Markdown generation
22 
23 static std::string genItalic(const Twine &Text) {
24   return "*" + Text.str() + "*";
25 }
26 
27 static std::string genEmphasis(const Twine &Text) {
28   return "**" + Text.str() + "**";
29 }
30 
31 static std::string
32 genReferenceList(const llvm::SmallVectorImpl<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 
43 static void writeLine(const Twine &Text, raw_ostream &OS) {
44   OS << Text << "\n\n";
45 }
46 
47 static void writeNewLine(raw_ostream &OS) { OS << "\n\n"; }
48 
49 static void writeHeader(const Twine &Text, unsigned int Num, raw_ostream &OS) {
50   OS << std::string(Num, '#') + " " + Text << "\n\n";
51 }
52 
53 static 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 
69 static 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);
76     writeNewLine(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;
98     writeNewLine(OS);
99   } else if (I.Kind == "VerbatimLineComment") {
100     OS << I.Text;
101     writeNewLine(OS);
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 
121 static 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 
131 static 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)
149     writeDescription(C, OS);
150 }
151 
152 static 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)
177     writeDescription(C, OS);
178 }
179 
180 static 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);
186   writeNewLine(OS);
187 
188   if (!I.Description.empty()) {
189     for (const auto &C : I.Description)
190       writeDescription(C, OS);
191     writeNewLine(OS);
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     }
203     writeNewLine(OS);
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     }
213     writeNewLine(OS);
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);
220     writeNewLine(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);
226     writeNewLine(OS);
227   }
228 }
229 
230 static 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)
238       writeDescription(C, OS);
239     writeNewLine(OS);
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);
251     writeNewLine(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     }
263     writeNewLine(OS);
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);
270     writeNewLine(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);
276     writeNewLine(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);
282     writeNewLine(OS);
283   }
284 }
285 
286 static void genMarkdown(const ClangDocContext &CDCtx, const TypedefInfo &I,
287                         llvm::raw_ostream &OS) {
288   // TODO support typedefs in markdown.
289 }
290 
291 static 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 
298 static 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)
316     serializeReference(OS, C, 0);
317 
318   return llvm::Error::success();
319 }
320 
321 static 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) {
337       case InfoType::IT_namespace:
338         Type = "Namespace";
339         break;
340       case InfoType::IT_record:
341         Type = "Type";
342         break;
343       case InfoType::IT_enum:
344         Type = "Enum";
345         break;
346       case InfoType::IT_function:
347         Type = "Function";
348         break;
349       case InfoType::IT_typedef:
350         Type = "Typedef";
351         break;
352       case InfoType::IT_default:
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.
365 class MDGenerator : public Generator {
366 public:
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 
377 const char *MDGenerator::Format = "md";
378 
379 llvm::Error
380 MDGenerator::generateDocs(StringRef RootDir,
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 
426 llvm::Error MDGenerator::generateDocForInfo(Info *I, llvm::raw_ostream &OS,
427                                             const ClangDocContext &CDCtx) {
428   switch (I->IT) {
429   case InfoType::IT_namespace:
430     genMarkdown(CDCtx, *static_cast<clang::doc::NamespaceInfo *>(I), OS);
431     break;
432   case InfoType::IT_record:
433     genMarkdown(CDCtx, *static_cast<clang::doc::RecordInfo *>(I), OS);
434     break;
435   case InfoType::IT_enum:
436     genMarkdown(CDCtx, *static_cast<clang::doc::EnumInfo *>(I), OS);
437     break;
438   case InfoType::IT_function:
439     genMarkdown(CDCtx, *static_cast<clang::doc::FunctionInfo *>(I), OS);
440     break;
441   case InfoType::IT_typedef:
442     genMarkdown(CDCtx, *static_cast<clang::doc::TypedefInfo *>(I), OS);
443     break;
444   case InfoType::IT_default:
445     return createStringError(llvm::inconvertibleErrorCode(),
446                              "unexpected InfoType");
447   }
448   return llvm::Error::success();
449 }
450 
451 llvm::Error MDGenerator::createResources(ClangDocContext &CDCtx) {
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 
465 static 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.
470 volatile int MDGeneratorAnchorSource = 0;
471 
472 } // namespace doc
473 } // namespace clang
474