• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
genItalic(const Twine & Text)23 static std::string genItalic(const Twine &Text) {
24   return "*" + Text.str() + "*";
25 }
26 
genEmphasis(const Twine & Text)27 static std::string genEmphasis(const Twine &Text) {
28   return "**" + Text.str() + "**";
29 }
30 
31 static std::string
genReferenceList(const llvm::SmallVectorImpl<Reference> & Refs)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 
writeLine(const Twine & Text,raw_ostream & OS)43 static void writeLine(const Twine &Text, raw_ostream &OS) {
44   OS << Text << "\n\n";
45 }
46 
writeNewLine(raw_ostream & OS)47 static void writeNewLine(raw_ostream &OS) { OS << "\n\n"; }
48 
writeHeader(const Twine & Text,unsigned int Num,raw_ostream & OS)49 static void writeHeader(const Twine &Text, unsigned int Num, raw_ostream &OS) {
50   OS << std::string(Num, '#') + " " + Text << "\n\n";
51 }
52 
writeFileDefinition(const ClangDocContext & CDCtx,const Location & L,raw_ostream & OS)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.getValue()}
62        << llvm::sys::path::relative_path(L.Filename) << "#"
63        << std::to_string(L.LineNumber) << ")"
64        << "*";
65   }
66   OS << "\n\n";
67 }
68 
writeDescription(const CommentInfo & I,raw_ostream & OS)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 << "\n\n";
86   } else if (I.Kind == "TParamCommandComment") {
87     std::string Direction = I.Explicit ? (" " + I.Direction).str() : "";
88     OS << genEmphasis(I.ParamName) << I.Text << Direction << "\n\n";
89   } else if (I.Kind == "VerbatimBlockComment") {
90     for (const auto &Child : I.Children)
91       writeDescription(*Child, OS);
92   } else if (I.Kind == "VerbatimBlockLineComment") {
93     OS << I.Text;
94     writeNewLine(OS);
95   } else if (I.Kind == "VerbatimLineComment") {
96     OS << I.Text;
97     writeNewLine(OS);
98   } else if (I.Kind == "HTMLStartTagComment") {
99     if (I.AttrKeys.size() != I.AttrValues.size())
100       return;
101     std::string Buffer;
102     llvm::raw_string_ostream Attrs(Buffer);
103     for (unsigned Idx = 0; Idx < I.AttrKeys.size(); ++Idx)
104       Attrs << " \"" << I.AttrKeys[Idx] << "=" << I.AttrValues[Idx] << "\"";
105 
106     std::string CloseTag = I.SelfClosing ? "/>" : ">";
107     writeLine("<" + I.Name + Attrs.str() + CloseTag, OS);
108   } else if (I.Kind == "HTMLEndTagComment") {
109     writeLine("</" + I.Name + ">", OS);
110   } else if (I.Kind == "TextComment") {
111     OS << I.Text;
112   } else {
113     OS << "Unknown comment kind: " << I.Kind << ".\n\n";
114   }
115 }
116 
writeNameLink(const StringRef & CurrentPath,const Reference & R,llvm::raw_ostream & OS)117 static void writeNameLink(const StringRef &CurrentPath, const Reference &R,
118                           llvm::raw_ostream &OS) {
119   llvm::SmallString<64> Path = R.getRelativeFilePath(CurrentPath);
120   // Paths in Markdown use POSIX separators.
121   llvm::sys::path::native(Path, llvm::sys::path::Style::posix);
122   llvm::sys::path::append(Path, llvm::sys::path::Style::posix,
123                           R.getFileBaseName() + ".md");
124   OS << "[" << R.Name << "](" << Path << ")";
125 }
126 
genMarkdown(const ClangDocContext & CDCtx,const EnumInfo & I,llvm::raw_ostream & OS)127 static void genMarkdown(const ClangDocContext &CDCtx, const EnumInfo &I,
128                         llvm::raw_ostream &OS) {
129   if (I.Scoped)
130     writeLine("| enum class " + I.Name + " |", OS);
131   else
132     writeLine("| enum " + I.Name + " |", OS);
133   writeLine("--", OS);
134 
135   std::string Buffer;
136   llvm::raw_string_ostream Members(Buffer);
137   if (!I.Members.empty())
138     for (const auto &N : I.Members)
139       Members << "| " << N << " |\n";
140   writeLine(Members.str(), OS);
141   if (I.DefLoc)
142     writeFileDefinition(CDCtx, I.DefLoc.getValue(), OS);
143 
144   for (const auto &C : I.Description)
145     writeDescription(C, OS);
146 }
147 
genMarkdown(const ClangDocContext & CDCtx,const FunctionInfo & I,llvm::raw_ostream & OS)148 static void genMarkdown(const ClangDocContext &CDCtx, const FunctionInfo &I,
149                         llvm::raw_ostream &OS) {
150   std::string Buffer;
151   llvm::raw_string_ostream Stream(Buffer);
152   bool First = true;
153   for (const auto &N : I.Params) {
154     if (!First)
155       Stream << ", ";
156     Stream << N.Type.Name + " " + N.Name;
157     First = false;
158   }
159   writeHeader(I.Name, 3, OS);
160   std::string Access = getAccessSpelling(I.Access).str();
161   if (Access != "")
162     writeLine(genItalic(Access + " " + I.ReturnType.Type.Name + " " + I.Name +
163                         "(" + Stream.str() + ")"),
164               OS);
165   else
166     writeLine(genItalic(I.ReturnType.Type.Name + " " + I.Name + "(" +
167                         Stream.str() + ")"),
168               OS);
169   if (I.DefLoc)
170     writeFileDefinition(CDCtx, I.DefLoc.getValue(), OS);
171 
172   for (const auto &C : I.Description)
173     writeDescription(C, OS);
174 }
175 
genMarkdown(const ClangDocContext & CDCtx,const NamespaceInfo & I,llvm::raw_ostream & OS)176 static void genMarkdown(const ClangDocContext &CDCtx, const NamespaceInfo &I,
177                         llvm::raw_ostream &OS) {
178   if (I.Name == "")
179     writeHeader("Global Namespace", 1, OS);
180   else
181     writeHeader("namespace " + I.Name, 1, OS);
182   writeNewLine(OS);
183 
184   if (!I.Description.empty()) {
185     for (const auto &C : I.Description)
186       writeDescription(C, OS);
187     writeNewLine(OS);
188   }
189 
190   llvm::SmallString<64> BasePath = I.getRelativeFilePath("");
191 
192   if (!I.ChildNamespaces.empty()) {
193     writeHeader("Namespaces", 2, OS);
194     for (const auto &R : I.ChildNamespaces) {
195       OS << "* ";
196       writeNameLink(BasePath, R, OS);
197       OS << "\n";
198     }
199     writeNewLine(OS);
200   }
201 
202   if (!I.ChildRecords.empty()) {
203     writeHeader("Records", 2, OS);
204     for (const auto &R : I.ChildRecords) {
205       OS << "* ";
206       writeNameLink(BasePath, R, OS);
207       OS << "\n";
208     }
209     writeNewLine(OS);
210   }
211 
212   if (!I.ChildFunctions.empty()) {
213     writeHeader("Functions", 2, OS);
214     for (const auto &F : I.ChildFunctions)
215       genMarkdown(CDCtx, F, OS);
216     writeNewLine(OS);
217   }
218   if (!I.ChildEnums.empty()) {
219     writeHeader("Enums", 2, OS);
220     for (const auto &E : I.ChildEnums)
221       genMarkdown(CDCtx, E, OS);
222     writeNewLine(OS);
223   }
224 }
225 
genMarkdown(const ClangDocContext & CDCtx,const RecordInfo & I,llvm::raw_ostream & OS)226 static void genMarkdown(const ClangDocContext &CDCtx, const RecordInfo &I,
227                         llvm::raw_ostream &OS) {
228   writeHeader(getTagType(I.TagType) + " " + I.Name, 1, OS);
229   if (I.DefLoc)
230     writeFileDefinition(CDCtx, I.DefLoc.getValue(), OS);
231 
232   if (!I.Description.empty()) {
233     for (const auto &C : I.Description)
234       writeDescription(C, OS);
235     writeNewLine(OS);
236   }
237 
238   std::string Parents = genReferenceList(I.Parents);
239   std::string VParents = genReferenceList(I.VirtualParents);
240   if (!Parents.empty() || !VParents.empty()) {
241     if (Parents.empty())
242       writeLine("Inherits from " + VParents, OS);
243     else if (VParents.empty())
244       writeLine("Inherits from " + Parents, OS);
245     else
246       writeLine("Inherits from " + Parents + ", " + VParents, OS);
247     writeNewLine(OS);
248   }
249 
250   if (!I.Members.empty()) {
251     writeHeader("Members", 2, OS);
252     for (const auto &Member : I.Members) {
253       std::string Access = getAccessSpelling(Member.Access).str();
254       if (Access != "")
255         writeLine(Access + " " + Member.Type.Name + " " + Member.Name, OS);
256       else
257         writeLine(Member.Type.Name + " " + Member.Name, OS);
258     }
259     writeNewLine(OS);
260   }
261 
262   if (!I.ChildRecords.empty()) {
263     writeHeader("Records", 2, OS);
264     for (const auto &R : I.ChildRecords)
265       writeLine(R.Name, OS);
266     writeNewLine(OS);
267   }
268   if (!I.ChildFunctions.empty()) {
269     writeHeader("Functions", 2, OS);
270     for (const auto &F : I.ChildFunctions)
271       genMarkdown(CDCtx, F, OS);
272     writeNewLine(OS);
273   }
274   if (!I.ChildEnums.empty()) {
275     writeHeader("Enums", 2, OS);
276     for (const auto &E : I.ChildEnums)
277       genMarkdown(CDCtx, E, OS);
278     writeNewLine(OS);
279   }
280 }
281 
serializeReference(llvm::raw_fd_ostream & OS,Index & I,int Level)282 static void serializeReference(llvm::raw_fd_ostream &OS, Index &I, int Level) {
283   // Write out the heading level starting at ##
284   OS << "##" << std::string(Level, '#') << " ";
285   writeNameLink("", I, OS);
286   OS << "\n";
287 }
288 
serializeIndex(ClangDocContext & CDCtx)289 static llvm::Error serializeIndex(ClangDocContext &CDCtx) {
290   std::error_code FileErr;
291   llvm::SmallString<128> FilePath;
292   llvm::sys::path::native(CDCtx.OutDirectory, FilePath);
293   llvm::sys::path::append(FilePath, "all_files.md");
294   llvm::raw_fd_ostream OS(FilePath, FileErr, llvm::sys::fs::OF_None);
295   if (FileErr)
296     return llvm::createStringError(llvm::inconvertibleErrorCode(),
297                                    "error creating index file: " +
298                                        FileErr.message());
299 
300   CDCtx.Idx.sort();
301   OS << "# All Files";
302   if (!CDCtx.ProjectName.empty())
303     OS << " for " << CDCtx.ProjectName;
304   OS << "\n\n";
305 
306   for (auto C : CDCtx.Idx.Children)
307     serializeReference(OS, C, 0);
308 
309   return llvm::Error::success();
310 }
311 
genIndex(ClangDocContext & CDCtx)312 static llvm::Error genIndex(ClangDocContext &CDCtx) {
313   std::error_code FileErr;
314   llvm::SmallString<128> FilePath;
315   llvm::sys::path::native(CDCtx.OutDirectory, FilePath);
316   llvm::sys::path::append(FilePath, "index.md");
317   llvm::raw_fd_ostream OS(FilePath, FileErr, llvm::sys::fs::OF_None);
318   if (FileErr)
319     return llvm::createStringError(llvm::inconvertibleErrorCode(),
320                                    "error creating index file: " +
321                                        FileErr.message());
322   CDCtx.Idx.sort();
323   OS << "# " << CDCtx.ProjectName << " C/C++ Reference\n\n";
324   for (auto C : CDCtx.Idx.Children) {
325     if (!C.Children.empty()) {
326       const char *Type;
327       switch (C.RefType) {
328       case InfoType::IT_namespace:
329         Type = "Namespace";
330         break;
331       case InfoType::IT_record:
332         Type = "Type";
333         break;
334       case InfoType::IT_enum:
335         Type = "Enum";
336         break;
337       case InfoType::IT_function:
338         Type = "Function";
339         break;
340       case InfoType::IT_default:
341         Type = "Other";
342       }
343       OS << "* " << Type << ": [" << C.Name << "](";
344       if (!C.Path.empty())
345         OS << C.Path << "/";
346       OS << C.Name << ")\n";
347     }
348   }
349   return llvm::Error::success();
350 }
351 /// Generator for Markdown documentation.
352 class MDGenerator : public Generator {
353 public:
354   static const char *Format;
355 
356   llvm::Error generateDocForInfo(Info *I, llvm::raw_ostream &OS,
357                                  const ClangDocContext &CDCtx) override;
358   llvm::Error createResources(ClangDocContext &CDCtx) override;
359 };
360 
361 const char *MDGenerator::Format = "md";
362 
generateDocForInfo(Info * I,llvm::raw_ostream & OS,const ClangDocContext & CDCtx)363 llvm::Error MDGenerator::generateDocForInfo(Info *I, llvm::raw_ostream &OS,
364                                             const ClangDocContext &CDCtx) {
365   switch (I->IT) {
366   case InfoType::IT_namespace:
367     genMarkdown(CDCtx, *static_cast<clang::doc::NamespaceInfo *>(I), OS);
368     break;
369   case InfoType::IT_record:
370     genMarkdown(CDCtx, *static_cast<clang::doc::RecordInfo *>(I), OS);
371     break;
372   case InfoType::IT_enum:
373     genMarkdown(CDCtx, *static_cast<clang::doc::EnumInfo *>(I), OS);
374     break;
375   case InfoType::IT_function:
376     genMarkdown(CDCtx, *static_cast<clang::doc::FunctionInfo *>(I), OS);
377     break;
378   case InfoType::IT_default:
379     return createStringError(llvm::inconvertibleErrorCode(),
380                              "unexpected InfoType");
381   }
382   return llvm::Error::success();
383 }
384 
createResources(ClangDocContext & CDCtx)385 llvm::Error MDGenerator::createResources(ClangDocContext &CDCtx) {
386   // Write an all_files.md
387   auto Err = serializeIndex(CDCtx);
388   if (Err)
389     return Err;
390 
391   // Generate the index page.
392   Err = genIndex(CDCtx);
393   if (Err)
394     return Err;
395 
396   return llvm::Error::success();
397 }
398 
399 static GeneratorRegistry::Add<MDGenerator> MD(MDGenerator::Format,
400                                               "Generator for MD output.");
401 
402 // This anchor is used to force the linker to link in the generated object
403 // file and thus register the generator.
404 volatile int MDGeneratorAnchorSource = 0;
405 
406 } // namespace doc
407 } // namespace clang
408