diff --git a/clang-tools-extra/clang-doc/CMakeLists.txt b/clang-tools-extra/clang-doc/CMakeLists.txt index 520fe58cbe68e..d361e928860a6 100644 --- a/clang-tools-extra/clang-doc/CMakeLists.txt +++ b/clang-tools-extra/clang-doc/CMakeLists.txt @@ -15,6 +15,8 @@ add_clang_library(clangDoc STATIC Representation.cpp Serialize.cpp YAMLGenerator.cpp + HTMLMustacheGenerator.cpp + FileHelpersClangDoc.cpp DEPENDS omp_gen diff --git a/clang-tools-extra/clang-doc/FileHelpersClangDoc.cpp b/clang-tools-extra/clang-doc/FileHelpersClangDoc.cpp new file mode 100644 index 0000000000000..50209cfac1ca3 --- /dev/null +++ b/clang-tools-extra/clang-doc/FileHelpersClangDoc.cpp @@ -0,0 +1,75 @@ +//===-- FileHelpersClangDoc.cpp - File Helpers -------------------*- C++-*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +#include "FileHelpersClangDoc.h" +#include "llvm/Support/FileSystem.h" +#include "llvm/Support/Path.h" + +namespace clang { +namespace doc { + +llvm::Error +copyFile(llvm::StringRef FilePath, llvm::StringRef OutDirectory) { + llvm::SmallString<128> PathWrite; + llvm::sys::path::native(OutDirectory, PathWrite); + llvm::sys::path::append(PathWrite, llvm::sys::path::filename(FilePath)); + llvm::SmallString<128> PathRead; + llvm::sys::path::native(FilePath, PathRead); + std::error_code OK; + std::error_code FileErr = llvm::sys::fs::copy_file(PathRead, PathWrite); + if (FileErr != OK) { + return llvm::createStringError(llvm::inconvertibleErrorCode(), + "error creating file " + + llvm::sys::path::filename(FilePath) + + ": " + FileErr.message() + "\n"); + } + return llvm::Error::success(); +} + + +llvm::SmallString<128> computeRelativePath(llvm::StringRef Destination, + llvm::StringRef Origin) { + // If Origin is empty, the relative path to the Destination is its complete + // path. + if (Origin.empty()) + return Destination; + + // The relative path is an empty path if both directories are the same. + if (Destination == Origin) + return {}; + + // These iterators iterate through each of their parent directories + llvm::sys::path::const_iterator FileI = llvm::sys::path::begin(Destination); + llvm::sys::path::const_iterator FileE = llvm::sys::path::end(Destination); + llvm::sys::path::const_iterator DirI = llvm::sys::path::begin(Origin); + llvm::sys::path::const_iterator DirE = llvm::sys::path::end(Origin); + // Advance both iterators until the paths differ. Example: + // Destination = A/B/C/D + // Origin = A/B/E/F + // FileI will point to C and DirI to E. The directories behind them is the + // directory they share (A/B). + while (FileI != FileE && DirI != DirE && *FileI == *DirI) { + ++FileI; + ++DirI; + } + llvm::SmallString<128> Result; // This will hold the resulting path. + // Result has to go up one directory for each of the remaining directories in + // Origin + while (DirI != DirE) { + llvm::sys::path::append(Result, ".."); + ++DirI; + } + // Result has to append each of the remaining directories in Destination + while (FileI != FileE) { + llvm::sys::path::append(Result, *FileI); + ++FileI; + } + return Result; +} + +} // namespace doc +} // namespace clang \ No newline at end of file diff --git a/clang-tools-extra/clang-doc/FileHelpersClangDoc.h b/clang-tools-extra/clang-doc/FileHelpersClangDoc.h new file mode 100644 index 0000000000000..9072a7bd08a4f --- /dev/null +++ b/clang-tools-extra/clang-doc/FileHelpersClangDoc.h @@ -0,0 +1,26 @@ +//===-- FileHelpersClangDoc.h --- File Helpers -------------------*- C++-*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_DOC_FILEHELPER_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_DOC_FILEHELPER_H + +#include "llvm/ADT/StringExtras.h" +#include "llvm/Support/Error.h" + +namespace clang { +namespace doc { + +llvm::Error +copyFile(llvm::StringRef FilePath, llvm::StringRef OutDirectory); + +llvm::SmallString<128> +computeRelativePath(llvm::StringRef Destination,llvm::StringRef Origin); + +} // namespace doc +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_DOC_FILEHELPER_H \ No newline at end of file diff --git a/clang-tools-extra/clang-doc/Generators.cpp b/clang-tools-extra/clang-doc/Generators.cpp index a3986b66f3c74..ae5b556d17063 100644 --- a/clang-tools-extra/clang-doc/Generators.cpp +++ b/clang-tools-extra/clang-doc/Generators.cpp @@ -100,12 +100,14 @@ void Generator::addInfoToIndex(Index &Idx, const doc::Info *Info) { extern volatile int YAMLGeneratorAnchorSource; extern volatile int MDGeneratorAnchorSource; extern volatile int HTMLGeneratorAnchorSource; +extern volatile int MHTMLGeneratorAnchorSource; static int LLVM_ATTRIBUTE_UNUSED YAMLGeneratorAnchorDest = YAMLGeneratorAnchorSource; static int LLVM_ATTRIBUTE_UNUSED MDGeneratorAnchorDest = MDGeneratorAnchorSource; static int LLVM_ATTRIBUTE_UNUSED HTMLGeneratorAnchorDest = HTMLGeneratorAnchorSource; - +static int LLVM_ATTRIBUTE_UNUSED MHTMLGeneratorAnchorDest = + MHTMLGeneratorAnchorSource; } // namespace doc } // namespace clang diff --git a/clang-tools-extra/clang-doc/HTMLGenerator.cpp b/clang-tools-extra/clang-doc/HTMLGenerator.cpp index a8404479569f9..6b0efc9d4f37c 100644 --- a/clang-tools-extra/clang-doc/HTMLGenerator.cpp +++ b/clang-tools-extra/clang-doc/HTMLGenerator.cpp @@ -496,7 +496,7 @@ writeFileDefinition(const Location &L, std::optional RepositoryUrl = std::nullopt) { if (!L.IsFileInRootDir && !RepositoryUrl) return std::make_unique( - HTMLTag::TAG_P, "Defined at line " + std::to_string(L.LineNumber) + + HTMLTag::TAG_P, "Defined at line " + std::to_string(L.StartLineNumber) + " of file " + L.Filename); SmallString<128> FileURL(RepositoryUrl.value_or("")); llvm::sys::path::append( @@ -513,14 +513,14 @@ writeFileDefinition(const Location &L, auto Node = std::make_unique(HTMLTag::TAG_P); Node->Children.emplace_back(std::make_unique("Defined at line ")); auto LocNumberNode = - std::make_unique(HTMLTag::TAG_A, std::to_string(L.LineNumber)); + std::make_unique(HTMLTag::TAG_A, std::to_string(L.StartLineNumber)); // The links to a specific line in the source code use the github / // googlesource notation so it won't work for all hosting pages. // FIXME: we probably should have a configuration setting for line number // rendering in the HTML. For example, GitHub uses #L22, while googlesource // uses #22 for line numbers. LocNumberNode->Attributes.emplace_back( - "href", (FileURL + "#" + std::to_string(L.LineNumber)).str()); + "href", (FileURL + "#" + std::to_string(L.StartLineNumber)).str()); Node->Children.emplace_back(std::move(LocNumberNode)); Node->Children.emplace_back(std::make_unique(" of file ")); auto LocFileNode = std::make_unique( @@ -1146,7 +1146,8 @@ static llvm::Error genIndex(const ClangDocContext &CDCtx) { return llvm::Error::success(); } -static llvm::Error copyFile(StringRef FilePath, StringRef OutDirectory) { +static llvm::Error +copyFile(StringRef FilePath, StringRef OutDirectory) { llvm::SmallString<128> PathWrite; llvm::sys::path::native(OutDirectory, PathWrite); llvm::sys::path::append(PathWrite, llvm::sys::path::filename(FilePath)); diff --git a/clang-tools-extra/clang-doc/HTMLMustacheGenerator.cpp b/clang-tools-extra/clang-doc/HTMLMustacheGenerator.cpp new file mode 100644 index 0000000000000..1f96202f1f6de --- /dev/null +++ b/clang-tools-extra/clang-doc/HTMLMustacheGenerator.cpp @@ -0,0 +1,523 @@ +//===-- HTMLMustacheGenerator.cpp - HTML Mustache Generator -----*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +#include "Generators.h" +#include "Representation.h" +#include "FileHelpersClangDoc.h" +#include "llvm/Support/MemoryBuffer.h" +#include "llvm/Support/Mustache.h" + +using namespace llvm; +using namespace llvm::json; +using namespace llvm::mustache; + +namespace clang { +namespace doc { + + +class MustacheHTMLGenerator : public Generator { +public: + static const char *Format; + llvm::Error generateDocs(StringRef RootDir, + llvm::StringMap> Infos, + const ClangDocContext &CDCtx) override; + llvm::Error createResources(ClangDocContext &CDCtx) override; + llvm::Error generateDocForInfo(Info *I, llvm::raw_ostream &OS, + const ClangDocContext &CDCtx) override; + +}; + +class MustacheTemplateFile : public Template { +public: + static ErrorOr> createMustacheFile + (StringRef FileName) { + ErrorOr> BufferOrError = + MemoryBuffer::getFile(FileName); + + if (auto EC = BufferOrError.getError()) { + return EC; + } + std::unique_ptr Buffer = std::move(BufferOrError.get()); + llvm::StringRef FileContent = Buffer->getBuffer(); + return std::make_unique(FileContent); + } + + Error registerPartialFile(StringRef Name, StringRef FileName) { + ErrorOr> BufferOrError = + MemoryBuffer::getFile(FileName); + if (auto EC = BufferOrError.getError()) + return llvm::createFileError("cannot open file", EC); + std::unique_ptr Buffer = std::move(BufferOrError.get()); + llvm::StringRef FileContent = Buffer->getBuffer(); + registerPartial(Name, FileContent); + return llvm::Error::success(); + } + + MustacheTemplateFile(StringRef TemplateStr) : Template(TemplateStr) {} +}; + +static std::unique_ptr NamespaceTemplate = nullptr; + +static std::unique_ptr RecordTemplate = nullptr; + + +llvm::Error setupTemplate( + std::unique_ptr &Template, + StringRef TemplatePath, + std::vector> Partials) { + auto T = MustacheTemplateFile::createMustacheFile(TemplatePath); + if (auto EC = T.getError()) + return llvm::createFileError("cannot open file", EC); + Template = std::move(T.get()); + for (const auto &P : Partials) { + auto Err = Template->registerPartialFile(P.first, P.second); + if (Err) + return Err; + } + return llvm::Error::success(); +} + +llvm::Error +setupTemplateFiles(const clang::doc::ClangDocContext &CDCtx) { + auto NamespaceFilePath = CDCtx.MustacheTemplates.lookup("namespace-template"); + auto ClassFilePath = CDCtx.MustacheTemplates.lookup("class-template"); + auto CommentFilePath = CDCtx.MustacheTemplates.lookup("comments-template"); + auto FunctionFilePath = CDCtx.MustacheTemplates.lookup("function-template"); + auto EnumFilePath = CDCtx.MustacheTemplates.lookup("enum-template"); + std::vector> Partials = { + {"Comments", CommentFilePath}, + {"FunctionPartial", FunctionFilePath}, + {"EnumPartial", EnumFilePath} + }; + + auto Err = setupTemplate(NamespaceTemplate, NamespaceFilePath, Partials); + if (Err) + return Err; + + Err = setupTemplate(RecordTemplate, ClassFilePath, Partials); + + if (Err) + return Err; + + return llvm::Error::success(); +} + + +llvm::Error +MustacheHTMLGenerator::generateDocs(llvm::StringRef RootDir, + llvm::StringMap> Infos, + const clang::doc::ClangDocContext &CDCtx) { + if (auto Err = setupTemplateFiles(CDCtx)) + return Err; + // Track which directories we already tried to create. + llvm::StringSet<> CreatedDirs; + // Collect all output by file name and create the necessary directories. + llvm::StringMap> FileToInfos; + for (const auto &Group : Infos) { + doc::Info *Info = Group.getValue().get(); + + llvm::SmallString<128> Path; + llvm::sys::path::native(RootDir, Path); + llvm::sys::path::append(Path, Info->getRelativeFilePath("")); + if (!CreatedDirs.contains(Path)) { + if (std::error_code Err = llvm::sys::fs::create_directories(Path); + Err != std::error_code()) + return llvm::createStringError(Err, "Failed to create directory '%s'.", + Path.c_str()); + CreatedDirs.insert(Path); + } + + llvm::sys::path::append(Path, Info->getFileBaseName() + ".html"); + FileToInfos[Path].push_back(Info); + } + + for (const auto &Group : FileToInfos) { + std::error_code FileErr; + llvm::raw_fd_ostream InfoOS(Group.getKey(), FileErr, + llvm::sys::fs::OF_None); + if (FileErr) + return llvm::createStringError(FileErr, "Error opening file '%s'", + Group.getKey().str().c_str()); + + for (const auto &Info : Group.getValue()) { + if (llvm::Error Err = generateDocForInfo(Info, InfoOS, CDCtx)) + return Err; + } + } + return llvm::Error::success(); +} + +Value extractValue(const Location &L, + std::optional RepositoryUrl = std::nullopt) { + Object Obj = Object(); + Obj.insert({"LineNumber", L.LineNumber}); + Obj.insert({"Filename", L.Filename}); + + if (!L.IsFileInRootDir || !RepositoryUrl) { + return Obj; + } + SmallString<128> FileURL(*RepositoryUrl); + llvm::sys::path::append(FileURL, llvm::sys::path::Style::posix, L.Filename); + FileURL += "#" + std::to_string(L.LineNumber); + Obj.insert({"FileURL", FileURL}); + + return Obj; +} + +Value extractValue(const Reference &I, StringRef CurrentDirectory) { + llvm::SmallString<64> Path = I.getRelativeFilePath(CurrentDirectory); + llvm::sys::path::append(Path, I.getFileBaseName() + ".html"); + llvm::sys::path::native(Path, llvm::sys::path::Style::posix); + Object Obj = Object(); + Obj.insert({"Link", Path}); + Obj.insert({"Name", I.Name}); + Obj.insert({"QualName", I.QualName}); + Obj.insert({"ID", llvm::toHex(llvm::toStringRef(I.USR))}); + return Obj; +} + + +Value extractValue(const TypedefInfo &I) { + // Not Supported + return nullptr; +} + +Value extractValue(const CommentInfo &I) { + Object Obj = Object(); + Value Child = Object(); + + if (I.Kind == "FullComment") { + Value ChildArr = Array(); + for (const auto& C: I.Children) + ChildArr.getAsArray()->emplace_back(extractValue(*C)); + Child.getAsObject()->insert({"Children", ChildArr}); + Obj.insert({"FullComment", Child}); + } + if (I.Kind == "ParagraphComment") { + Value ChildArr = Array(); + for (const auto& C: I.Children) + ChildArr.getAsArray()->emplace_back(extractValue(*C)); + Child.getAsObject()->insert({"Children", ChildArr}); + Obj.insert({"ParagraphComment", Child}); + } + if (I.Kind == "BlockCommandComment") { + Child.getAsObject()->insert({"Command", I.Name}); + Value ChildArr = Array(); + for (const auto& C: I.Children) + ChildArr.getAsArray()->emplace_back(extractValue(*C)); + Child.getAsObject()->insert({"Children", ChildArr}); + Obj.insert({"BlockCommandComment", Child}); + } + if (I.Kind == "TextComment") + Obj.insert({"TextComment", I.Text}); + + return Obj; +} + +Value extractValue(const FunctionInfo &I, StringRef ParentInfoDir, + const ClangDocContext &CDCtx) { + Object Obj = Object(); + Obj.insert({"Name", I.Name}); + Obj.insert({"ID", llvm::toHex(llvm::toStringRef(I.USR))}); + Obj.insert({"Access", getAccessSpelling(I.Access).str()}); + Obj.insert({"ReturnType", extractValue(I.ReturnType.Type, ParentInfoDir)}); + + Value ParamArr = Array(); + for (const auto Val : llvm::enumerate(I.Params)) { + Value V = Object(); + V.getAsObject()->insert({"Name", Val.value().Name}); + V.getAsObject()->insert({"Type", Val.value().Type.Name}); + V.getAsObject()->insert({"End", Val.index() + 1 == I.Params.size()}); + ParamArr.getAsArray()->emplace_back(V); + } + Obj.insert({"Params", ParamArr}); + + if (!I.Description.empty()) { + Value ArrDesc = Array(); + for (const CommentInfo& Child : I.Description) + ArrDesc.getAsArray()->emplace_back(extractValue(Child)); + Obj.insert({"FunctionComments", ArrDesc}); + } + if (I.DefLoc.has_value()) { + Location L = *I.DefLoc; + if (CDCtx.RepositoryUrl.has_value()) + Obj.insert({"Location", extractValue(L, + StringRef{*CDCtx.RepositoryUrl})}); + else + Obj.insert({"Location", extractValue(L)}); + } + return Obj; +} + +Value extractValue(const EnumInfo &I, const ClangDocContext &CDCtx) { + Object Obj = Object(); + std::string EnumType = I.Scoped ? "enum class " : "enum "; + EnumType += I.Name; + bool HasComment = std::any_of( + I.Members.begin(), I.Members.end(), + [](const EnumValueInfo &M) { return !M.Description.empty(); }); + Obj.insert({"EnumName", EnumType}); + Obj.insert({"HasComment", HasComment}); + Obj.insert({"ID", llvm::toHex(llvm::toStringRef(I.USR))}); + Value Arr = Array(); + for (const EnumValueInfo& M: I.Members) { + Value EnumValue = Object(); + EnumValue.getAsObject()->insert({"Name", M.Name}); + if (!M.ValueExpr.empty()) + EnumValue.getAsObject()->insert({"ValueExpr", M.ValueExpr}); + else + EnumValue.getAsObject()->insert({"Value", M.Value}); + + if (!M.Description.empty()) { + Value ArrDesc = Array(); + for (const CommentInfo& Child : M.Description) + ArrDesc.getAsArray()->emplace_back(extractValue(Child)); + EnumValue.getAsObject()->insert({"EnumValueComments", ArrDesc}); + } + Arr.getAsArray()->emplace_back(EnumValue); + } + Obj.insert({"EnumValues", Arr}); + + if (!I.Description.empty()) { + Value ArrDesc = Array(); + for (const CommentInfo& Child : I.Description) + ArrDesc.getAsArray()->emplace_back(extractValue(Child)); + Obj.insert({"EnumComments", ArrDesc}); + } + + if (I.DefLoc.has_value()) { + Location L = *I.DefLoc; + if (CDCtx.RepositoryUrl.has_value()) + Obj.insert({"Location", extractValue(L, + StringRef{*CDCtx.RepositoryUrl})}); + else + Obj.insert({"Location", extractValue(L)}); + } + + return Obj; +} + +void extractScopeChildren(const ScopeChildren &S, Object &Obj, + StringRef ParentInfoDir, + const ClangDocContext &CDCtx) { + Value ArrNamespace = Array(); + for (const Reference& Child : S.Namespaces) + ArrNamespace.getAsArray()->emplace_back(extractValue(Child, ParentInfoDir)); + + if (!ArrNamespace.getAsArray()->empty()) + Obj.insert({"Namespace", Object{{"Links", ArrNamespace}}}); + + Value ArrRecord = Array(); + for (const Reference& Child : S.Records) + ArrRecord.getAsArray()->emplace_back(extractValue(Child, ParentInfoDir)); + + if (!ArrRecord.getAsArray()->empty()) + Obj.insert({"Record", Object{{"Links", ArrRecord}}}); + + Value ArrFunction = Array(); + Value PublicFunction = Array(); + Value ProtectedFunction = Array(); + Value PrivateFunction = Array(); + + for (const FunctionInfo& Child : S.Functions) { + Value F = extractValue(Child, ParentInfoDir, CDCtx); + AccessSpecifier Access = Child.Access; + if (Access == AccessSpecifier::AS_public) + PublicFunction.getAsArray()->emplace_back(F); + else if (Access == AccessSpecifier::AS_protected) + ProtectedFunction.getAsArray()->emplace_back(F); + else + ArrFunction.getAsArray()->emplace_back(F); + } + if (!ArrFunction.getAsArray()->empty()) + Obj.insert({"Function", Object{{"Obj", ArrFunction}}}); + + if (!PublicFunction.getAsArray()->empty()) + Obj.insert({"PublicFunction", Object{{"Obj", PublicFunction}}}); + + if (!ProtectedFunction.getAsArray()->empty()) + Obj.insert({"ProtectedFunction", Object{{"Obj", ProtectedFunction}}}); + + + Value ArrEnum = Array(); + for (const EnumInfo& Child : S.Enums) + ArrEnum.getAsArray()->emplace_back(extractValue(Child, CDCtx)); + + if (!ArrEnum.getAsArray()->empty()) + Obj.insert({"Enums", Object{{"Obj", ArrEnum }}}); + + Value ArrTypedefs = Array(); + for (const TypedefInfo& Child : S.Typedefs) + ArrTypedefs.getAsArray()->emplace_back(extractValue(Child)); + + if (!ArrTypedefs.getAsArray()->empty()) + Obj.insert({"Typedefs", Object{{"Obj", ArrTypedefs }}}); +} + +Value extractValue(const NamespaceInfo &I, const ClangDocContext &CDCtx) { + Object NamespaceValue = Object(); + std::string InfoTitle; + if (I.Name.str() == "") + InfoTitle = "Global Namespace"; + else + InfoTitle = ("namespace " + I.Name).str(); + + StringRef BasePath = I.getRelativeFilePath(""); + NamespaceValue.insert({"NamespaceTitle", InfoTitle}); + NamespaceValue.insert({"NamespacePath", I.getRelativeFilePath("")}); + + if (!I.Description.empty()) { + Value ArrDesc = Array(); + for (const CommentInfo& Child : I.Description) + ArrDesc.getAsArray()->emplace_back(extractValue(Child)); + NamespaceValue.insert({"NamespaceComments", ArrDesc }); + } + extractScopeChildren(I.Children, NamespaceValue, BasePath, CDCtx); + return NamespaceValue; +} + +Value extractValue(const RecordInfo &I, const ClangDocContext &CDCtx) { + Object RecordValue = Object(); + + if (!I.Description.empty()) { + Value ArrDesc = Array(); + for (const CommentInfo& Child : I.Description) + ArrDesc.getAsArray()->emplace_back(extractValue(Child)); + RecordValue.insert({"RecordComments", ArrDesc }); + } + RecordValue.insert({"Name", I.Name}); + RecordValue.insert({"FullName", I.FullName}); + RecordValue.insert({"RecordType", getTagType(I.TagType)}); + + if (I.DefLoc.has_value()) { + Location L = *I.DefLoc; + if (CDCtx.RepositoryUrl.has_value()) + RecordValue.insert({"Location", extractValue(L, + StringRef{*CDCtx.RepositoryUrl})}); + else + RecordValue.insert({"Location", extractValue(L)}); + } + + StringRef BasePath = I.getRelativeFilePath(""); + extractScopeChildren(I.Children, RecordValue, BasePath, CDCtx); + Value PublicMembers = Array(); + Value ProtectedMembers = Array(); + Value PrivateMembers = Array(); + for (const MemberTypeInfo &Member : I.Members ) { + Value MemberValue = Object(); + MemberValue.getAsObject()->insert({"Name", Member.Name}); + MemberValue.getAsObject()->insert({"Type", Member.Type.Name}); + if (!Member.Description.empty()) { + Value ArrDesc = Array(); + for (const CommentInfo& Child : Member.Description) + ArrDesc.getAsArray()->emplace_back(extractValue(Child)); + MemberValue.getAsObject()->insert({"MemberComments", ArrDesc }); + } + + if (Member.Access == AccessSpecifier::AS_public) + PublicMembers.getAsArray()->emplace_back(MemberValue); + else if (Member.Access == AccessSpecifier::AS_protected) + ProtectedMembers.getAsArray()->emplace_back(MemberValue); + else if (Member.Access == AccessSpecifier::AS_private) + PrivateMembers.getAsArray()->emplace_back(MemberValue); + } + if (!PublicMembers.getAsArray()->empty()) + RecordValue.insert({"PublicMembers", Object{{"Obj", PublicMembers}}}); + if (!ProtectedMembers.getAsArray()->empty()) + RecordValue.insert({"ProtectedMembers", Object{{"Obj", ProtectedMembers}}}); + if (!PrivateMembers.getAsArray()->empty()) + RecordValue.insert({"PrivateMembers", Object{{"Obj", PrivateMembers}}}); + + return RecordValue; +} + +void setupTemplateValue(const ClangDocContext &CDCtx, Value &V, Info *I) { + V.getAsObject()->insert({"ProjectName", CDCtx.ProjectName}); + Value StylesheetArr = Array(); + auto InfoPath = I->getRelativeFilePath(""); + SmallString<128> RelativePath = computeRelativePath("", InfoPath); + for (const auto &FilePath : CDCtx.UserStylesheets) { + SmallString<128> StylesheetPath = RelativePath; + llvm::sys::path::append(StylesheetPath, + llvm::sys::path::filename(FilePath)); + llvm::sys::path::native(StylesheetPath, llvm::sys::path::Style::posix); + StylesheetArr.getAsArray()->emplace_back(StylesheetPath); + } + V.getAsObject()->insert({"Stylesheets", StylesheetArr}); + + Value ScriptArr = Array(); + for (auto Script : CDCtx.JsScripts) { + SmallString<128> JsPath = RelativePath; + llvm::sys::path::append(JsPath, llvm::sys::path::filename(Script)); + ScriptArr.getAsArray()->emplace_back(JsPath); + } + V.getAsObject()->insert({"Scripts", ScriptArr}); +} + + +llvm::Error +MustacheHTMLGenerator::generateDocForInfo(Info *I, llvm::raw_ostream &OS, + const ClangDocContext &CDCtx) { + switch (I->IT) { + case InfoType::IT_namespace: { + Value V = extractValue(*static_cast(I), CDCtx); + setupTemplateValue(CDCtx, V, I); + OS << NamespaceTemplate->render(V); + break; + } + case InfoType::IT_record: { + Value V = extractValue(*static_cast(I), CDCtx); + setupTemplateValue(CDCtx, V, I); + // Serialize the JSON value to the output stream in a readable format. + llvm::outs() << "Visit: " << I->Name << "\n"; + //llvm::outs() << llvm::formatv("{0:2}", V) << "\n"; + llvm::outs() << RecordTemplate->render(V); + break; + } + case InfoType::IT_enum: + llvm::outs() << "IT_enum\n"; + break; + case InfoType::IT_function: + llvm::outs() << "IT_Function\n"; + break; + case InfoType::IT_typedef: + llvm::outs() << "IT_typedef\n"; + break; + case InfoType::IT_default: + return createStringError(llvm::inconvertibleErrorCode(), + "unexpected InfoType"); + } + return llvm::Error::success(); +} + +llvm::Error MustacheHTMLGenerator::createResources(ClangDocContext &CDCtx) { + llvm::Error Err = llvm::Error::success(); + for (const auto &FilePath : CDCtx.UserStylesheets) { + Err = copyFile(FilePath, CDCtx.OutDirectory); + if (Err) + return Err; + } + for (const auto &FilePath : CDCtx.JsScripts) { + Err = copyFile(FilePath, CDCtx.OutDirectory); + if (Err) + return Err; + } + return llvm::Error::success(); +} + +const char *MustacheHTMLGenerator::Format = "mhtml"; + + +static GeneratorRegistry::Add MHTML(MustacheHTMLGenerator::Format, + "Generator for mustache HTML output."); + +// This anchor is used to force the linker to link in the generated object +// file and thus register the generator. +volatile int MHTMLGeneratorAnchorSource = 0; + +} // namespace doc +} // namespace clang \ No newline at end of file diff --git a/clang-tools-extra/clang-doc/Representation.cpp b/clang-tools-extra/clang-doc/Representation.cpp index 4da93b24c131f..cb42709f467a3 100644 --- a/clang-tools-extra/clang-doc/Representation.cpp +++ b/clang-tools-extra/clang-doc/Representation.cpp @@ -239,6 +239,8 @@ RecordInfo::RecordInfo(SymbolID USR, StringRef Name, StringRef Path) void RecordInfo::merge(RecordInfo &&Other) { assert(mergeable(Other)); + if (FullName.empty()) + FullName = std::move(Other.FullName); if (!llvm::to_underlying(TagType)) TagType = Other.TagType; IsTypeDef = IsTypeDef || Other.IsTypeDef; diff --git a/clang-tools-extra/clang-doc/Representation.h b/clang-tools-extra/clang-doc/Representation.h index 1f2ea51e06b71..84a647cbe8933 100644 --- a/clang-tools-extra/clang-doc/Representation.h +++ b/clang-tools-extra/clang-doc/Representation.h @@ -62,10 +62,10 @@ struct CommentInfo { SmallString<16> Kind; // Kind of comment (FullComment, ParagraphComment, TextComment, - // InlineCommandComment, HTMLStartTagComment, HTMLEndTagComment, - // BlockCommandComment, ParamCommandComment, - // TParamCommandComment, VerbatimBlockComment, - // VerbatimBlockLineComment, VerbatimLineComment). + // InlineCommandComment, HTMLStartTagComment, HTMLEndTagComment, + // BlockCommandComment, ParamCommandComment, + // TParamCommandComment, VerbatimBlockComment, + // VerbatimBlockLineComment, VerbatimLineComment). SmallString<64> Text; // Text of the comment. SmallString<16> Name; // Name of the comment (for Verbatim and HTML). SmallString<8> Direction; // Parameter direction (for (T)ParamCommand). @@ -73,7 +73,7 @@ struct CommentInfo { SmallString<16> CloseName; // Closing tag name (for VerbatimBlock). bool SelfClosing = false; // Indicates if tag is self-closing (for HTML). bool Explicit = false; // Indicates if the direction of a param is explicit - // (for (T)ParamCommand). + // (for (T)ParamCommand). llvm::SmallVector, 4> AttrKeys; // List of attribute keys (for HTML). llvm::SmallVector, 4> @@ -113,7 +113,8 @@ struct Reference { llvm::SmallString<16> getFileBaseName() const; SymbolID USR = SymbolID(); // Unique identifier for referenced decl - + + // Name of type (possibly unresolved). Not including namespaces or template // parameters (so for a std::vector this would be "vector"). See also // QualName. @@ -152,7 +153,9 @@ struct ScopeChildren { // A base struct for TypeInfos struct TypeInfo { + TypeInfo() = default; + TypeInfo(const Reference &R) : Type(R) {} // Convenience constructor for when there is no symbol ID or info type @@ -161,8 +164,11 @@ struct TypeInfo { : Type(SymbolID(), Name, InfoType::IT_default, Name, Path) {} bool operator==(const TypeInfo &Other) const { return Type == Other.Type; } - + Reference Type; // Referenced type in this info. + + bool IsTemplate = false; + bool IsBuiltIn = false; }; // Represents one template parameter. @@ -209,6 +215,7 @@ struct FieldTypeInfo : public TypeInfo { return std::tie(Type, Name, DefaultValue) == std::tie(Other.Type, Other.Name, Other.DefaultValue); } + SmallString<16> Name; // Name associated with this info. @@ -238,19 +245,23 @@ struct MemberTypeInfo : public FieldTypeInfo { }; struct Location { - Location(int LineNumber = 0, StringRef Filename = StringRef(), + Location(int StartLineNumber = 0, + int EndLineNumber = 0, + StringRef Filename = StringRef(), bool IsFileInRootDir = false) - : LineNumber(LineNumber), Filename(Filename), + : StartLineNumber(StartLineNumber), + EndLineNumber(EndLineNumber), + Filename(Filename), IsFileInRootDir(IsFileInRootDir) {} bool operator==(const Location &Other) const { - return std::tie(LineNumber, Filename) == - std::tie(Other.LineNumber, Other.Filename); + return std::tie(StartLineNumber, EndLineNumber, Filename) == + std::tie(Other.StartLineNumber, Other.EndLineNumber, Other.Filename); } bool operator!=(const Location &Other) const { - return std::tie(LineNumber, Filename) != - std::tie(Other.LineNumber, Other.Filename); + return std::tie(StartLineNumber, Filename) != + std::tie(Other.StartLineNumber, Other.Filename); } // This operator is used to sort a vector of Locations. @@ -258,11 +269,12 @@ struct Location { // sort is enough, the order is only needed to call std::unique after sorting // the vector. bool operator<(const Location &Other) const { - return std::tie(LineNumber, Filename) < - std::tie(Other.LineNumber, Other.Filename); + return std::tie(StartLineNumber, Filename) < + std::tie(Other.StartLineNumber, Other.Filename); } - int LineNumber = 0; // Line number of this Location. + int StartLineNumber = 0; // Line number of this Location. + int EndLineNumber = 0; // End line number of this Location. SmallString<32> Filename; // File for this Location. bool IsFileInRootDir = false; // Indicates if file is inside root directory }; @@ -359,6 +371,9 @@ struct FunctionInfo : public SymbolInfo { // Full qualified name of this function, including namespaces and template // specializations. SmallString<16> FullName; + + // Function Prototype + SmallString<256> ProtoType; // When present, this function is a template or specialization. std::optional Template; @@ -379,7 +394,7 @@ struct RecordInfo : public SymbolInfo { // Full qualified name of this record, including namespaces and template // specializations. SmallString<16> FullName; - + // When present, this record is a template or specialization. std::optional Template; @@ -412,12 +427,15 @@ struct TypedefInfo : public SymbolInfo { void merge(TypedefInfo &&I); TypeInfo Underlying; - - // Inidicates if this is a new C++ "using"-style typedef: + // Underlying type declaration + SmallString<16> TypeDeclaration; + // Indicates if this is a new C++ "using"-style typedef: // using MyVector = std::vector // False means it's a C-style typedef: // typedef std::vector MyVector; bool IsUsing = false; + + std::vector Description; }; struct BaseRecordInfo : public RecordInfo { @@ -455,8 +473,9 @@ struct EnumValueInfo { // Stores the user-supplied initialization expression for this enumeration // constant. This will be empty for implicit enumeration values. SmallString<16> ValueExpr; - - std::vector Description; /// Comment description of this field. + + /// Comment description of this field. + std::vector Description; }; // TODO: Expand to allow for documenting templating. @@ -521,8 +540,9 @@ struct ClangDocContext { // Path of CSS stylesheets that will be copied to OutDirectory and used to // style all HTML files. std::vector UserStylesheets; - // JavaScript files that will be imported in all HTML files. std::vector JsScripts; + // Mustache Template files + llvm::StringMap MustacheTemplates; Index Idx; }; diff --git a/clang-tools-extra/clang-doc/Serialize.cpp b/clang-tools-extra/clang-doc/Serialize.cpp index f737fc75135a1..d4d1436646575 100644 --- a/clang-tools-extra/clang-doc/Serialize.cpp +++ b/clang-tools-extra/clang-doc/Serialize.cpp @@ -11,6 +11,7 @@ #include "clang/AST/Comment.h" #include "clang/Index/USRGeneration.h" #include "clang/Lex/Lexer.h" +#include "clang/AST/Attr.h" #include "llvm/ADT/Hashing.h" #include "llvm/ADT/StringExtras.h" #include "llvm/Support/SHA1.h" @@ -32,6 +33,183 @@ populateParentNamespaces(llvm::SmallVector &Namespaces, static void populateMemberTypeInfo(MemberTypeInfo &I, const FieldDecl *D); +void getTemplateParameters(const TemplateParameterList *TemplateParams, + llvm::raw_ostream &Stream) { + Stream << "template <"; + + for (unsigned i = 0; i < TemplateParams->size(); ++i) { + if (i > 0) { + Stream << ", "; + } + + const NamedDecl *Param = TemplateParams->getParam(i); + if (const auto *TTP = llvm::dyn_cast(Param)) { + if (TTP->wasDeclaredWithTypename()) { + Stream << "typename"; + } else { + Stream << "class"; + } + if (TTP->isParameterPack()) { + Stream << "..."; + } + Stream << " " << TTP->getNameAsString(); + } else if (const auto *NTTP = llvm::dyn_cast(Param)) { + NTTP->getType().print(Stream, NTTP->getASTContext().getPrintingPolicy()); + if (NTTP->isParameterPack()) { + Stream << "..."; + } + Stream << " " << NTTP->getNameAsString(); + } else if (const auto *TTPD = llvm::dyn_cast(Param)) { + Stream << "template <"; + getTemplateParameters(TTPD->getTemplateParameters(), Stream); + Stream << "> class " << TTPD->getNameAsString(); + } + } + + Stream << "> "; +} + +// Extract the full function prototype from a FunctionDecl including +// Full Decl +llvm::SmallString<256> getFunctionPrototype(const FunctionDecl *FuncDecl) { + llvm::SmallString<256> Result; + llvm::raw_svector_ostream Stream(Result); + const ASTContext& Ctx = FuncDecl->getASTContext(); + const auto *Method = llvm::dyn_cast(FuncDecl); + // If it's a templated function, handle the template parameters + if (const auto *TmplDecl = FuncDecl->getDescribedTemplate()) { + getTemplateParameters(TmplDecl->getTemplateParameters(), Stream); + } + // If it's a virtual method + if (Method) { + if (Method->isVirtual()) + { + Stream << "virtual "; + } + } + // Print return type + FuncDecl->getReturnType().print(Stream, Ctx.getPrintingPolicy()); + + // Print function name + Stream << " " << FuncDecl->getNameAsString() << "("; + + // Print parameter list with types, names, and default values + for (unsigned I = 0; I < FuncDecl->getNumParams(); ++I) { + if (I > 0) { + Stream << ", "; + } + const ParmVarDecl *ParamDecl = FuncDecl->getParamDecl(I); + QualType ParamType = ParamDecl->getType(); + ParamType.print(Stream, Ctx.getPrintingPolicy()); + + // Print parameter name if it has one + if (!ParamDecl->getName().empty()) { + Stream << " " << ParamDecl->getNameAsString(); + } + + // Print default argument if it exists + if (ParamDecl->hasDefaultArg()) { + const Expr *DefaultArg = ParamDecl->getDefaultArg(); + if (DefaultArg) { + Stream << " = "; + DefaultArg->printPretty(Stream, nullptr, Ctx.getPrintingPolicy()); + } + } + } + + // If it is a variadic function, add '...' + if (FuncDecl->isVariadic()) { + if (FuncDecl->getNumParams() > 0) { + Stream << ", "; + } + Stream << "..."; + } + + Stream << ")"; + + // If it's a const method, add 'const' qualifier + if (Method) { + if (Method->size_overridden_methods()) + Stream << " override"; + if (Method->hasAttr()) + Stream << " final"; + if (Method->isConst()) + Stream << " const"; + if (Method->isPureVirtual()) + Stream << " = 0"; + } + return Result; // Convert SmallString to std::string for return +} + +llvm::SmallString<16> getTypeDefDecl(const TypedefDecl *TypeDef) { + llvm::SmallString<16> Result; + llvm::raw_svector_ostream Stream(Result); + const ASTContext& Ctx = TypeDef->getASTContext(); + Stream << "typedef "; + QualType Q = TypeDef->getUnderlyingType(); + Q.print(Stream, Ctx.getPrintingPolicy()); + Stream << " " << TypeDef->getNameAsString(); + return Result; +} + +llvm::SmallString<16> getTypeAlias(const TypeAliasDecl *Alias) { + llvm::SmallString<16> Result; + llvm::raw_svector_ostream Stream(Result); + const ASTContext& Ctx = Alias->getASTContext(); + if (const auto *TmplDecl = Alias->getDescribedTemplate()) { + getTemplateParameters(TmplDecl->getTemplateParameters(), Stream); + } + Stream << "using " + << Alias->getNameAsString() + << " = "; + QualType Q = Alias->getUnderlyingType(); + Q.print(Stream, Ctx.getPrintingPolicy()); + + return Result; +} + +// extract full syntax for record declaration +llvm::SmallString<16> getRecordPrototype(const CXXRecordDecl *CXXRD) { + llvm::SmallString<16> Result; + LangOptions LangOpts; + PrintingPolicy Policy(LangOpts); + Policy.SuppressTagKeyword = false; + Policy.FullyQualifiedName = true; + Policy.IncludeNewlines = false; + llvm::raw_svector_ostream OS(Result); + if (const auto *TD = CXXRD->getDescribedClassTemplate()) { + OS << "template <"; + bool FirstParam = true; + for (const auto *Param : *TD->getTemplateParameters()) { + if (!FirstParam) OS << ", "; + Param->print(OS, Policy); + FirstParam = false; + } + OS << ">\n"; + } + if (CXXRD->isStruct()) { + OS << "struct "; + } else if (CXXRD->isClass()) { + OS << "class "; + } else if (CXXRD->isUnion()) { + OS << "union "; + } + OS << CXXRD->getNameAsString(); + if (CXXRD->getNumBases() > 0) { + OS << " : "; + bool FirstBase = true; + for (const auto &Base : CXXRD->bases()) { + if (!FirstBase) OS << ", "; + if (Base.isVirtual()) OS << "virtual "; + OS << getAccessSpelling(Base.getAccessSpecifier()) << " "; + OS << Base.getType().getAsString(Policy); + FirstBase = false; + } + } + return Result; +} + + // A function to extract the appropriate relative path for a given info's // documentation. The path returned is a composite of the parent namespaces. // @@ -224,10 +402,38 @@ static SymbolID getUSRForDecl(const Decl *D) { return hashUSR(USR); } -static TagDecl *getTagDeclForType(const QualType &T) { - if (const TagDecl *D = T->getAsTagDecl()) - return D->getDefinition(); - return nullptr; +static QualType getBaseQualType(const QualType &T) { + QualType QT = T; + // Get the base type of the QualType + // eg. int* -> int, int& -> int, const int -> int + bool Modified = true; // Track whether we've modified `qt` in this loop iteration + while (Modified) { + Modified = false; + // If it's a reference type, strip the reference + if (QT->isReferenceType()) { + QT = QT->getPointeeType(); + Modified = true; + } + // If it's a pointer type, strip the pointer + else if (QT->isPointerType()) { + QT = QT->getPointeeType(); + Modified = true; + } + else if (const auto *ElaboratedType = QT->getAs()) { + QT = ElaboratedType->desugar(); + Modified = true; + } + // Remove const/volatile qualifiers if present + else if (QT.hasQualifiers()) { + QT = QT.getUnqualifiedType(); + Modified = true; + } + else if (const auto *TypedefType = QT->getAs()) { + return QT; + } + } + + return QT; } static RecordDecl *getRecordDeclForType(const QualType &T) { @@ -236,29 +442,37 @@ static RecordDecl *getRecordDeclForType(const QualType &T) { return nullptr; } -TypeInfo getTypeInfoForType(const QualType &T, const PrintingPolicy &Policy) { - const TagDecl *TD = getTagDeclForType(T); - if (!TD) - return TypeInfo(Reference(SymbolID(), T.getAsString(Policy))); - +TypeInfo getTypeInfoForType(const QualType &T) { + const QualType QT = getBaseQualType(T); + const TagDecl *TD = QT->getAsTagDecl(); + if (!TD) { + TypeInfo TI = TypeInfo(Reference(SymbolID(), T.getAsString())); + TI.IsBuiltIn = QT->isBuiltinType(); + TI.IsTemplate = QT->isTemplateTypeParmType(); + return TI; + } InfoType IT; - if (dyn_cast(TD)) { + if (isa(TD)) IT = InfoType::IT_enum; - } else if (dyn_cast(TD)) { + else if (isa(TD)) IT = InfoType::IT_record; - } else { + else IT = InfoType::IT_default; - } - return TypeInfo(Reference(getUSRForDecl(TD), TD->getNameAsString(), IT, - T.getAsString(Policy), getInfoRelativePath(TD))); + + Reference R = Reference(getUSRForDecl(TD), TD->getNameAsString(), IT, + T.getAsString(), getInfoRelativePath(TD)); + TypeInfo TI = TypeInfo(R); + TI.IsBuiltIn = QT->isBuiltinType(); + TI.IsTemplate = QT->isTemplateTypeParmType(); + return TI; } static bool isPublic(const clang::AccessSpecifier AS, const clang::Linkage Link) { if (AS == clang::AccessSpecifier::AS_private) return false; - else if ((Link == clang::Linkage::Module) || - (Link == clang::Linkage::External)) + if ((Link == clang::Linkage::Module) || + (Link == clang::Linkage::External)) return true; return false; // otherwise, linkage is some form of internal linkage } @@ -379,11 +593,10 @@ static void parseFields(RecordInfo &I, const RecordDecl *D, bool PublicOnly, if (!shouldSerializeInfo(PublicOnly, /*IsInAnonymousNamespace=*/false, F)) continue; - auto &LO = F->getLangOpts(); // Use getAccessUnsafe so that we just get the default AS_none if it's not // valid, as opposed to an assert. MemberTypeInfo &NewMember = I.Members.emplace_back( - getTypeInfoForType(F->getTypeSourceInfo()->getType(), LO), + getTypeInfoForType(F->getTypeSourceInfo()->getType()), F->getNameAsString(), getFinalAccessSpecifier(Access, F->getAccessUnsafe())); populateMemberTypeInfo(NewMember, F); @@ -401,7 +614,6 @@ static void parseEnumerators(EnumInfo &I, const EnumDecl *D) { ASTContext &Context = E->getASTContext(); if (RawComment *Comment = E->getASTContext().getRawCommentForDeclNoCache(E)) { - CommentInfo CInfo; Comment->setAttached(); if (comments::FullComment *Fc = Comment->parse(Context, nullptr, E)) { EnumValueInfo &Member = I.Members.back(); @@ -413,10 +625,9 @@ static void parseEnumerators(EnumInfo &I, const EnumDecl *D) { } static void parseParameters(FunctionInfo &I, const FunctionDecl *D) { - auto &LO = D->getLangOpts(); for (const ParmVarDecl *P : D->parameters()) { FieldTypeInfo &FieldInfo = I.Params.emplace_back( - getTypeInfoForType(P->getOriginalType(), LO), P->getNameAsString()); + getTypeInfoForType(P->getOriginalType()), P->getNameAsString()); FieldInfo.DefaultValue = getSourceCode(D, P->getDefaultArgRange()); } } @@ -427,6 +638,7 @@ static void parseBases(RecordInfo &I, const CXXRecordDecl *D) { // Don't parse bases if this isn't a definition. if (!D->isThisDeclarationADefinition()) return; + for (const CXXBaseSpecifier &B : D->bases()) { if (B.isVirtual()) continue; @@ -489,7 +701,7 @@ populateParentNamespaces(llvm::SmallVector &Namespaces, InfoType::IT_namespace); } -void PopulateTemplateParameters(std::optional &TemplateInfo, +void populateTemplateParameters(std::optional &TemplateInfo, const clang::Decl *D) { if (const TemplateParameterList *ParamList = D->getDescribedTemplateParams()) { @@ -527,27 +739,24 @@ static void populateInfo(Info &I, const T *D, const FullComment *C, template static void populateSymbolInfo(SymbolInfo &I, const T *D, const FullComment *C, - int LineNumber, StringRef Filename, - bool IsFileInRootDir, + Location Loc, bool &IsInAnonymousNamespace) { populateInfo(I, D, C, IsInAnonymousNamespace); if (D->isThisDeclarationADefinition()) - I.DefLoc.emplace(LineNumber, Filename, IsFileInRootDir); + I.DefLoc = Loc; else - I.Loc.emplace_back(LineNumber, Filename, IsFileInRootDir); + I.Loc.emplace_back(Loc); } static void populateFunctionInfo(FunctionInfo &I, const FunctionDecl *D, - const FullComment *FC, int LineNumber, - StringRef Filename, bool IsFileInRootDir, + const FullComment *FC, + Location Loc, bool &IsInAnonymousNamespace) { - populateSymbolInfo(I, D, FC, LineNumber, Filename, IsFileInRootDir, - IsInAnonymousNamespace); - auto &LO = D->getLangOpts(); - I.ReturnType = getTypeInfoForType(D->getReturnType(), LO); + populateSymbolInfo(I, D, FC, Loc, IsInAnonymousNamespace); + I.ReturnType = getTypeInfoForType(D->getReturnType()); + I.ProtoType = getFunctionPrototype(D); parseParameters(I, D); - - PopulateTemplateParameters(I.Template, D); + populateTemplateParameters(I.Template, D); // Handle function template specializations. if (const FunctionTemplateSpecializationInfo *FTSI = @@ -570,7 +779,8 @@ static void populateFunctionInfo(FunctionInfo &I, const FunctionDecl *D, static void populateMemberTypeInfo(MemberTypeInfo &I, const FieldDecl *D) { assert(D && "Expect non-null FieldDecl in populateMemberTypeInfo"); - + if (!D) + return; ASTContext& Context = D->getASTContext(); // TODO investigate whether we can use ASTContext::getCommentForDecl instead // of this logic. See also similar code in Mapper.cpp. @@ -579,9 +789,9 @@ static void populateMemberTypeInfo(MemberTypeInfo &I, const FieldDecl *D) { return; Comment->setAttached(); - if (comments::FullComment *fc = Comment->parse(Context, nullptr, D)) { + if (comments::FullComment *Fc = Comment->parse(Context, nullptr, D)) { I.Description.emplace_back(); - parseFullComment(fc, I.Description.back()); + parseFullComment(Fc, I.Description.back()); } } @@ -623,8 +833,7 @@ parseBases(RecordInfo &I, const CXXRecordDecl *D, bool IsFileInRootDir, // reference, its value is not relevant in here so it's not used // anywhere besides the function call. bool IsInAnonymousNamespace; - populateFunctionInfo(FI, MD, /*FullComment=*/{}, /*LineNumber=*/{}, - /*FileName=*/{}, IsFileInRootDir, + populateFunctionInfo(FI, MD, /*FullComment=*/{}, /*Location=*/{}, IsInAnonymousNamespace); FI.Access = getFinalAccessSpecifier(BI.Access, MD->getAccessUnsafe()); @@ -642,8 +851,8 @@ parseBases(RecordInfo &I, const CXXRecordDecl *D, bool IsFileInRootDir, } std::pair, std::unique_ptr> -emitInfo(const NamespaceDecl *D, const FullComment *FC, int LineNumber, - llvm::StringRef File, bool IsFileInRootDir, bool PublicOnly) { +emitInfo(const NamespaceDecl *D, const FullComment *FC, Location Loc, + bool PublicOnly) { auto I = std::make_unique(); bool IsInAnonymousNamespace = false; populateInfo(*I, D, FC, IsInAnonymousNamespace); @@ -663,29 +872,29 @@ emitInfo(const NamespaceDecl *D, const FullComment *FC, int LineNumber, } std::pair, std::unique_ptr> -emitInfo(const RecordDecl *D, const FullComment *FC, int LineNumber, - llvm::StringRef File, bool IsFileInRootDir, bool PublicOnly) { +emitInfo(const RecordDecl *D, const FullComment *FC, + Location Loc, bool PublicOnly) { auto I = std::make_unique(); bool IsInAnonymousNamespace = false; - populateSymbolInfo(*I, D, FC, LineNumber, File, IsFileInRootDir, - IsInAnonymousNamespace); + populateSymbolInfo(*I, D, FC, Loc, IsInAnonymousNamespace); if (!shouldSerializeInfo(PublicOnly, IsInAnonymousNamespace, D)) return {}; - + I->TagType = D->getTagKind(); parseFields(*I, D, PublicOnly); if (const auto *C = dyn_cast(D)) { + I->FullName = getRecordPrototype(C); if (const TypedefNameDecl *TD = C->getTypedefNameForAnonDecl()) { I->Name = TD->getNameAsString(); I->IsTypeDef = true; } // TODO: remove first call to parseBases, that function should be deleted parseBases(*I, C); - parseBases(*I, C, IsFileInRootDir, PublicOnly, true); + parseBases(*I, C, true, PublicOnly, true); } I->Path = getInfoRelativePath(I->Namespace); - PopulateTemplateParameters(I->Template, D); + populateTemplateParameters(I->Template, D); // Full and partial specializations. if (auto *CTSD = dyn_cast(D)) { @@ -696,11 +905,13 @@ emitInfo(const RecordDecl *D, const FullComment *FC, int LineNumber, // What this is a specialization of. auto SpecOf = CTSD->getSpecializedTemplateOrPartial(); - if (auto *CTD = dyn_cast(SpecOf)) - Specialization.SpecializationOf = getUSRForDecl(CTD); - else if (auto *CTPSD = - dyn_cast(SpecOf)) - Specialization.SpecializationOf = getUSRForDecl(CTPSD); + if (SpecOf.is()) { + Specialization.SpecializationOf = + getUSRForDecl(SpecOf.get()); + } else if (SpecOf.is()) { + Specialization.SpecializationOf = + getUSRForDecl(SpecOf.get()); + } // Parameters to the specilization. For partial specializations, get the // parameters "as written" from the ClassTemplatePartialSpecializationDecl @@ -722,7 +933,6 @@ emitInfo(const RecordDecl *D, const FullComment *FC, int LineNumber, } } } - // Records are inserted into the parent by reference, so we need to return // both the parent and the record itself. auto Parent = MakeAndInsertIntoParent(*I); @@ -730,12 +940,11 @@ emitInfo(const RecordDecl *D, const FullComment *FC, int LineNumber, } std::pair, std::unique_ptr> -emitInfo(const FunctionDecl *D, const FullComment *FC, int LineNumber, - llvm::StringRef File, bool IsFileInRootDir, bool PublicOnly) { +emitInfo(const FunctionDecl *D, const FullComment *FC, + Location Loc, bool PublicOnly) { FunctionInfo Func; bool IsInAnonymousNamespace = false; - populateFunctionInfo(Func, D, FC, LineNumber, File, IsFileInRootDir, - IsInAnonymousNamespace); + populateFunctionInfo(Func, D, FC, Loc, IsInAnonymousNamespace); Func.Access = clang::AccessSpecifier::AS_none; if (!shouldSerializeInfo(PublicOnly, IsInAnonymousNamespace, D)) return {}; @@ -745,12 +954,11 @@ emitInfo(const FunctionDecl *D, const FullComment *FC, int LineNumber, } std::pair, std::unique_ptr> -emitInfo(const CXXMethodDecl *D, const FullComment *FC, int LineNumber, - llvm::StringRef File, bool IsFileInRootDir, bool PublicOnly) { +emitInfo(const CXXMethodDecl *D, const FullComment *FC, + Location Loc, bool PublicOnly) { FunctionInfo Func; bool IsInAnonymousNamespace = false; - populateFunctionInfo(Func, D, FC, LineNumber, File, IsFileInRootDir, - IsInAnonymousNamespace); + populateFunctionInfo(Func, D, FC, Loc, IsInAnonymousNamespace); if (!shouldSerializeInfo(PublicOnly, IsInAnonymousNamespace, D)) return {}; @@ -774,18 +982,21 @@ emitInfo(const CXXMethodDecl *D, const FullComment *FC, int LineNumber, } std::pair, std::unique_ptr> -emitInfo(const TypedefDecl *D, const FullComment *FC, int LineNumber, - StringRef File, bool IsFileInRootDir, bool PublicOnly) { +emitInfo(const TypedefDecl *D, const FullComment *FC, Location Loc, + bool PublicOnly) { + TypedefInfo Info; - + ASTContext& Context = D->getASTContext(); bool IsInAnonymousNamespace = false; populateInfo(Info, D, FC, IsInAnonymousNamespace); + if (!shouldSerializeInfo(PublicOnly, IsInAnonymousNamespace, D)) return {}; - - Info.DefLoc.emplace(LineNumber, File, IsFileInRootDir); - auto &LO = D->getLangOpts(); - Info.Underlying = getTypeInfoForType(D->getUnderlyingType(), LO); + + Info.DefLoc = Loc; + Info.Underlying = getTypeInfoForType(D->getUnderlyingType()); + Info.TypeDeclaration = getTypeDefDecl(D); + if (Info.Underlying.Type.Name.empty()) { // Typedef for an unnamed type. This is like "typedef struct { } Foo;" // The record serializer explicitly checks for this syntax and constructs @@ -793,7 +1004,13 @@ emitInfo(const TypedefDecl *D, const FullComment *FC, int LineNumber, return {}; } Info.IsUsing = false; - + if (RawComment *Comment = D->getASTContext().getRawCommentForDeclNoCache(D)) { + Comment->setAttached(); + if (comments::FullComment *Fc = Comment->parse(Context, nullptr, D)) { + Info.Description.emplace_back(); + parseFullComment(Fc, Info.Description.back()); + } + } // Info is wrapped in its parent scope so is returned in the second position. return {nullptr, MakeAndInsertIntoParent(std::move(Info))}; } @@ -801,31 +1018,38 @@ emitInfo(const TypedefDecl *D, const FullComment *FC, int LineNumber, // A type alias is a C++ "using" declaration for a type. It gets mapped to a // TypedefInfo with the IsUsing flag set. std::pair, std::unique_ptr> -emitInfo(const TypeAliasDecl *D, const FullComment *FC, int LineNumber, - StringRef File, bool IsFileInRootDir, bool PublicOnly) { +emitInfo(const TypeAliasDecl *D, const FullComment *FC, + Location Loc, bool PublicOnly) { TypedefInfo Info; - + ASTContext& Context = D->getASTContext(); bool IsInAnonymousNamespace = false; populateInfo(Info, D, FC, IsInAnonymousNamespace); if (!shouldSerializeInfo(PublicOnly, IsInAnonymousNamespace, D)) return {}; - Info.DefLoc.emplace(LineNumber, File, IsFileInRootDir); - auto &LO = D->getLangOpts(); - Info.Underlying = getTypeInfoForType(D->getUnderlyingType(), LO); + Info.DefLoc = Loc; + Info.Underlying = getTypeInfoForType(D->getUnderlyingType()); + Info.TypeDeclaration = getTypeAlias(D); Info.IsUsing = true; - + + if (RawComment *Comment = D->getASTContext().getRawCommentForDeclNoCache(D)) { + Comment->setAttached(); + if (comments::FullComment *Fc = Comment->parse(Context, nullptr, D)) { + Info.Description.emplace_back(); + parseFullComment(Fc, Info.Description.back()); + } + } // Info is wrapped in its parent scope so is returned in the second position. return {nullptr, MakeAndInsertIntoParent(std::move(Info))}; } std::pair, std::unique_ptr> -emitInfo(const EnumDecl *D, const FullComment *FC, int LineNumber, - llvm::StringRef File, bool IsFileInRootDir, bool PublicOnly) { +emitInfo(const EnumDecl *D, const FullComment *FC, Location Loc, + bool PublicOnly) { EnumInfo Enum; bool IsInAnonymousNamespace = false; - populateSymbolInfo(Enum, D, FC, LineNumber, File, IsFileInRootDir, - IsInAnonymousNamespace); + populateSymbolInfo(Enum, D, FC, Loc, IsInAnonymousNamespace); + if (!shouldSerializeInfo(PublicOnly, IsInAnonymousNamespace, D)) return {}; diff --git a/clang-tools-extra/clang-doc/Serialize.h b/clang-tools-extra/clang-doc/Serialize.h index 4e203ca7891ac..8874299e9af9e 100644 --- a/clang-tools-extra/clang-doc/Serialize.h +++ b/clang-tools-extra/clang-doc/Serialize.h @@ -37,32 +37,32 @@ namespace serialize { // its parent scope. For NamespaceDecl and RecordDecl both elements are not // nullptr. std::pair, std::unique_ptr> -emitInfo(const NamespaceDecl *D, const FullComment *FC, int LineNumber, - StringRef File, bool IsFileInRootDir, bool PublicOnly); +emitInfo(const NamespaceDecl *D, const FullComment *FC, Location Loc, + bool PublicOnly); std::pair, std::unique_ptr> -emitInfo(const RecordDecl *D, const FullComment *FC, int LineNumber, - StringRef File, bool IsFileInRootDir, bool PublicOnly); +emitInfo(const RecordDecl *D, const FullComment *FC, Location Loc, + bool PublicOnly); std::pair, std::unique_ptr> -emitInfo(const EnumDecl *D, const FullComment *FC, int LineNumber, - StringRef File, bool IsFileInRootDir, bool PublicOnly); +emitInfo(const EnumDecl *D, const FullComment *FC, Location Loc, + bool PublicOnly); std::pair, std::unique_ptr> -emitInfo(const FunctionDecl *D, const FullComment *FC, int LineNumber, - StringRef File, bool IsFileInRootDir, bool PublicOnly); +emitInfo(const FunctionDecl *D, const FullComment *FC, Location Loc, + bool PublicOnly); std::pair, std::unique_ptr> -emitInfo(const CXXMethodDecl *D, const FullComment *FC, int LineNumber, - StringRef File, bool IsFileInRootDir, bool PublicOnly); +emitInfo(const CXXMethodDecl *D, const FullComment *FC, Location Loc, + bool PublicOnly); std::pair, std::unique_ptr> -emitInfo(const TypedefDecl *D, const FullComment *FC, int LineNumber, - StringRef File, bool IsFileInRootDir, bool PublicOnly); +emitInfo(const TypedefDecl *D, const FullComment *FC, Location Loc, + bool PublicOnly); std::pair, std::unique_ptr> -emitInfo(const TypeAliasDecl *D, const FullComment *FC, int LineNumber, - StringRef File, bool IsFileInRootDir, bool PublicOnly); +emitInfo(const TypeAliasDecl *D, const FullComment *FC, Location Loc, + bool PublicOnly); // Function to hash a given USR value for storage. // As USRs (Unified Symbol Resolution) could be large, especially for functions diff --git a/clang-tools-extra/clang-doc/assets/clang-doc-mustache.css b/clang-tools-extra/clang-doc/assets/clang-doc-mustache.css new file mode 100644 index 0000000000000..a885a36cb4a3d --- /dev/null +++ b/clang-tools-extra/clang-doc/assets/clang-doc-mustache.css @@ -0,0 +1,471 @@ +/* css for clang-doc mustache backend */ +@import "https://fonts.googleapis.com/css2?family=Inter:ital,opsz,wght@0,14..32,100..900;1,14..32,100..900&display=swap"; + +*,*::before *::after { + box-sizing:border-box +} +* { + margin:0; + padding:0 +} +ol, +ul { + list-style:none +} +img, +picture, +svg, +video { + display:block; + max-width:100% +} + +* { + --brand-light:#ce6300; + --text1-light:#000000; + --text2-light:#333333; + --surface1-light:#ffffff; + --surface2-light:#f5f5f5; + --brand-dark:#de9853; + --text1-dark:#ffffff; + --text2-dark:#cccccc; + --surface1-dark:#161212; + --surface2-dark:#272424 +} + +:root { + color-scheme:light; + --brand:var(--brand-light); + --text1:var(--text1-light); + --text2:var(--text2-light); + --text1-inverse:var(--text1-dark); + --text2-inverse:var(--text2-dark); + --surface1:var(--surface1-light); + --surface2:var(--surface2-light) +} + +@media(prefers-color-scheme:dark) { + :root { + color-scheme:dark; + --brand:var(--brand-dark); + --text1:var(--text1-dark); + --text2:var(--text2-dark); + --text1-inverse:var(--text1-light); + --text2-inverse:var(--text2-light); + --surface1:var(--surface1-dark); + --surface2:var(--surface2-dark) + } +} + +[color-scheme=light] { + color-scheme:light; + --brand:var(--brand-light); + --text1:var(--text1-light); + --text2:var(--text2-light); + --text1-inverse:var(--text1-dark); + --text2-inverse:var(--text2-dark); + --surface1:var(--surface1-light); + --surface2:var(--surface2-light) +} + +[color-scheme=dark] { + color-scheme:dark; + --brand:var(--brand-dark); + --text1:var(--text1-dark); + --text2:var(--text2-dark); + --text1-inverse:var(--text1-light); + --text2-inverse:var(--text2-light); + --surface1:var(--surface1-dark); + --surface2:var(--surface2-dark) +} + +html { + background-color:var(--surface1) +} + +html, body { + min-height: 100vh; + margin: 0; + padding: 0; + width: 100%; +} + +.container { + display: flex; + margin-top: 60px; + height: calc(100% - 60px); + box-sizing: border-box; +} + +body, html { + font-family:Inter,sans-serif; + margin: 0; + padding: 0; + height: 100%; +} + +/* Navbar Styles */ +.navbar { + background-color: var(--surface2); + border-bottom: 1px solid var(--text2); + position: fixed; + width: 100%; + top: 0; + left: 0; + height: 60px; /* Adjust as needed */ + color: white; + display: flex; + align-items: center; + padding: 0 20px; + box-sizing: border-box; + z-index: 1000; +} + + +.navbar__container { + display:flex; + justify-content:space-between; + align-items:center; + padding:1rem; + color:var(--text1); + max-width:2048px; + margin:auto +} +.navbar__logo { + display:flex; + align-items:center; + height:40px +} +.navbar__logo a { + display:flex; + align-items:center; + text-decoration:none; + height:100% +} +.navbar__logo img { + height:100%; + width:auto +} +.navbar__toggle { + background:0 0; + color:var(--text2); + border:none; + cursor:pointer; + font-size:1.5rem; + width:2.5rem; + height:2.5rem; + margin-left:auto +} +.navbar__toggle:hover { + color:var(--text1) +} +@media(min-width:769px) { + .navbar__toggle { + display:none + } +} +.navbar__menu { + display:flex; + justify-content:space-between; + align-items:center; + list-style:none; + margin:0; + padding:0; + gap:.25rem; + margin-left:auto +} + +@media(max-width:768px) { + .navbar__menu { + flex-direction:column; + justify-content:flex-start; + width:100%; + background-color:var(--surface2); + position:fixed; + top:0; + left:0; + right:0; + bottom:0; + padding:1.5rem; + transform:translateX(100%); + transition:transform .5s ease-in-out + } +} +@media(max-width:768px) { + .navbar__menu.active { + transform:translateX(0) + } +} +.navbar__close { + background:0 0; + border:none; + cursor:pointer; + font-size:1.5rem; + color:var(--text2); + margin-left:auto +} +.navbar__close:hover { + color:var(--text1) +} + +@media(min-width:769px) { + .navbar__close { + display:none + } +} +.navbar__links { + display:flex; + gap:1rem; + align-items:center; + margin:0; + padding:0 +} + +@media(max-width:768px) { + .navbar__links { + flex-direction:column + } +} + +.navbar__item { + list-style-type:none +} + +.navbar__link { + color:var(--text2); + text-decoration:none; + padding:.5rem +} + +.navbar__link:hover { + color:var(--text1) +} + +.navbar__theme-toggle-button { + background:0 0; + color:var(--text2); + border:none; + cursor:pointer; + font-size:1.5rem; + width:2.5rem; + height:2.5rem +} + +.navbar__theme-toggle-button:hover { + color:var(--text1) +} + +.hero__container { + margin-top:1rem; + display:flex; + justify-content:center; + align-items:center; + gap:2rem +} + +.hero__title { + font-size:2.5rem; + margin-bottom:.5rem +} + +.hero__title-large { + font-size:3rem +} + +@media(max-width:768px) { + .hero__title-large { + font-size:2.5rem + } +} + +@media(max-width:480px) { + .hero__title-large { + font-size:2rem + } +} + +@media(max-width:768px) { + .hero__title { + font-size:2rem + } +} + +@media(max-width:480px) { + .hero__title { + font-size:1.75rem + } +} + +.hero__subtitle { + font-size:1.25rem; + font-weight:500 +} + +@media(max-width:768px) { + .hero__subtitle { + font-size:1rem + } +} + +@media(max-width:480px) { + .hero__subtitle { + font-size:.875rem + } +} + +.section-container { + max-width: 2048px; + margin-left:auto; + margin-right:auto; + margin-top:0; + margin-bottom: 1rem; + padding:1rem 2rem +} + +@media(max-width:768px) { + .section-container { + padding:1rem + } +} + +.section-container h2 { + font-size:1.5rem; + margin-bottom:1rem; + color:var(--brand); + border-bottom: 1px solid var(--text2); +} + +@media(max-width:768px) { + .section-container h2 { + font-size:1.25rem + } +} + +.section-container p { + font-size:1rem; + line-height:1.5 +} + +@media(max-width:768px) { + .section-container p { + font-size:.875rem + } +} + +.home__row { + display:grid; + grid-template-columns:repeat(auto-fit,minmax(300px,1fr)); + gap:2rem +} + +.table-wrapper { + display:flex; + flex-direction:column; + padding:1rem; + border-collapse: collapse; /* Ensures there are no gaps between cells */ +} + +.table-wrapper th, .table-wrapper td { + padding: 0.5rem 1rem; /* Adds padding inside the cells */ + border:1px solid var(--text1); + text-align: left; +} + +.block-command-command { + font-weight: bold; +} + +.code-clang-doc { + font-size: 1.1rem; +} + +.delimiter-container { + padding: 0.5rem 1rem; + margin-bottom:1rem; +} + +.resizer { + width: 5px; + cursor: col-resize; + background-color: var(--text2); +} + +.resizer:hover { + background-color: var(--text2-inverse); +} + +.sidebar { + width: 250px; + top: 0; + left: 0; + height: 100%; + position: fixed; + background-color: var(--surface1); + display: flex; + border-left: 1px solid var(--text2); + flex-direction: column; + overflow-y: auto; + scrollbar-width: thin; +} + +.sidebar h2 { + margin-top: 0; + margin-bottom: 20px; + padding: 10px; +} + +.sidebar ul { + width: 100%; + padding: 0; + list-style-type: none; +} + +.sidebar ul li { + padding-right: 1rem; + padding-left: 2rem; + padding-top: 0.25rem; + padding-bottom: 0.25rem; +} + +.sidebar-section { + font-size:1.5rem; + font-weight: bold; + margin-bottom: 1rem; + padding: 3rem; +} +.sidebar-section a { + color: var(--brand) +} + +/* Content */ +.content { + background-color: var(--text1-inverse); + padding: 20px; + left: 250px; + position: relative; + width: calc(100% - 250px); + height: 100vh; +} + +.sidebar-item { + color: var(--text1); +} + +.sidebar-item-container:hover { + width: 100%; + background-color: grey; +} + +.sidebar-item-container:hover a { + width: 100%; + color: var(--text1-inverse); +} + +.class-container { + padding: 0.5rem 1rem; +} + +a, a:visited, a:hover, a:active { + text-decoration: none; + color: inherit; +} diff --git a/clang-tools-extra/clang-doc/assets/class-template.mustache b/clang-tools-extra/clang-doc/assets/class-template.mustache new file mode 100644 index 0000000000000..7ce51c6e16211 --- /dev/null +++ b/clang-tools-extra/clang-doc/assets/class-template.mustache @@ -0,0 +1,227 @@ +{{! + Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. + See https://llvm.org/LICENSE.txt for license information. + SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + + This file defines the template for classes/struct +}} + + + + + {{Name}} + {{#Stylesheets}} + + {{/Stylesheets}} + {{#Scripts}} + + {{/Scripts}} + {{! Highlight.js dependency for syntax highlighting }} + + + + + + +
+
+ +
+
+
+
+

{{RecordType}} {{Name}}

+ {{#RecordComments}} +
+ {{>Comments}} +
+ {{/RecordComments}} +
+
+ {{#PublicMembers}} +
+

Public Members

+
+ {{#Obj}} +
+
+{{Type}} {{Name}}
+                        
+ {{#MemberComments}} +
+ {{>Comments}} +
+ {{/MemberComments}} +
+ {{/Obj}} +
+
+ {{/PublicMembers}} + {{#ProtectedMembers}} +
+

Protected Members

+
+ {{#Obj}} +
+
+{{Type}} {{Name}}
+                        
+ {{#MemberComments}} +
+ {{>Comments}} +
+ {{/MemberComments}} +
+ {{/Obj}} +
+
+ {{/ProtectedMembers}} + {{#PublicFunction}} +
+

Public Methods

+
+ {{#Obj}} +{{>FunctionPartial}} + {{/Obj}} +
+
+ {{/PublicFunction}} + {{#ProtectedFunction}} +
+

Protected Methods

+
+ {{#Obj}} +{{>FunctionPartial}} + {{/Obj}} +
+
+ {{/ProtectedFunction}} + {{#Enums}} +
+

Enumerations

+
+ {{#Obj}} +{{>EnumPartial}} + {{/Obj}} +
+
+ {{/Enums}} + {{#Record}} +
+

Inner Classes

+ +
+ {{/Record}} + {{#Typedef}} +
+

Enums

+
+ {{/Typedef}} +
+
+
+ + \ No newline at end of file diff --git a/clang-tools-extra/clang-doc/assets/comments-template.mustache b/clang-tools-extra/clang-doc/assets/comments-template.mustache new file mode 100644 index 0000000000000..f6b62b4407b9c --- /dev/null +++ b/clang-tools-extra/clang-doc/assets/comments-template.mustache @@ -0,0 +1,34 @@ +{{! + Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. + See https://llvm.org/LICENSE.txt for license information. + SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + + This file defines templates for generating comments +}} +{{#FullComment}} + {{#Children}} + {{>Comments}} + {{/Children}} +{{/FullComment}} +{{#ParagraphComment}} + {{#Children}} + {{>Comments}} + {{/Children}} +{{/ParagraphComment}} +{{#BlockCommandComment}} +
+
+ {{Command}} +
+
+ {{#Children}} + {{>Comments}} + {{/Children}} +
+
+{{/BlockCommandComment}} +{{#TextComment}} +
+

{{TextComment}}

+
+{{/TextComment}} \ No newline at end of file diff --git a/clang-tools-extra/clang-doc/assets/enum-template.mustache b/clang-tools-extra/clang-doc/assets/enum-template.mustache new file mode 100644 index 0000000000000..d63bf258f8f0f --- /dev/null +++ b/clang-tools-extra/clang-doc/assets/enum-template.mustache @@ -0,0 +1,47 @@ +{{! + Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. + See https://llvm.org/LICENSE.txt for license information. + SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + + This file defines the template for enums +}} +
+
+
+            
+{{EnumName}}
+            
+        
+
+ {{! Enum Values }} + + + + + + {{#HasComment}} + + {{/HasComment}} + + {{#EnumValues}} + + + + {{#EnumValueComments}} + + {{/EnumValueComments}} + + {{/EnumValues}} + +
NameValueComment
{{Name}}{{Value}}{{>Comments}}
+ {{#EnumComments}} +
+ {{>Comments}} +
+ {{/EnumComments}} + {{#Location}} +
+ Defined at line {{LineNumber}} of file {{Filename}} +
+ {{/Location}} +
\ No newline at end of file diff --git a/clang-tools-extra/clang-doc/assets/function-template.mustache b/clang-tools-extra/clang-doc/assets/function-template.mustache new file mode 100644 index 0000000000000..0564647467aa6 --- /dev/null +++ b/clang-tools-extra/clang-doc/assets/function-template.mustache @@ -0,0 +1,23 @@ +{{! + Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. + See https://llvm.org/LICENSE.txt for license information. + SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + + This file defines the template for functions/methods +}} +
+
+ {{! Function Prototype }} +
+            
+{{ReturnType.Name}} {{Name}} ({{#Params}}{{^End}}{{Type}} {{Name}}, {{/End}}{{#End}}{{Type}} {{Name}}{{/End}}{{/Params}})
+            
+        
+ {{! Function Comments }} + {{#FunctionComments}} +
+ {{>Comments}} +
+ {{/FunctionComments}} +
+
\ No newline at end of file diff --git a/clang-tools-extra/clang-doc/assets/mustache-index.js b/clang-tools-extra/clang-doc/assets/mustache-index.js new file mode 100644 index 0000000000000..db320cd8ae403 --- /dev/null +++ b/clang-tools-extra/clang-doc/assets/mustache-index.js @@ -0,0 +1,33 @@ +document.addEventListener("DOMContentLoaded", function() { + const resizer = document.getElementById('resizer'); + const sidebar = document.querySelector('.sidebar'); + + let isResizing = false; + resizer.addEventListener('mousedown', (e) => { + isResizing = true; + }); + + document.addEventListener('mousemove', (e) => { + if (!isResizing) return; + const newWidth = e.clientX; + if (newWidth > 100 && newWidth < window.innerWidth - 100) { + sidebar.style.width = `${newWidth}px`; + } + }); + + document.addEventListener('mouseup', () => { + isResizing = false; + }); + + document.querySelectorAll('pre code').forEach((el) => { + hljs.highlightElement(el); + el.classList.remove("hljs"); + }); + + document.querySelectorAll('.sidebar-item-container').forEach(item => { + item.addEventListener('click', function() { + const anchor = item.getElementsByTagName("a"); + window.location.hash = anchor[0].getAttribute('href'); + }); + }); +}) \ No newline at end of file diff --git a/clang-tools-extra/clang-doc/assets/namespace-template.mustache b/clang-tools-extra/clang-doc/assets/namespace-template.mustache new file mode 100644 index 0000000000000..21cbaa7ec5cf3 --- /dev/null +++ b/clang-tools-extra/clang-doc/assets/namespace-template.mustache @@ -0,0 +1,47 @@ +{{! + Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. + See https://llvm.org/LICENSE.txt for license information. + SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + + This file defines the template for generating namespaces +}} + + + + + {{NamespaceTitle}} + {{#Stylesheets}} + + {{/Stylesheets}} + {{#Scripts}} + + {{/Scripts}} + {{! Highlight.js dependency for syntax highlighting }} + + + + + + +
+
+ +
+
+ Content +
+
+
+ + \ No newline at end of file diff --git a/clang-tools-extra/clang-doc/assets/template.mustache b/clang-tools-extra/clang-doc/assets/template.mustache new file mode 100644 index 0000000000000..1d3407f8b5292 --- /dev/null +++ b/clang-tools-extra/clang-doc/assets/template.mustache @@ -0,0 +1,52 @@ +{{! + Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. + See https://llvm.org/LICENSE.txt for license information. + SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + + This file defines the template for generating Namespaces +}} + + + + + {{NamespaceTitle}} + +

{{NamespaceTitle}}

+ {{#NamespaceComments}} +

Namespace Comment

+ {{/NamespaceComments}} + {{#Namespace}} +

Namespace

+
    + {{#Links}} +
  • + {{Name}} +
  • + {{/Links}} +
+ {{/Namespace}} + {{#Record}} +

Class

+
    + {{#Links}} +
  • + {{Name}} +
  • + {{/Links}} +
+ {{/Record}} + {{#Function}} +

Function

+
+ {{#Obj}} + {{/Obj}} +
+ {{/Function}} + {{#Enums}} +

Enums

+
+ {{#Obj}} + {{/Obj}} +
+ {{/Enums}} + \ No newline at end of file diff --git a/clang-tools-extra/clang-doc/tool/CMakeLists.txt b/clang-tools-extra/clang-doc/tool/CMakeLists.txt index 601a0460d76b3..eccbc99a7ecc4 100644 --- a/clang-tools-extra/clang-doc/tool/CMakeLists.txt +++ b/clang-tools-extra/clang-doc/tool/CMakeLists.txt @@ -21,6 +21,13 @@ target_link_libraries(clang-doc set(assets index.js + mustache-index.js + class-template.mustache + comments-template.mustache + enum-template.mustache + function-template.mustache + namespace-template.mustache + clang-doc-mustache.css clang-doc-default-stylesheet.css ) diff --git a/clang-tools-extra/clang-doc/tool/ClangDocMain.cpp b/clang-tools-extra/clang-doc/tool/ClangDocMain.cpp index 2ce707feb3d5e..0bde27eb2a75e 100644 --- a/clang-tools-extra/clang-doc/tool/ClangDocMain.cpp +++ b/clang-tools-extra/clang-doc/tool/ClangDocMain.cpp @@ -103,6 +103,7 @@ enum OutputFormatTy { md, yaml, html, + mhtml }; static llvm::cl::opt @@ -112,7 +113,9 @@ static llvm::cl::opt clEnumValN(OutputFormatTy::md, "md", "Documentation in MD format."), clEnumValN(OutputFormatTy::html, "html", - "Documentation in HTML format.")), + "Documentation in HTML format."), + clEnumValN(OutputFormatTy::mhtml, "mhtml", + "Documentation in mHTML format")), llvm::cl::init(OutputFormatTy::yaml), llvm::cl::cat(ClangDocCategory)); @@ -124,6 +127,8 @@ std::string getFormatString() { return "md"; case OutputFormatTy::html: return "html"; + case OutputFormatTy::mhtml: + return "mhtml"; } llvm_unreachable("Unknown OutputFormatTy"); } @@ -158,6 +163,15 @@ llvm::Error getAssetFiles(clang::doc::ClangDocContext &CDCtx) { return llvm::Error::success(); } +llvm::SmallString<128> appendPathNative(llvm::SmallString<128> Path, + llvm::StringRef Asset) { + llvm::SmallString<128> Default; + llvm::sys::path::native(Path, Default); + llvm::sys::path::append(Default, Asset); + return Default; +} + + llvm::Error getDefaultAssetFiles(const char *Argv0, clang::doc::ClangDocContext &CDCtx) { void *MainAddr = (void *)(intptr_t)getExecutablePath; @@ -168,13 +182,10 @@ llvm::Error getDefaultAssetFiles(const char *Argv0, llvm::SmallString<128> AssetsPath; AssetsPath = llvm::sys::path::parent_path(NativeClangDocPath); llvm::sys::path::append(AssetsPath, "..", "share", "clang-doc"); - llvm::SmallString<128> DefaultStylesheet; - llvm::sys::path::native(AssetsPath, DefaultStylesheet); - llvm::sys::path::append(DefaultStylesheet, - "clang-doc-default-stylesheet.css"); - llvm::SmallString<128> IndexJS; - llvm::sys::path::native(AssetsPath, IndexJS); - llvm::sys::path::append(IndexJS, "index.js"); + llvm::SmallString<128> DefaultStylesheet = + appendPathNative(AssetsPath, "clang-doc-default-stylesheet.css"); + llvm::SmallString<128> IndexJS = + appendPathNative(AssetsPath, "index.js"); if (!llvm::sys::fs::is_regular_file(IndexJS)) return llvm::createStringError(llvm::inconvertibleErrorCode(), @@ -205,6 +216,57 @@ llvm::Error getHtmlAssetFiles(const char *Argv0, return getDefaultAssetFiles(Argv0, CDCtx); } + +llvm::Error getMustacheHtmlFiles(const char *Argv0, + clang::doc::ClangDocContext &CDCtx) { + if (!UserAssetPath.empty() && + !llvm::sys::fs::is_directory(std::string(UserAssetPath))) + llvm::outs() << "Asset path supply is not a directory: " << UserAssetPath + << " falling back to default\n"; + if (llvm::sys::fs::is_directory(std::string(UserAssetPath))) + return getAssetFiles(CDCtx); + + void *MainAddr = (void *)(intptr_t)getExecutablePath; + std::string ClangDocPath = getExecutablePath(Argv0, MainAddr); + llvm::SmallString<128> NativeClangDocPath; + llvm::sys::path::native(ClangDocPath, NativeClangDocPath); + + llvm::SmallString<128> AssetsPath; + AssetsPath = llvm::sys::path::parent_path(NativeClangDocPath); + llvm::sys::path::append(AssetsPath, "..", "share", "clang-doc"); + + llvm::SmallString<128> DefaultStylesheet + = appendPathNative(AssetsPath, "clang-doc-mustache.css"); + llvm::SmallString<128> NamespaceTemplate + = appendPathNative(AssetsPath, "namespace-template.mustache"); + llvm::SmallString<128> ClassTemplate + = appendPathNative(AssetsPath, "class-template.mustache"); + llvm::SmallString<128> EnumTemplate + = appendPathNative(AssetsPath, "enum-template.mustache"); + llvm::SmallString<128> FunctionTemplate + = appendPathNative(AssetsPath, "function-template.mustache"); + llvm::SmallString<128> CommentTemplate + = appendPathNative(AssetsPath, "comments-template.mustache"); + llvm::SmallString<128> IndexJS + = appendPathNative(AssetsPath, "mustache-index.js"); + + CDCtx.JsScripts.insert(CDCtx.JsScripts.begin(), IndexJS.c_str()); + CDCtx.UserStylesheets.insert(CDCtx.UserStylesheets.begin(), + std::string(DefaultStylesheet)); + CDCtx.MustacheTemplates.insert({"namespace-template", + NamespaceTemplate.c_str()}); + CDCtx.MustacheTemplates.insert({"class-template", + ClassTemplate.c_str()}); + CDCtx.MustacheTemplates.insert({"enum-template", + EnumTemplate.c_str()}); + CDCtx.MustacheTemplates.insert({"function-template", + FunctionTemplate.c_str()}); + CDCtx.MustacheTemplates.insert({"comments-template", + CommentTemplate.c_str()}); + + return llvm::Error::success(); +} + /// Make the output of clang-doc deterministic by sorting the children of /// namespaces and records. void sortUsrToInfo(llvm::StringMap> &USRToInfo) { @@ -277,6 +339,13 @@ Example usage for a project using a compile commands database: return 1; } } + + if (Format == "mhtml") { + if (auto Err = getMustacheHtmlFiles(argv[0], CDCtx)) { + llvm::errs() << toString(std::move(Err)) << "\n"; + return 1; + } + } // Mapping phase llvm::outs() << "Mapping decls...\n"; diff --git a/clang-tools-extra/test/clang-doc/Inputs/basic-project/include/Shape.h b/clang-tools-extra/test/clang-doc/Inputs/basic-project/include/Shape.h index e5c5d4c9e4412..5354032f4d832 100644 --- a/clang-tools-extra/test/clang-doc/Inputs/basic-project/include/Shape.h +++ b/clang-tools-extra/test/clang-doc/Inputs/basic-project/include/Shape.h @@ -26,5 +26,3 @@ class Shape { */ virtual double perimeter() const = 0; }; - - diff --git a/llvm/tools/mustache/CMakeLists.txt b/llvm/tools/mustache/CMakeLists.txt new file mode 100644 index 0000000000000..81e42264d4830 --- /dev/null +++ b/llvm/tools/mustache/CMakeLists.txt @@ -0,0 +1,3 @@ +set(LLVM_LINK_COMPONENTS Support) + +add_llvm_tool(mustache mustache.cpp) \ No newline at end of file diff --git a/llvm/tools/mustache/mustache.cpp b/llvm/tools/mustache/mustache.cpp new file mode 100644 index 0000000000000..6d01d6b88ec9e --- /dev/null +++ b/llvm/tools/mustache/mustache.cpp @@ -0,0 +1,104 @@ +//===- mustache.cpp - The LLVM Modular Optimizer +//-------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// +// Simple drivers to test the mustache spec found here +// https://github.com/mustache/ +// It is used to verify that the current implementation conforms to the spec +// simply download the spec and pass the test files to the driver +// +// Currently Triple Mustache is not supported we expect the following spec +// test to fail: +// Triple Mustache +// Triple Mustache Integer Interpolation +// Triple Mustache Decimal Interpolation +// Triple Mustache Null Interpolation +// Triple Mustache Context Miss Interpolation +// Dotted Names - Triple Mustache Interpolation +// Implicit Iterators - Triple Mustache +// Triple Mustache - Surrounding Whitespace +// Triple Mustache - Standalone +// Triple Mustache With Padding +// Standalone Indentation +// Implicit Iterator - Triple mustache +// +// Usage: +// mustache path/to/test/file/test.json path/to/test/file/test2.json ... +//===----------------------------------------------------------------------===// + +#include "llvm/Support/Mustache.h" +#include "llvm/Support/CommandLine.h" +#include "llvm/Support/MemoryBuffer.h" +#include + +using namespace llvm; +using namespace llvm::json; +using namespace llvm::mustache; + +cl::list InputFiles(cl::Positional, cl::desc(""), + cl::OneOrMore); + +void runThroughTest(StringRef InputFile) { + llvm::outs() << "Running Tests: " << InputFile << "\n"; + ErrorOr> BufferOrError = + MemoryBuffer::getFile(InputFile); + + if (auto EC = BufferOrError.getError()) { + return; + } + std::unique_ptr Buffer = std::move(BufferOrError.get()); + llvm::StringRef FileContent = Buffer->getBuffer(); + Expected Json = parse(FileContent); + + if (auto E = Json.takeError()) { + errs() << "Parsing error: " << toString(std::move(E)) << "\n"; + return; + } + // Get test + Array *Obj = (*Json).getAsObject()->getArray("tests"); + size_t Total = 0; + size_t Success = 0; + for (Value V : *Obj) { + Object *TestCase = V.getAsObject(); + StringRef TemplateStr = TestCase->getString("template").value(); + StringRef ExpectedStr = TestCase->getString("expected").value(); + StringRef Name = TestCase->getString("name").value(); + Value *Data = TestCase->get("data"); + Value *Partials = TestCase->get("partials"); + + if (!Data) + continue; + + Template T = Template(TemplateStr); + if (Partials) { + for (auto PartialPairs : *Partials->getAsObject()) { + StringRef Partial = PartialPairs.getSecond().getAsString().value(); + StringRef Str = llvm::StringRef(PartialPairs.getFirst()); + T.registerPartial(Str, Partial); + } + } + std::string ActualStr; + llvm::raw_string_ostream OS(ActualStr); + T.render(*Data, OS); + if (ExpectedStr == ActualStr) { + Success++; + } else { + llvm::outs() << "Test Failed: " << Name << "\n"; + } + Total++; + } + + llvm::outs() << "Result " << Success << "/" << Total << " succeeded\n"; +} +int main(int argc, char **argv) { + llvm::cl::ParseCommandLineOptions(argc, argv); + for (const auto &FileName : InputFiles) { + runThroughTest(FileName); + } + return 0; +} \ No newline at end of file diff --git a/llvm/unittests/Support/CMakeLists.txt b/llvm/unittests/Support/CMakeLists.txt index 6c4e7cb689b20..eb24bb188d969 100644 --- a/llvm/unittests/Support/CMakeLists.txt +++ b/llvm/unittests/Support/CMakeLists.txt @@ -61,7 +61,7 @@ add_llvm_unittest(SupportTests MemoryBufferRefTest.cpp MemoryBufferTest.cpp MemoryTest.cpp - MustacheTest.cpp + MustacheTest.cpp ModRefTest.cpp NativeFormatTests.cpp OptimizedStructLayoutTest.cpp