From 3d7441a6e7cc9978046f3a0d64c74e91b678f19f Mon Sep 17 00:00:00 2001 From: PeterChou1 Date: Tue, 30 Jul 2024 23:58:27 -0400 Subject: [PATCH 01/24] [clang-doc] add suport for clang-doc enum generation --- clang-tools-extra/clang-doc/HTMLGenerator.cpp | 2 +- .../clang-doc/Representation.cpp | 2 + clang-tools-extra/clang-doc/Representation.h | 65 +- clang-tools-extra/clang-doc/Serialize.cpp | 861 +----------------- clang-tools-extra/test/clang-doc/enum.cpp | 68 +- 5 files changed, 125 insertions(+), 873 deletions(-) diff --git a/clang-tools-extra/clang-doc/HTMLGenerator.cpp b/clang-tools-extra/clang-doc/HTMLGenerator.cpp index 18a0de826630c..a8404479569f9 100644 --- a/clang-tools-extra/clang-doc/HTMLGenerator.cpp +++ b/clang-tools-extra/clang-doc/HTMLGenerator.cpp @@ -498,7 +498,7 @@ writeFileDefinition(const Location &L, return std::make_unique( HTMLTag::TAG_P, "Defined at line " + std::to_string(L.LineNumber) + " of file " + L.Filename); - SmallString<128> FileURL(*RepositoryUrl); + SmallString<128> FileURL(RepositoryUrl.value_or("")); llvm::sys::path::append( FileURL, llvm::sys::path::Style::posix, // If we're on Windows, the file name will be in the wrong format, and diff --git a/clang-tools-extra/clang-doc/Representation.cpp b/clang-tools-extra/clang-doc/Representation.cpp index 4da93b24c131f..e056c4c9d156d 100644 --- a/clang-tools-extra/clang-doc/Representation.cpp +++ b/clang-tools-extra/clang-doc/Representation.cpp @@ -266,6 +266,8 @@ void EnumInfo::merge(EnumInfo &&Other) { Scoped = Other.Scoped; if (Members.empty()) Members = std::move(Other.Members); + if (Other.HasComments || HasComments) + HasComments = true; SymbolInfo::merge(std::move(Other)); } diff --git a/clang-tools-extra/clang-doc/Representation.h b/clang-tools-extra/clang-doc/Representation.h index bb0c534af7b74..cc5d5696a5404 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,10 @@ 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 allHTML file. + // JavaScript files that will be imported in all HTML file. 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..8874299e9af9e 100644 --- a/clang-tools-extra/clang-doc/Serialize.cpp +++ b/clang-tools-extra/clang-doc/Serialize.cpp @@ -1,845 +1,80 @@ -//===-- Serialize.cpp - ClangDoc Serializer ---------------------*- C++ -*-===// +//===-- Serializer.h - ClangDoc Serializer ----------------------*- 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 "Serialize.h" -#include "BitcodeWriter.h" -#include "clang/AST/Comment.h" -#include "clang/Index/USRGeneration.h" -#include "clang/Lex/Lexer.h" -#include "llvm/ADT/Hashing.h" -#include "llvm/ADT/StringExtras.h" -#include "llvm/Support/SHA1.h" - -using clang::comments::FullComment; - -namespace clang { -namespace doc { -namespace serialize { - -SymbolID hashUSR(llvm::StringRef USR) { - return llvm::SHA1::hash(arrayRefFromStringRef(USR)); -} - -template -static void -populateParentNamespaces(llvm::SmallVector &Namespaces, - const T *D, bool &IsAnonymousNamespace); - -static void populateMemberTypeInfo(MemberTypeInfo &I, const FieldDecl *D); - -// A function to extract the appropriate relative path for a given info's -// documentation. The path returned is a composite of the parent namespaces. -// -// Example: Given the below, the directory path for class C info will be -// /A/B -// -// namespace A { -// namespace B { // -// class C {}; +// This file implements the serializing functions fro the clang-doc tool. Given +// a particular declaration, it collects the appropriate information and returns +// a serialized bitcode string for the declaration. // -// } -// } -llvm::SmallString<128> -getInfoRelativePath(const llvm::SmallVectorImpl &Namespaces) { - llvm::SmallString<128> Path; - for (auto R = Namespaces.rbegin(), E = Namespaces.rend(); R != E; ++R) - llvm::sys::path::append(Path, R->Name); - return Path; -} - -llvm::SmallString<128> getInfoRelativePath(const Decl *D) { - llvm::SmallVector Namespaces; - // The third arg in populateParentNamespaces is a boolean passed by reference, - // its value is not relevant in here so it's not used anywhere besides the - // function call - bool B = true; - populateParentNamespaces(Namespaces, D, B); - return getInfoRelativePath(Namespaces); -} - -class ClangDocCommentVisitor - : public ConstCommentVisitor { -public: - ClangDocCommentVisitor(CommentInfo &CI) : CurrentCI(CI) {} - - void parseComment(const comments::Comment *C); - - void visitTextComment(const TextComment *C); - void visitInlineCommandComment(const InlineCommandComment *C); - void visitHTMLStartTagComment(const HTMLStartTagComment *C); - void visitHTMLEndTagComment(const HTMLEndTagComment *C); - void visitBlockCommandComment(const BlockCommandComment *C); - void visitParamCommandComment(const ParamCommandComment *C); - void visitTParamCommandComment(const TParamCommandComment *C); - void visitVerbatimBlockComment(const VerbatimBlockComment *C); - void visitVerbatimBlockLineComment(const VerbatimBlockLineComment *C); - void visitVerbatimLineComment(const VerbatimLineComment *C); - -private: - std::string getCommandName(unsigned CommandID) const; - bool isWhitespaceOnly(StringRef S) const; - - CommentInfo &CurrentCI; -}; - -void ClangDocCommentVisitor::parseComment(const comments::Comment *C) { - CurrentCI.Kind = C->getCommentKindName(); - ConstCommentVisitor::visit(C); - for (comments::Comment *Child : - llvm::make_range(C->child_begin(), C->child_end())) { - CurrentCI.Children.emplace_back(std::make_unique()); - ClangDocCommentVisitor Visitor(*CurrentCI.Children.back()); - Visitor.parseComment(Child); - } -} - -void ClangDocCommentVisitor::visitTextComment(const TextComment *C) { - if (!isWhitespaceOnly(C->getText())) - CurrentCI.Text = C->getText(); -} - -void ClangDocCommentVisitor::visitInlineCommandComment( - const InlineCommandComment *C) { - CurrentCI.Name = getCommandName(C->getCommandID()); - for (unsigned I = 0, E = C->getNumArgs(); I != E; ++I) - CurrentCI.Args.push_back(C->getArgText(I)); -} - -void ClangDocCommentVisitor::visitHTMLStartTagComment( - const HTMLStartTagComment *C) { - CurrentCI.Name = C->getTagName(); - CurrentCI.SelfClosing = C->isSelfClosing(); - for (unsigned I = 0, E = C->getNumAttrs(); I < E; ++I) { - const HTMLStartTagComment::Attribute &Attr = C->getAttr(I); - CurrentCI.AttrKeys.push_back(Attr.Name); - CurrentCI.AttrValues.push_back(Attr.Value); - } -} - -void ClangDocCommentVisitor::visitHTMLEndTagComment( - const HTMLEndTagComment *C) { - CurrentCI.Name = C->getTagName(); - CurrentCI.SelfClosing = true; -} - -void ClangDocCommentVisitor::visitBlockCommandComment( - const BlockCommandComment *C) { - CurrentCI.Name = getCommandName(C->getCommandID()); - for (unsigned I = 0, E = C->getNumArgs(); I < E; ++I) - CurrentCI.Args.push_back(C->getArgText(I)); -} - -void ClangDocCommentVisitor::visitParamCommandComment( - const ParamCommandComment *C) { - CurrentCI.Direction = - ParamCommandComment::getDirectionAsString(C->getDirection()); - CurrentCI.Explicit = C->isDirectionExplicit(); - if (C->hasParamName()) - CurrentCI.ParamName = C->getParamNameAsWritten(); -} - -void ClangDocCommentVisitor::visitTParamCommandComment( - const TParamCommandComment *C) { - if (C->hasParamName()) - CurrentCI.ParamName = C->getParamNameAsWritten(); -} - -void ClangDocCommentVisitor::visitVerbatimBlockComment( - const VerbatimBlockComment *C) { - CurrentCI.Name = getCommandName(C->getCommandID()); - CurrentCI.CloseName = C->getCloseName(); -} - -void ClangDocCommentVisitor::visitVerbatimBlockLineComment( - const VerbatimBlockLineComment *C) { - if (!isWhitespaceOnly(C->getText())) - CurrentCI.Text = C->getText(); -} - -void ClangDocCommentVisitor::visitVerbatimLineComment( - const VerbatimLineComment *C) { - if (!isWhitespaceOnly(C->getText())) - CurrentCI.Text = C->getText(); -} - -bool ClangDocCommentVisitor::isWhitespaceOnly(llvm::StringRef S) const { - return llvm::all_of(S, isspace); -} - -std::string ClangDocCommentVisitor::getCommandName(unsigned CommandID) const { - const CommandInfo *Info = CommandTraits::getBuiltinCommandInfo(CommandID); - if (Info) - return Info->Name; - // TODO: Add parsing for \file command. - return ""; -} - -// Serializing functions. - -std::string getSourceCode(const Decl *D, const SourceRange &R) { - return Lexer::getSourceText(CharSourceRange::getTokenRange(R), - D->getASTContext().getSourceManager(), - D->getASTContext().getLangOpts()) - .str(); -} - -template static std::string serialize(T &I) { - SmallString<2048> Buffer; - llvm::BitstreamWriter Stream(Buffer); - ClangDocBitcodeWriter Writer(Stream); - Writer.emitBlock(I); - return Buffer.str().str(); -} - -std::string serialize(std::unique_ptr &I) { - switch (I->IT) { - case InfoType::IT_namespace: - return serialize(*static_cast(I.get())); - case InfoType::IT_record: - return serialize(*static_cast(I.get())); - case InfoType::IT_enum: - return serialize(*static_cast(I.get())); - case InfoType::IT_function: - return serialize(*static_cast(I.get())); - default: - return ""; - } -} - -static void parseFullComment(const FullComment *C, CommentInfo &CI) { - ClangDocCommentVisitor Visitor(CI); - Visitor.parseComment(C); -} - -static SymbolID getUSRForDecl(const Decl *D) { - llvm::SmallString<128> USR; - if (index::generateUSRForDecl(D, USR)) - return SymbolID(); - return hashUSR(USR); -} - -static TagDecl *getTagDeclForType(const QualType &T) { - if (const TagDecl *D = T->getAsTagDecl()) - return D->getDefinition(); - return nullptr; -} - -static RecordDecl *getRecordDeclForType(const QualType &T) { - if (const RecordDecl *D = T->getAsRecordDecl()) - return D->getDefinition(); - return nullptr; -} - -TypeInfo getTypeInfoForType(const QualType &T, const PrintingPolicy &Policy) { - const TagDecl *TD = getTagDeclForType(T); - if (!TD) - return TypeInfo(Reference(SymbolID(), T.getAsString(Policy))); - - InfoType IT; - if (dyn_cast(TD)) { - IT = InfoType::IT_enum; - } else if (dyn_cast(TD)) { - IT = InfoType::IT_record; - } else { - IT = InfoType::IT_default; - } - return TypeInfo(Reference(getUSRForDecl(TD), TD->getNameAsString(), IT, - T.getAsString(Policy), getInfoRelativePath(TD))); -} - -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)) - return true; - return false; // otherwise, linkage is some form of internal linkage -} - -static bool shouldSerializeInfo(bool PublicOnly, bool IsInAnonymousNamespace, - const NamedDecl *D) { - bool IsAnonymousNamespace = false; - if (const auto *N = dyn_cast(D)) - IsAnonymousNamespace = N->isAnonymousNamespace(); - return !PublicOnly || - (!IsInAnonymousNamespace && !IsAnonymousNamespace && - isPublic(D->getAccessUnsafe(), D->getLinkageInternal())); -} - -// The InsertChild functions insert the given info into the given scope using -// the method appropriate for that type. Some types are moved into the -// appropriate vector, while other types have Reference objects generated to -// refer to them. -// -// See MakeAndInsertIntoParent(). -static void InsertChild(ScopeChildren &Scope, const NamespaceInfo &Info) { - Scope.Namespaces.emplace_back(Info.USR, Info.Name, InfoType::IT_namespace, - Info.Name, getInfoRelativePath(Info.Namespace)); -} - -static void InsertChild(ScopeChildren &Scope, const RecordInfo &Info) { - Scope.Records.emplace_back(Info.USR, Info.Name, InfoType::IT_record, - Info.Name, getInfoRelativePath(Info.Namespace)); -} - -static void InsertChild(ScopeChildren &Scope, EnumInfo Info) { - Scope.Enums.push_back(std::move(Info)); -} - -static void InsertChild(ScopeChildren &Scope, FunctionInfo Info) { - Scope.Functions.push_back(std::move(Info)); -} - -static void InsertChild(ScopeChildren &Scope, TypedefInfo Info) { - Scope.Typedefs.push_back(std::move(Info)); -} - -// Creates a parent of the correct type for the given child and inserts it into -// that parent. -// -// This is complicated by the fact that namespaces and records are inserted by -// reference (constructing a "Reference" object with that namespace/record's -// info), while everything else is inserted by moving it directly into the child -// vectors. -// -// For namespaces and records, explicitly specify a const& template parameter -// when invoking this function: -// MakeAndInsertIntoParent(...); -// Otherwise, specify an rvalue reference and move into the -// parameter. Since each variant is used once, it's not worth having a more -// elaborate system to automatically deduce this information. -template -std::unique_ptr MakeAndInsertIntoParent(ChildType Child) { - if (Child.Namespace.empty()) { - // Insert into unnamed parent namespace. - auto ParentNS = std::make_unique(); - InsertChild(ParentNS->Children, std::forward(Child)); - return ParentNS; - } - - switch (Child.Namespace[0].RefType) { - case InfoType::IT_namespace: { - auto ParentNS = std::make_unique(); - ParentNS->USR = Child.Namespace[0].USR; - InsertChild(ParentNS->Children, std::forward(Child)); - return ParentNS; - } - case InfoType::IT_record: { - auto ParentRec = std::make_unique(); - ParentRec->USR = Child.Namespace[0].USR; - InsertChild(ParentRec->Children, std::forward(Child)); - return ParentRec; - } - default: - llvm_unreachable("Invalid reference type for parent namespace"); - } -} - -// There are two uses for this function. -// 1) Getting the resulting mode of inheritance of a record. -// Example: class A {}; class B : private A {}; class C : public B {}; -// It's explicit that C is publicly inherited from C and B is privately -// inherited from A. It's not explicit but C is also privately inherited from -// A. This is the AS that this function calculates. FirstAS is the -// inheritance mode of `class C : B` and SecondAS is the inheritance mode of -// `class B : A`. -// 2) Getting the inheritance mode of an inherited attribute / method. -// Example : class A { public: int M; }; class B : private A {}; -// Class B is inherited from class A, which has a public attribute. This -// attribute is now part of the derived class B but it's not public. This -// will be private because the inheritance is private. This is the AS that -// this function calculates. FirstAS is the inheritance mode and SecondAS is -// the AS of the attribute / method. -static AccessSpecifier getFinalAccessSpecifier(AccessSpecifier FirstAS, - AccessSpecifier SecondAS) { - if (FirstAS == AccessSpecifier::AS_none || - SecondAS == AccessSpecifier::AS_none) - return AccessSpecifier::AS_none; - if (FirstAS == AccessSpecifier::AS_private || - SecondAS == AccessSpecifier::AS_private) - return AccessSpecifier::AS_private; - if (FirstAS == AccessSpecifier::AS_protected || - SecondAS == AccessSpecifier::AS_protected) - return AccessSpecifier::AS_protected; - return AccessSpecifier::AS_public; -} - -// The Access parameter is only provided when parsing the field of an inherited -// record, the access specification of the field depends on the inheritance mode -static void parseFields(RecordInfo &I, const RecordDecl *D, bool PublicOnly, - AccessSpecifier Access = AccessSpecifier::AS_public) { - for (const FieldDecl *F : D->fields()) { - 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), - F->getNameAsString(), - getFinalAccessSpecifier(Access, F->getAccessUnsafe())); - populateMemberTypeInfo(NewMember, F); - } -} - -static void parseEnumerators(EnumInfo &I, const EnumDecl *D) { - for (const EnumConstantDecl *E : D->enumerators()) { - std::string ValueExpr; - if (const Expr *InitExpr = E->getInitExpr()) - ValueExpr = getSourceCode(D, InitExpr->getSourceRange()); - SmallString<16> ValueStr; - E->getInitVal().toString(ValueStr); - I.Members.emplace_back(E->getNameAsString(), ValueStr.str(), ValueExpr); - 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(); - Member.Description.emplace_back(); - parseFullComment(Fc, Member.Description.back()); - } - } - } -} - -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()); - FieldInfo.DefaultValue = getSourceCode(D, P->getDefaultArgRange()); - } -} - -// TODO: Remove the serialization of Parents and VirtualParents, this -// information is also extracted in the other definition of parseBases. -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; - if (const auto *Ty = B.getType()->getAs()) { - const TemplateDecl *D = Ty->getTemplateName().getAsTemplateDecl(); - I.Parents.emplace_back(getUSRForDecl(D), B.getType().getAsString(), - InfoType::IT_record, B.getType().getAsString()); - } else if (const RecordDecl *P = getRecordDeclForType(B.getType())) - I.Parents.emplace_back(getUSRForDecl(P), P->getNameAsString(), - InfoType::IT_record, P->getQualifiedNameAsString(), - getInfoRelativePath(P)); - else - I.Parents.emplace_back(SymbolID(), B.getType().getAsString()); - } - for (const CXXBaseSpecifier &B : D->vbases()) { - if (const RecordDecl *P = getRecordDeclForType(B.getType())) - I.VirtualParents.emplace_back( - getUSRForDecl(P), P->getNameAsString(), InfoType::IT_record, - P->getQualifiedNameAsString(), getInfoRelativePath(P)); - else - I.VirtualParents.emplace_back(SymbolID(), B.getType().getAsString()); - } -} - -template -static void -populateParentNamespaces(llvm::SmallVector &Namespaces, - const T *D, bool &IsInAnonymousNamespace) { - const DeclContext *DC = D->getDeclContext(); - do { - if (const auto *N = dyn_cast(DC)) { - std::string Namespace; - if (N->isAnonymousNamespace()) { - Namespace = "@nonymous_namespace"; - IsInAnonymousNamespace = true; - } else - Namespace = N->getNameAsString(); - Namespaces.emplace_back(getUSRForDecl(N), Namespace, - InfoType::IT_namespace, - N->getQualifiedNameAsString()); - } else if (const auto *N = dyn_cast(DC)) - Namespaces.emplace_back(getUSRForDecl(N), N->getNameAsString(), - InfoType::IT_record, - N->getQualifiedNameAsString()); - else if (const auto *N = dyn_cast(DC)) - Namespaces.emplace_back(getUSRForDecl(N), N->getNameAsString(), - InfoType::IT_function, - N->getQualifiedNameAsString()); - else if (const auto *N = dyn_cast(DC)) - Namespaces.emplace_back(getUSRForDecl(N), N->getNameAsString(), - InfoType::IT_enum, N->getQualifiedNameAsString()); - } while ((DC = DC->getParent())); - // The global namespace should be added to the list of namespaces if the decl - // corresponds to a Record and if it doesn't have any namespace (because this - // means it's in the global namespace). Also if its outermost namespace is a - // record because that record matches the previous condition mentioned. - if ((Namespaces.empty() && isa(D)) || - (!Namespaces.empty() && Namespaces.back().RefType == InfoType::IT_record)) - Namespaces.emplace_back(SymbolID(), "GlobalNamespace", - InfoType::IT_namespace); -} - -void PopulateTemplateParameters(std::optional &TemplateInfo, - const clang::Decl *D) { - if (const TemplateParameterList *ParamList = - D->getDescribedTemplateParams()) { - if (!TemplateInfo) { - TemplateInfo.emplace(); - } - for (const NamedDecl *ND : *ParamList) { - TemplateInfo->Params.emplace_back( - getSourceCode(ND, ND->getSourceRange())); - } - } -} - -TemplateParamInfo TemplateArgumentToInfo(const clang::Decl *D, - const TemplateArgument &Arg) { - // The TemplateArgument's pretty printing handles all the normal cases - // well enough for our requirements. - std::string Str; - llvm::raw_string_ostream Stream(Str); - Arg.print(PrintingPolicy(D->getLangOpts()), Stream, false); - return TemplateParamInfo(Str); -} - -template -static void populateInfo(Info &I, const T *D, const FullComment *C, - bool &IsInAnonymousNamespace) { - I.USR = getUSRForDecl(D); - I.Name = D->getNameAsString(); - populateParentNamespaces(I.Namespace, D, IsInAnonymousNamespace); - if (C) { - I.Description.emplace_back(); - parseFullComment(C, I.Description.back()); - } -} - -template -static void populateSymbolInfo(SymbolInfo &I, const T *D, const FullComment *C, - int LineNumber, StringRef Filename, - bool IsFileInRootDir, - bool &IsInAnonymousNamespace) { - populateInfo(I, D, C, IsInAnonymousNamespace); - if (D->isThisDeclarationADefinition()) - I.DefLoc.emplace(LineNumber, Filename, IsFileInRootDir); - else - I.Loc.emplace_back(LineNumber, Filename, IsFileInRootDir); -} - -static void populateFunctionInfo(FunctionInfo &I, const FunctionDecl *D, - const FullComment *FC, int LineNumber, - StringRef Filename, bool IsFileInRootDir, - bool &IsInAnonymousNamespace) { - populateSymbolInfo(I, D, FC, LineNumber, Filename, IsFileInRootDir, - IsInAnonymousNamespace); - auto &LO = D->getLangOpts(); - I.ReturnType = getTypeInfoForType(D->getReturnType(), LO); - parseParameters(I, D); - - PopulateTemplateParameters(I.Template, D); - - // Handle function template specializations. - if (const FunctionTemplateSpecializationInfo *FTSI = - D->getTemplateSpecializationInfo()) { - if (!I.Template) - I.Template.emplace(); - I.Template->Specialization.emplace(); - auto &Specialization = *I.Template->Specialization; - - Specialization.SpecializationOf = getUSRForDecl(FTSI->getTemplate()); - - // Template parameters to the specialization. - if (FTSI->TemplateArguments) { - for (const TemplateArgument &Arg : FTSI->TemplateArguments->asArray()) { - Specialization.Params.push_back(TemplateArgumentToInfo(D, Arg)); - } - } - } -} +//===----------------------------------------------------------------------===// -static void populateMemberTypeInfo(MemberTypeInfo &I, const FieldDecl *D) { - assert(D && "Expect non-null FieldDecl in populateMemberTypeInfo"); +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_DOC_SERIALIZE_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_DOC_SERIALIZE_H - ASTContext& Context = D->getASTContext(); - // TODO investigate whether we can use ASTContext::getCommentForDecl instead - // of this logic. See also similar code in Mapper.cpp. - RawComment *Comment = Context.getRawCommentForDeclNoCache(D); - if (!Comment) - return; +#include "Representation.h" +#include "clang/AST/AST.h" +#include "clang/AST/CommentVisitor.h" +#include +#include - Comment->setAttached(); - if (comments::FullComment *fc = Comment->parse(Context, nullptr, D)) { - I.Description.emplace_back(); - parseFullComment(fc, I.Description.back()); - } -} +using namespace clang::comments; -static void -parseBases(RecordInfo &I, const CXXRecordDecl *D, bool IsFileInRootDir, - bool PublicOnly, bool IsParent, - AccessSpecifier ParentAccess = AccessSpecifier::AS_public) { - // Don't parse bases if this isn't a definition. - if (!D->isThisDeclarationADefinition()) - return; - for (const CXXBaseSpecifier &B : D->bases()) { - if (const RecordType *Ty = B.getType()->getAs()) { - if (const CXXRecordDecl *Base = - cast_or_null(Ty->getDecl()->getDefinition())) { - // Initialized without USR and name, this will be set in the following - // if-else stmt. - BaseRecordInfo BI( - {}, "", getInfoRelativePath(Base), B.isVirtual(), - getFinalAccessSpecifier(ParentAccess, B.getAccessSpecifier()), - IsParent); - if (const auto *Ty = B.getType()->getAs()) { - const TemplateDecl *D = Ty->getTemplateName().getAsTemplateDecl(); - BI.USR = getUSRForDecl(D); - BI.Name = B.getType().getAsString(); - } else { - BI.USR = getUSRForDecl(Base); - BI.Name = Base->getNameAsString(); - } - parseFields(BI, Base, PublicOnly, BI.Access); - for (const auto &Decl : Base->decls()) - if (const auto *MD = dyn_cast(Decl)) { - // Don't serialize private methods - if (MD->getAccessUnsafe() == AccessSpecifier::AS_private || - !MD->isUserProvided()) - continue; - FunctionInfo FI; - FI.IsMethod = true; - // The seventh arg in populateFunctionInfo is a boolean passed by - // 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, - IsInAnonymousNamespace); - FI.Access = - getFinalAccessSpecifier(BI.Access, MD->getAccessUnsafe()); - BI.Children.Functions.emplace_back(std::move(FI)); - } - I.Bases.emplace_back(std::move(BI)); - // Call this function recursively to get the inherited classes of - // this base; these new bases will also get stored in the original - // RecordInfo: I. - parseBases(I, Base, IsFileInRootDir, PublicOnly, false, - I.Bases.back().Access); - } - } - } -} +namespace clang { +namespace doc { +namespace serialize { +// The first element will contain the relevant information about the declaration +// passed as parameter. +// The second element will contain the relevant information about the +// declaration's parent, it can be a NamespaceInfo or RecordInfo. +// Both elements can be nullptrs if the declaration shouldn't be handled. +// When the declaration is handled, the first element will be a nullptr for +// EnumDecl, FunctionDecl and CXXMethodDecl; they are only returned wrapped in +// 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, - llvm::StringRef File, bool IsFileInRootDir, bool PublicOnly) { - auto I = std::make_unique(); - bool IsInAnonymousNamespace = false; - populateInfo(*I, D, FC, IsInAnonymousNamespace); - if (!shouldSerializeInfo(PublicOnly, IsInAnonymousNamespace, D)) - return {}; - - I->Name = D->isAnonymousNamespace() - ? llvm::SmallString<16>("@nonymous_namespace") - : I->Name; - I->Path = getInfoRelativePath(I->Namespace); - if (I->Namespace.empty() && I->USR == SymbolID()) - return {std::unique_ptr{std::move(I)}, nullptr}; - - // Namespaces are inserted into the parent by reference, so we need to return - // both the parent and the record itself. - return {std::move(I), MakeAndInsertIntoParent(*I)}; -} +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, - llvm::StringRef File, bool IsFileInRootDir, bool PublicOnly) { - auto I = std::make_unique(); - bool IsInAnonymousNamespace = false; - populateSymbolInfo(*I, D, FC, LineNumber, File, IsFileInRootDir, - IsInAnonymousNamespace); - if (!shouldSerializeInfo(PublicOnly, IsInAnonymousNamespace, D)) - return {}; - - I->TagType = D->getTagKind(); - parseFields(*I, D, PublicOnly); - if (const auto *C = dyn_cast(D)) { - 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); - } - I->Path = getInfoRelativePath(I->Namespace); - - PopulateTemplateParameters(I->Template, D); - - // Full and partial specializations. - if (auto *CTSD = dyn_cast(D)) { - if (!I->Template) - I->Template.emplace(); - I->Template->Specialization.emplace(); - auto &Specialization = *I->Template->Specialization; - - // 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); - - // Parameters to the specilization. For partial specializations, get the - // parameters "as written" from the ClassTemplatePartialSpecializationDecl - // because the non-explicit template parameters will have generated internal - // placeholder names rather than the names the user typed that match the - // template parameters. - if (const ClassTemplatePartialSpecializationDecl *CTPSD = - dyn_cast(D)) { - if (const ASTTemplateArgumentListInfo *AsWritten = - CTPSD->getTemplateArgsAsWritten()) { - for (unsigned i = 0; i < AsWritten->getNumTemplateArgs(); i++) { - Specialization.Params.emplace_back( - getSourceCode(D, (*AsWritten)[i].getSourceRange())); - } - } - } else { - for (const TemplateArgument &Arg : CTSD->getTemplateArgs().asArray()) { - Specialization.Params.push_back(TemplateArgumentToInfo(D, Arg)); - } - } - } - - // Records are inserted into the parent by reference, so we need to return - // both the parent and the record itself. - auto Parent = MakeAndInsertIntoParent(*I); - return {std::move(I), std::move(Parent)}; -} +emitInfo(const RecordDecl *D, const FullComment *FC, Location Loc, + bool PublicOnly); std::pair, std::unique_ptr> -emitInfo(const FunctionDecl *D, const FullComment *FC, int LineNumber, - llvm::StringRef File, bool IsFileInRootDir, bool PublicOnly) { - FunctionInfo Func; - bool IsInAnonymousNamespace = false; - populateFunctionInfo(Func, D, FC, LineNumber, File, IsFileInRootDir, - IsInAnonymousNamespace); - Func.Access = clang::AccessSpecifier::AS_none; - if (!shouldSerializeInfo(PublicOnly, IsInAnonymousNamespace, D)) - return {}; - - // Info is wrapped in its parent scope so is returned in the second position. - return {nullptr, MakeAndInsertIntoParent(std::move(Func))}; -} +emitInfo(const EnumDecl *D, const FullComment *FC, Location Loc, + bool PublicOnly); std::pair, std::unique_ptr> -emitInfo(const CXXMethodDecl *D, const FullComment *FC, int LineNumber, - llvm::StringRef File, bool IsFileInRootDir, bool PublicOnly) { - FunctionInfo Func; - bool IsInAnonymousNamespace = false; - populateFunctionInfo(Func, D, FC, LineNumber, File, IsFileInRootDir, - IsInAnonymousNamespace); - if (!shouldSerializeInfo(PublicOnly, IsInAnonymousNamespace, D)) - return {}; - - Func.IsMethod = true; - - const NamedDecl *Parent = nullptr; - if (const auto *SD = - dyn_cast(D->getParent())) - Parent = SD->getSpecializedTemplate(); - else - Parent = D->getParent(); - - SymbolID ParentUSR = getUSRForDecl(Parent); - Func.Parent = - Reference{ParentUSR, Parent->getNameAsString(), InfoType::IT_record, - Parent->getQualifiedNameAsString()}; - Func.Access = D->getAccess(); - - // Info is wrapped in its parent scope so is returned in the second position. - return {nullptr, MakeAndInsertIntoParent(std::move(Func))}; -} +emitInfo(const FunctionDecl *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) { - TypedefInfo Info; - - 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); - 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 - // a record with that name, so we don't want to emit a duplicate here. - return {}; - } - Info.IsUsing = false; +emitInfo(const CXXMethodDecl *D, const FullComment *FC, Location Loc, + bool PublicOnly); - // Info is wrapped in its parent scope so is returned in the second position. - return {nullptr, MakeAndInsertIntoParent(std::move(Info))}; -} - -// 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) { - TypedefInfo Info; - - 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.IsUsing = true; - - // Info is wrapped in its parent scope so is returned in the second position. - return {nullptr, MakeAndInsertIntoParent(std::move(Info))}; -} +emitInfo(const TypedefDecl *D, const FullComment *FC, Location Loc, + bool PublicOnly); std::pair, std::unique_ptr> -emitInfo(const EnumDecl *D, const FullComment *FC, int LineNumber, - llvm::StringRef File, bool IsFileInRootDir, bool PublicOnly) { - EnumInfo Enum; - bool IsInAnonymousNamespace = false; - populateSymbolInfo(Enum, D, FC, LineNumber, File, IsFileInRootDir, - IsInAnonymousNamespace); - if (!shouldSerializeInfo(PublicOnly, IsInAnonymousNamespace, D)) - return {}; +emitInfo(const TypeAliasDecl *D, const FullComment *FC, Location Loc, + bool PublicOnly); - Enum.Scoped = D->isScoped(); - if (D->isFixed()) { - auto Name = D->getIntegerType().getAsString(); - Enum.BaseType = TypeInfo(Name, Name); - } - parseEnumerators(Enum, D); +// Function to hash a given USR value for storage. +// As USRs (Unified Symbol Resolution) could be large, especially for functions +// with long type arguments, we use 160-bits SHA1(USR) values to +// guarantee the uniqueness of symbols while using a relatively small amount of +// memory (vs storing USRs directly). +SymbolID hashUSR(llvm::StringRef USR); - // Info is wrapped in its parent scope so is returned in the second position. - return {nullptr, MakeAndInsertIntoParent(std::move(Enum))}; -} +std::string serialize(std::unique_ptr &I); } // namespace serialize } // namespace doc } // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_DOC_SERIALIZE_H diff --git a/clang-tools-extra/test/clang-doc/enum.cpp b/clang-tools-extra/test/clang-doc/enum.cpp index ef768e33b4566..b05d8e2029070 100644 --- a/clang-tools-extra/test/clang-doc/enum.cpp +++ b/clang-tools-extra/test/clang-doc/enum.cpp @@ -14,16 +14,15 @@ // RUN: FileCheck %s < %t/Vehicles/index.md --check-prefix=MD-VEHICLES-LINE // RUN: FileCheck %s < %t/Vehicles/index.md --check-prefix=MD-VEHICLES - /** * @brief For specifying RGB colors */ enum Color { -// MD-INDEX-LINE: *Defined at {{.*}}clang-tools-extra{{[\/]}}test{{[\/]}}clang-doc{{[\/]}}enum.cpp#[[@LINE-1]]* -// HTML-INDEX-LINE:

Defined at line [[@LINE-2]] of file {{.*}}clang-tools-extra{{[\/]}}test{{[\/]}}clang-doc{{[\/]}}enum.cpp

- Red, ///< Comment 1 + // MD-INDEX-LINE: *Defined at {{.*}}clang-tools-extra{{[\/]}}test{{[\/]}}clang-doc{{[\/]}}enum.cpp#[[@LINE-1]]* + // HTML-INDEX-LINE:

Defined at line [[@LINE-2]] of file {{.*}}clang-tools-extra{{[\/]}}test{{[\/]}}clang-doc{{[\/]}}enum.cpp

+ Red, ///< Comment 1 Green, ///< Comment 2 - Blue ///< Comment 3 + Blue ///< Comment 3 }; // MD-INDEX: ## Enums @@ -49,8 +48,8 @@ enum Color { * @brief Shape Types */ enum class Shapes { -// MD-INDEX-LINE: *Defined at {{.*}}clang-tools-extra{{[\/]}}test{{[\/]}}clang-doc{{[\/]}}enum.cpp#[[@LINE-1]]* -// HTML-INDEX-LINE:

Defined at line [[@LINE-2]] of file {{.*}}clang-tools-extra{{[\/]}}test{{[\/]}}clang-doc{{[\/]}}enum.cpp

+ // MD-INDEX-LINE: *Defined at {{.*}}clang-tools-extra{{[\/]}}test{{[\/]}}clang-doc{{[\/]}}enum.cpp#[[@LINE-1]]* + // HTML-INDEX-LINE:

Defined at line [[@LINE-2]] of file {{.*}}clang-tools-extra{{[\/]}}test{{[\/]}}clang-doc{{[\/]}}enum.cpp

/// Comment 1 Circle, @@ -77,22 +76,20 @@ enum class Shapes { // HTML-INDEX: 2 // HTML-INDEX:

Comment 3

- - class Animals { -// MD-ANIMAL-LINE: *Defined at {{.*}}clang-tools-extra{{[\/]}}test{{[\/]}}clang-doc{{[\/]}}enum.cpp#[[@LINE-1]]* -// HTML-ANIMAL-LINE:

Defined at line [[@LINE-2]] of file {{.*}}clang-tools-extra{{[\/]}}test{{[\/]}}clang-doc{{[\/]}}enum.cpp

+ // MD-ANIMAL-LINE: *Defined at {{.*}}clang-tools-extra{{[\/]}}test{{[\/]}}clang-doc{{[\/]}}enum.cpp#[[@LINE-1]]* + // HTML-ANIMAL-LINE:

Defined at line [[@LINE-2]] of file {{.*}}clang-tools-extra{{[\/]}}test{{[\/]}}clang-doc{{[\/]}}enum.cpp

public: - /** - * @brief specify what animal the class is - */ - enum AnimalType { -// MD-ANIMAL-LINE: *Defined at {{.*}}clang-tools-extra{{[\/]}}test{{[\/]}}clang-doc{{[\/]}}enum.cpp#[[@LINE-1]]* -// HTML-ANIMAL-LINE:

Defined at line [[@LINE-2]] of file {{.*}}clang-tools-extra{{[\/]}}test{{[\/]}}clang-doc{{[\/]}}enum.cpp

- Dog, ///< Man's best friend - Cat, ///< Man's other best friend - Iguana ///< A lizard - }; + /** + * @brief specify what animal the class is + */ + enum AnimalType { + // MD-ANIMAL-LINE: *Defined at {{.*}}clang-tools-extra{{[\/]}}test{{[\/]}}clang-doc{{[\/]}}enum.cpp#[[@LINE-1]]* + // HTML-ANIMAL-LINE:

Defined at line [[@LINE-2]] of file {{.*}}clang-tools-extra{{[\/]}}test{{[\/]}}clang-doc{{[\/]}}enum.cpp

+ Dog, ///< Man's best friend + Cat, ///< Man's other best friend + Iguana ///< A lizard + }; }; // HTML-ANIMAL:

class Animals

@@ -108,7 +105,6 @@ class Animals { // HTML-ANIMAL: 2 // HTML-ANIMAL:

A lizard

- // MD-ANIMAL: # class Animals // MD-ANIMAL: ## Enums // MD-ANIMAL: | enum AnimalType | @@ -118,21 +114,20 @@ class Animals { // MD-ANIMAL: | Iguana | // MD-ANIMAL: **brief** specify what animal the class is - namespace Vehicles { - /** - * @brief specify type of car - */ - enum Car { -// MD-VEHICLES-LINE: *Defined at {{.*}}clang-tools-extra{{[\/]}}test{{[\/]}}clang-doc{{[\/]}}enum.cpp#[[@LINE-1]]* -// HTML-VEHICLES-LINE:

Defined at line [[@LINE-2]] of file {{.*}}clang-tools-extra{{[\/]}}test{{[\/]}}clang-doc{{[\/]}}enum.cpp

- - Sedan, ///< Comment 1 - SUV, ///< Comment 2 - Pickup, ///< Comment 3 - Hatchback ///< Comment 4 - }; -} +/** + * @brief specify type of car + */ +enum Car { + // MD-VEHICLES-LINE: *Defined at {{.*}}clang-tools-extra{{[\/]}}test{{[\/]}}clang-doc{{[\/]}}enum.cpp#[[@LINE-1]]* + // HTML-VEHICLES-LINE:

Defined at line [[@LINE-2]] of file {{.*}}clang-tools-extra{{[\/]}}test{{[\/]}}clang-doc{{[\/]}}enum.cpp

+ + Sedan, ///< Comment 1 + SUV, ///< Comment 2 + Pickup, ///< Comment 3 + Hatchback ///< Comment 4 +}; +} // namespace Vehicles // MD-VEHICLES: # namespace Vehicles // MD-VEHICLES: ## Enums @@ -159,7 +154,6 @@ namespace Vehicles { // HTML-VEHICLES: 3 // HTML-VEHICLES:

Comment 4

- enum ColorUserSpecified { RedUserSpecified = 'A', GreenUserSpecified = 2, From 59df7dd3b46c2895b17f154bea0128f25bbfe1a6 Mon Sep 17 00:00:00 2001 From: PeterChou1 Date: Wed, 31 Jul 2024 00:02:13 -0400 Subject: [PATCH 02/24] [clang-doc] remove useless code --- clang-tools-extra/clang-doc/Representation.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/clang-tools-extra/clang-doc/Representation.cpp b/clang-tools-extra/clang-doc/Representation.cpp index e056c4c9d156d..4da93b24c131f 100644 --- a/clang-tools-extra/clang-doc/Representation.cpp +++ b/clang-tools-extra/clang-doc/Representation.cpp @@ -266,8 +266,6 @@ void EnumInfo::merge(EnumInfo &&Other) { Scoped = Other.Scoped; if (Members.empty()) Members = std::move(Other.Members); - if (Other.HasComments || HasComments) - HasComments = true; SymbolInfo::merge(std::move(Other)); } From ad61ab4d5698e5dacc9df417ae40e734f7339a1a Mon Sep 17 00:00:00 2001 From: PeterChou1 Date: Mon, 12 Aug 2024 17:12:48 -0400 Subject: [PATCH 03/24] [clang-doc] address pr comments --- .../clang-doc/Representation.cpp | 2 + clang-tools-extra/clang-doc/Serialize.cpp | 447 +++++++++++++++--- clang-tools-extra/clang-doc/Serialize.h | 28 +- 3 files changed, 398 insertions(+), 79 deletions(-) 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/Serialize.cpp b/clang-tools-extra/clang-doc/Serialize.cpp index 8874299e9af9e..cb42709f467a3 100644 --- a/clang-tools-extra/clang-doc/Serialize.cpp +++ b/clang-tools-extra/clang-doc/Serialize.cpp @@ -1,4 +1,4 @@ -//===-- Serializer.h - ClangDoc Serializer ----------------------*- C++ -*-===// +///===-- Representation.cpp - ClangDoc Representation -----------*- C++ -*-===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. @@ -6,75 +6,392 @@ // //===----------------------------------------------------------------------===// // -// This file implements the serializing functions fro the clang-doc tool. Given -// a particular declaration, it collects the appropriate information and returns -// a serialized bitcode string for the declaration. +// This file defines the merging of different types of infos. The data in the +// calling Info is preserved during a merge unless that field is empty or +// default. In that case, the data from the parameter Info is used to replace +// the empty or default data. +// +// For most fields, the first decl seen provides the data. Exceptions to this +// include the location and description fields, which are collections of data on +// all decls related to a given definition. All other fields are ignored in new +// decls unless the first seen decl didn't, for whatever reason, incorporate +// data on that field (e.g. a forward declared class wouldn't have information +// on members on the forward declaration, but would have the class name). // //===----------------------------------------------------------------------===// - -#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_DOC_SERIALIZE_H -#define LLVM_CLANG_TOOLS_EXTRA_CLANG_DOC_SERIALIZE_H - #include "Representation.h" -#include "clang/AST/AST.h" -#include "clang/AST/CommentVisitor.h" -#include -#include - -using namespace clang::comments; +#include "llvm/Support/Error.h" +#include "llvm/Support/Path.h" namespace clang { namespace doc { -namespace serialize { - -// The first element will contain the relevant information about the declaration -// passed as parameter. -// The second element will contain the relevant information about the -// declaration's parent, it can be a NamespaceInfo or RecordInfo. -// Both elements can be nullptrs if the declaration shouldn't be handled. -// When the declaration is handled, the first element will be a nullptr for -// EnumDecl, FunctionDecl and CXXMethodDecl; they are only returned wrapped in -// its parent scope. For NamespaceDecl and RecordDecl both elements are not -// nullptr. -std::pair, std::unique_ptr> -emitInfo(const NamespaceDecl *D, const FullComment *FC, Location Loc, - bool PublicOnly); - -std::pair, std::unique_ptr> -emitInfo(const RecordDecl *D, const FullComment *FC, Location Loc, - bool PublicOnly); - -std::pair, std::unique_ptr> -emitInfo(const EnumDecl *D, const FullComment *FC, Location Loc, - bool PublicOnly); - -std::pair, std::unique_ptr> -emitInfo(const FunctionDecl *D, const FullComment *FC, Location Loc, - bool PublicOnly); - -std::pair, std::unique_ptr> -emitInfo(const CXXMethodDecl *D, const FullComment *FC, Location Loc, - bool PublicOnly); - -std::pair, std::unique_ptr> -emitInfo(const TypedefDecl *D, const FullComment *FC, Location Loc, - bool PublicOnly); - -std::pair, std::unique_ptr> -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 -// with long type arguments, we use 160-bits SHA1(USR) values to -// guarantee the uniqueness of symbols while using a relatively small amount of -// memory (vs storing USRs directly). -SymbolID hashUSR(llvm::StringRef USR); - -std::string serialize(std::unique_ptr &I); - -} // namespace serialize + +namespace { + +const SymbolID EmptySID = SymbolID(); + +template +llvm::Expected> +reduce(std::vector> &Values) { + if (Values.empty() || !Values[0]) + return llvm::createStringError(llvm::inconvertibleErrorCode(), + "no value to reduce"); + std::unique_ptr Merged = std::make_unique(Values[0]->USR); + T *Tmp = static_cast(Merged.get()); + for (auto &I : Values) + Tmp->merge(std::move(*static_cast(I.get()))); + return std::move(Merged); +} + +// Return the index of the matching child in the vector, or -1 if merge is not +// necessary. +template +int getChildIndexIfExists(std::vector &Children, T &ChildToMerge) { + for (unsigned long I = 0; I < Children.size(); I++) { + if (ChildToMerge.USR == Children[I].USR) + return I; + } + return -1; +} + +template +void reduceChildren(std::vector &Children, + std::vector &&ChildrenToMerge) { + for (auto &ChildToMerge : ChildrenToMerge) { + int MergeIdx = getChildIndexIfExists(Children, ChildToMerge); + if (MergeIdx == -1) { + Children.push_back(std::move(ChildToMerge)); + continue; + } + Children[MergeIdx].merge(std::move(ChildToMerge)); + } +} + +} // namespace + +// Dispatch function. +llvm::Expected> +mergeInfos(std::vector> &Values) { + if (Values.empty() || !Values[0]) + return llvm::createStringError(llvm::inconvertibleErrorCode(), + "no info values to merge"); + + switch (Values[0]->IT) { + case InfoType::IT_namespace: + return reduce(Values); + case InfoType::IT_record: + return reduce(Values); + case InfoType::IT_enum: + return reduce(Values); + case InfoType::IT_function: + return reduce(Values); + case InfoType::IT_typedef: + return reduce(Values); + default: + return llvm::createStringError(llvm::inconvertibleErrorCode(), + "unexpected info type"); + } +} + +bool CommentInfo::operator==(const CommentInfo &Other) const { + auto FirstCI = std::tie(Kind, Text, Name, Direction, ParamName, CloseName, + SelfClosing, Explicit, AttrKeys, AttrValues, Args); + auto SecondCI = + std::tie(Other.Kind, Other.Text, Other.Name, Other.Direction, + Other.ParamName, Other.CloseName, Other.SelfClosing, + Other.Explicit, Other.AttrKeys, Other.AttrValues, Other.Args); + + if (FirstCI != SecondCI || Children.size() != Other.Children.size()) + return false; + + return std::equal(Children.begin(), Children.end(), Other.Children.begin(), + llvm::deref>{}); +} + +bool CommentInfo::operator<(const CommentInfo &Other) const { + auto FirstCI = std::tie(Kind, Text, Name, Direction, ParamName, CloseName, + SelfClosing, Explicit, AttrKeys, AttrValues, Args); + auto SecondCI = + std::tie(Other.Kind, Other.Text, Other.Name, Other.Direction, + Other.ParamName, Other.CloseName, Other.SelfClosing, + Other.Explicit, Other.AttrKeys, Other.AttrValues, Other.Args); + + if (FirstCI < SecondCI) + return true; + + if (FirstCI == SecondCI) { + return std::lexicographical_compare( + Children.begin(), Children.end(), Other.Children.begin(), + Other.Children.end(), llvm::deref>()); + } + + return false; +} + +static llvm::SmallString<64> +calculateRelativeFilePath(const InfoType &Type, const StringRef &Path, + const StringRef &Name, const StringRef &CurrentPath) { + llvm::SmallString<64> FilePath; + + if (CurrentPath != Path) { + // iterate back to the top + for (llvm::sys::path::const_iterator I = + llvm::sys::path::begin(CurrentPath); + I != llvm::sys::path::end(CurrentPath); ++I) + llvm::sys::path::append(FilePath, ".."); + llvm::sys::path::append(FilePath, Path); + } + + // Namespace references have a Path to the parent namespace, but + // the file is actually in the subdirectory for the namespace. + if (Type == doc::InfoType::IT_namespace) + llvm::sys::path::append(FilePath, Name); + + return llvm::sys::path::relative_path(FilePath); +} + +llvm::SmallString<64> +Reference::getRelativeFilePath(const StringRef &CurrentPath) const { + return calculateRelativeFilePath(RefType, Path, Name, CurrentPath); +} + +llvm::SmallString<16> Reference::getFileBaseName() const { + if (RefType == InfoType::IT_namespace) + return llvm::SmallString<16>("index"); + + return Name; +} + +llvm::SmallString<64> +Info::getRelativeFilePath(const StringRef &CurrentPath) const { + return calculateRelativeFilePath(IT, Path, extractName(), CurrentPath); +} + +llvm::SmallString<16> Info::getFileBaseName() const { + if (IT == InfoType::IT_namespace) + return llvm::SmallString<16>("index"); + + return extractName(); +} + +bool Reference::mergeable(const Reference &Other) { + return RefType == Other.RefType && USR == Other.USR; +} + +void Reference::merge(Reference &&Other) { + assert(mergeable(Other)); + if (Name.empty()) + Name = Other.Name; + if (Path.empty()) + Path = Other.Path; +} + +void Info::mergeBase(Info &&Other) { + assert(mergeable(Other)); + if (USR == EmptySID) + USR = Other.USR; + if (Name == "") + Name = Other.Name; + if (Path == "") + Path = Other.Path; + if (Namespace.empty()) + Namespace = std::move(Other.Namespace); + // Unconditionally extend the description, since each decl may have a comment. + std::move(Other.Description.begin(), Other.Description.end(), + std::back_inserter(Description)); + llvm::sort(Description); + auto Last = std::unique(Description.begin(), Description.end()); + Description.erase(Last, Description.end()); +} + +bool Info::mergeable(const Info &Other) { + return IT == Other.IT && USR == Other.USR; +} + +void SymbolInfo::merge(SymbolInfo &&Other) { + assert(mergeable(Other)); + if (!DefLoc) + DefLoc = std::move(Other.DefLoc); + // Unconditionally extend the list of locations, since we want all of them. + std::move(Other.Loc.begin(), Other.Loc.end(), std::back_inserter(Loc)); + llvm::sort(Loc); + auto Last = std::unique(Loc.begin(), Loc.end()); + Loc.erase(Last, Loc.end()); + mergeBase(std::move(Other)); +} + +NamespaceInfo::NamespaceInfo(SymbolID USR, StringRef Name, StringRef Path) + : Info(InfoType::IT_namespace, USR, Name, Path) {} + +void NamespaceInfo::merge(NamespaceInfo &&Other) { + assert(mergeable(Other)); + // Reduce children if necessary. + reduceChildren(Children.Namespaces, std::move(Other.Children.Namespaces)); + reduceChildren(Children.Records, std::move(Other.Children.Records)); + reduceChildren(Children.Functions, std::move(Other.Children.Functions)); + reduceChildren(Children.Enums, std::move(Other.Children.Enums)); + reduceChildren(Children.Typedefs, std::move(Other.Children.Typedefs)); + mergeBase(std::move(Other)); +} + +RecordInfo::RecordInfo(SymbolID USR, StringRef Name, StringRef Path) + : SymbolInfo(InfoType::IT_record, USR, Name, 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; + if (Members.empty()) + Members = std::move(Other.Members); + if (Bases.empty()) + Bases = std::move(Other.Bases); + if (Parents.empty()) + Parents = std::move(Other.Parents); + if (VirtualParents.empty()) + VirtualParents = std::move(Other.VirtualParents); + // Reduce children if necessary. + reduceChildren(Children.Records, std::move(Other.Children.Records)); + reduceChildren(Children.Functions, std::move(Other.Children.Functions)); + reduceChildren(Children.Enums, std::move(Other.Children.Enums)); + reduceChildren(Children.Typedefs, std::move(Other.Children.Typedefs)); + SymbolInfo::merge(std::move(Other)); + if (!Template) + Template = Other.Template; +} + +void EnumInfo::merge(EnumInfo &&Other) { + assert(mergeable(Other)); + if (!Scoped) + Scoped = Other.Scoped; + if (Members.empty()) + Members = std::move(Other.Members); + SymbolInfo::merge(std::move(Other)); +} + +void FunctionInfo::merge(FunctionInfo &&Other) { + assert(mergeable(Other)); + if (!IsMethod) + IsMethod = Other.IsMethod; + if (!Access) + Access = Other.Access; + if (ReturnType.Type.USR == EmptySID && ReturnType.Type.Name == "") + ReturnType = std::move(Other.ReturnType); + if (Parent.USR == EmptySID && Parent.Name == "") + Parent = std::move(Other.Parent); + if (Params.empty()) + Params = std::move(Other.Params); + SymbolInfo::merge(std::move(Other)); + if (!Template) + Template = Other.Template; +} + +void TypedefInfo::merge(TypedefInfo &&Other) { + assert(mergeable(Other)); + if (!IsUsing) + IsUsing = Other.IsUsing; + if (Underlying.Type.Name == "") + Underlying = Other.Underlying; + SymbolInfo::merge(std::move(Other)); +} + +BaseRecordInfo::BaseRecordInfo() : RecordInfo() {} + +BaseRecordInfo::BaseRecordInfo(SymbolID USR, StringRef Name, StringRef Path, + bool IsVirtual, AccessSpecifier Access, + bool IsParent) + : RecordInfo(USR, Name, Path), IsVirtual(IsVirtual), Access(Access), + IsParent(IsParent) {} + +llvm::SmallString<16> Info::extractName() const { + if (!Name.empty()) + return Name; + + switch (IT) { + case InfoType::IT_namespace: + // Cover the case where the project contains a base namespace called + // 'GlobalNamespace' (i.e. a namespace at the same level as the global + // namespace, which would conflict with the hard-coded global namespace name + // below.) + if (Name == "GlobalNamespace" && Namespace.empty()) + return llvm::SmallString<16>("@GlobalNamespace"); + // The case of anonymous namespaces is taken care of in serialization, + // so here we can safely assume an unnamed namespace is the global + // one. + return llvm::SmallString<16>("GlobalNamespace"); + case InfoType::IT_record: + return llvm::SmallString<16>("@nonymous_record_" + + toHex(llvm::toStringRef(USR))); + case InfoType::IT_enum: + return llvm::SmallString<16>("@nonymous_enum_" + + toHex(llvm::toStringRef(USR))); + case InfoType::IT_typedef: + return llvm::SmallString<16>("@nonymous_typedef_" + + toHex(llvm::toStringRef(USR))); + case InfoType::IT_function: + return llvm::SmallString<16>("@nonymous_function_" + + toHex(llvm::toStringRef(USR))); + case InfoType::IT_default: + return llvm::SmallString<16>("@nonymous_" + toHex(llvm::toStringRef(USR))); + } + llvm_unreachable("Invalid InfoType."); + return llvm::SmallString<16>(""); +} + +// Order is based on the Name attribute: case insensitive order +bool Index::operator<(const Index &Other) const { + // Loop through each character of both strings + for (unsigned I = 0; I < Name.size() && I < Other.Name.size(); ++I) { + // Compare them after converting both to lower case + int D = tolower(Name[I]) - tolower(Other.Name[I]); + if (D == 0) + continue; + return D < 0; + } + // If both strings have the size it means they would be equal if changed to + // lower case. In here, lower case will be smaller than upper case + // Example: string < stRing = true + // This is the opposite of how operator < handles strings + if (Name.size() == Other.Name.size()) + return Name > Other.Name; + // If they are not the same size; the shorter string is smaller + return Name.size() < Other.Name.size(); +} + +void Index::sort() { + llvm::sort(Children); + for (auto &C : Children) + C.sort(); +} + +ClangDocContext::ClangDocContext(tooling::ExecutionContext *ECtx, + StringRef ProjectName, bool PublicOnly, + StringRef OutDirectory, StringRef SourceRoot, + StringRef RepositoryUrl, + std::vector UserStylesheets) + : ECtx(ECtx), ProjectName(ProjectName), PublicOnly(PublicOnly), + OutDirectory(OutDirectory), UserStylesheets(UserStylesheets) { + llvm::SmallString<128> SourceRootDir(SourceRoot); + if (SourceRoot.empty()) + // If no SourceRoot was provided the current path is used as the default + llvm::sys::fs::current_path(SourceRootDir); + this->SourceRoot = std::string(SourceRootDir); + if (!RepositoryUrl.empty()) { + this->RepositoryUrl = std::string(RepositoryUrl); + if (!RepositoryUrl.empty() && !RepositoryUrl.starts_with("http://") && + !RepositoryUrl.starts_with("https://")) + this->RepositoryUrl->insert(0, "https://"); + } +} + +void ScopeChildren::sort() { + llvm::sort(Namespaces.begin(), Namespaces.end()); + llvm::sort(Records.begin(), Records.end()); + llvm::sort(Functions.begin(), Functions.end()); + llvm::sort(Enums.begin(), Enums.end()); + llvm::sort(Typedefs.begin(), Typedefs.end()); +} } // namespace doc } // namespace clang - -#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_DOC_SERIALIZE_H 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 From 8e2d7fafb27f210abfbc748a8429610c08d612a7 Mon Sep 17 00:00:00 2001 From: PeterChou1 Date: Mon, 12 Aug 2024 19:19:39 -0400 Subject: [PATCH 04/24] [clang-doc] fix unittest --- clang-tools-extra/clang-doc/HTMLGenerator.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/clang-tools-extra/clang-doc/HTMLGenerator.cpp b/clang-tools-extra/clang-doc/HTMLGenerator.cpp index a8404479569f9..35a452e7fbe67 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( @@ -726,9 +726,9 @@ genHTML(const EnumInfo &I, const ClangDocContext &CDCtx) { std::vector> Out; std::string EnumType = I.Scoped ? "enum class " : "enum "; // Determine if enum members have comments attached - bool HasComments = std::any_of( - I.Members.begin(), I.Members.end(), - [](const EnumValueInfo &M) { return !M.Description.empty(); }); + bool HasComments = + std::any_of(I.Members.begin(), I.Members.end(), + [](const EnumValueInfo &M) { return !M.Description.empty(); }); std::unique_ptr Table = std::make_unique(HTMLTag::TAG_TABLE); std::unique_ptr THead = From 1e62f7d13b50591a7ee3e9bb6fd0761b42973e33 Mon Sep 17 00:00:00 2001 From: PeterChou1 Date: Mon, 12 Aug 2024 19:35:15 -0400 Subject: [PATCH 05/24] [clang-doc] address pr comments --- clang-tools-extra/clang-doc/HTMLGenerator.cpp | 7 +- clang-tools-extra/clang-doc/Serialize.cpp | 1346 ++++++++++++----- 2 files changed, 1012 insertions(+), 341 deletions(-) diff --git a/clang-tools-extra/clang-doc/HTMLGenerator.cpp b/clang-tools-extra/clang-doc/HTMLGenerator.cpp index 35a452e7fbe67..24aa2383abdad 100644 --- a/clang-tools-extra/clang-doc/HTMLGenerator.cpp +++ b/clang-tools-extra/clang-doc/HTMLGenerator.cpp @@ -726,9 +726,9 @@ genHTML(const EnumInfo &I, const ClangDocContext &CDCtx) { std::vector> Out; std::string EnumType = I.Scoped ? "enum class " : "enum "; // Determine if enum members have comments attached - bool HasComments = - std::any_of(I.Members.begin(), I.Members.end(), - [](const EnumValueInfo &M) { return !M.Description.empty(); }); + bool HasComments = std::any_of( + I.Members.begin(), I.Members.end(), + [](const EnumValueInfo &M) { return !M.Description.empty(); }); std::unique_ptr Table = std::make_unique(HTMLTag::TAG_TABLE); std::unique_ptr THead = @@ -743,7 +743,6 @@ genHTML(const EnumInfo &I, const ClangDocContext &CDCtx) { TRow->Children.emplace_back(std::move(TD)); THead->Children.emplace_back(std::move(TRow)); Table->Children.emplace_back(std::move(THead)); - if (std::unique_ptr Node = genEnumMembersBlock(I.Members)) Table->Children.emplace_back(std::move(Node)); diff --git a/clang-tools-extra/clang-doc/Serialize.cpp b/clang-tools-extra/clang-doc/Serialize.cpp index cb42709f467a3..d4d1436646575 100644 --- a/clang-tools-extra/clang-doc/Serialize.cpp +++ b/clang-tools-extra/clang-doc/Serialize.cpp @@ -1,397 +1,1069 @@ -///===-- Representation.cpp - ClangDoc Representation -----------*- C++ -*-===// +//===-- Serialize.cpp - ClangDoc Serializer ---------------------*- 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 // //===----------------------------------------------------------------------===// -// -// This file defines the merging of different types of infos. The data in the -// calling Info is preserved during a merge unless that field is empty or -// default. In that case, the data from the parameter Info is used to replace -// the empty or default data. -// -// For most fields, the first decl seen provides the data. Exceptions to this -// include the location and description fields, which are collections of data on -// all decls related to a given definition. All other fields are ignored in new -// decls unless the first seen decl didn't, for whatever reason, incorporate -// data on that field (e.g. a forward declared class wouldn't have information -// on members on the forward declaration, but would have the class name). -// -//===----------------------------------------------------------------------===// -#include "Representation.h" -#include "llvm/Support/Error.h" -#include "llvm/Support/Path.h" + +#include "Serialize.h" +#include "BitcodeWriter.h" +#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" + +using clang::comments::FullComment; namespace clang { namespace doc { +namespace serialize { -namespace { - -const SymbolID EmptySID = SymbolID(); +SymbolID hashUSR(llvm::StringRef USR) { + return llvm::SHA1::hash(arrayRefFromStringRef(USR)); +} template -llvm::Expected> -reduce(std::vector> &Values) { - if (Values.empty() || !Values[0]) - return llvm::createStringError(llvm::inconvertibleErrorCode(), - "no value to reduce"); - std::unique_ptr Merged = std::make_unique(Values[0]->USR); - T *Tmp = static_cast(Merged.get()); - for (auto &I : Values) - Tmp->merge(std::move(*static_cast(I.get()))); - return std::move(Merged); -} - -// Return the index of the matching child in the vector, or -1 if merge is not -// necessary. -template -int getChildIndexIfExists(std::vector &Children, T &ChildToMerge) { - for (unsigned long I = 0; I < Children.size(); I++) { - if (ChildToMerge.USR == Children[I].USR) - return I; +static void +populateParentNamespaces(llvm::SmallVector &Namespaces, + const T *D, bool &IsAnonymousNamespace); + +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(); + } } - return -1; + + Stream << "> "; } -template -void reduceChildren(std::vector &Children, - std::vector &&ChildrenToMerge) { - for (auto &ChildToMerge : ChildrenToMerge) { - int MergeIdx = getChildIndexIfExists(Children, ChildToMerge); - if (MergeIdx == -1) { - Children.push_back(std::move(ChildToMerge)); - continue; +// 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 << ", "; } - Children[MergeIdx].merge(std::move(ChildToMerge)); + 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. +// +// Example: Given the below, the directory path for class C info will be +// /A/B +// +// namespace A { +// namespace B { +// +// class C {}; +// +// } +// } +llvm::SmallString<128> +getInfoRelativePath(const llvm::SmallVectorImpl &Namespaces) { + llvm::SmallString<128> Path; + for (auto R = Namespaces.rbegin(), E = Namespaces.rend(); R != E; ++R) + llvm::sys::path::append(Path, R->Name); + return Path; +} + +llvm::SmallString<128> getInfoRelativePath(const Decl *D) { + llvm::SmallVector Namespaces; + // The third arg in populateParentNamespaces is a boolean passed by reference, + // its value is not relevant in here so it's not used anywhere besides the + // function call + bool B = true; + populateParentNamespaces(Namespaces, D, B); + return getInfoRelativePath(Namespaces); +} + +class ClangDocCommentVisitor + : public ConstCommentVisitor { +public: + ClangDocCommentVisitor(CommentInfo &CI) : CurrentCI(CI) {} + + void parseComment(const comments::Comment *C); + + void visitTextComment(const TextComment *C); + void visitInlineCommandComment(const InlineCommandComment *C); + void visitHTMLStartTagComment(const HTMLStartTagComment *C); + void visitHTMLEndTagComment(const HTMLEndTagComment *C); + void visitBlockCommandComment(const BlockCommandComment *C); + void visitParamCommandComment(const ParamCommandComment *C); + void visitTParamCommandComment(const TParamCommandComment *C); + void visitVerbatimBlockComment(const VerbatimBlockComment *C); + void visitVerbatimBlockLineComment(const VerbatimBlockLineComment *C); + void visitVerbatimLineComment(const VerbatimLineComment *C); + +private: + std::string getCommandName(unsigned CommandID) const; + bool isWhitespaceOnly(StringRef S) const; + + CommentInfo &CurrentCI; +}; + +void ClangDocCommentVisitor::parseComment(const comments::Comment *C) { + CurrentCI.Kind = C->getCommentKindName(); + ConstCommentVisitor::visit(C); + for (comments::Comment *Child : + llvm::make_range(C->child_begin(), C->child_end())) { + CurrentCI.Children.emplace_back(std::make_unique()); + ClangDocCommentVisitor Visitor(*CurrentCI.Children.back()); + Visitor.parseComment(Child); } } -} // namespace +void ClangDocCommentVisitor::visitTextComment(const TextComment *C) { + if (!isWhitespaceOnly(C->getText())) + CurrentCI.Text = C->getText(); +} + +void ClangDocCommentVisitor::visitInlineCommandComment( + const InlineCommandComment *C) { + CurrentCI.Name = getCommandName(C->getCommandID()); + for (unsigned I = 0, E = C->getNumArgs(); I != E; ++I) + CurrentCI.Args.push_back(C->getArgText(I)); +} + +void ClangDocCommentVisitor::visitHTMLStartTagComment( + const HTMLStartTagComment *C) { + CurrentCI.Name = C->getTagName(); + CurrentCI.SelfClosing = C->isSelfClosing(); + for (unsigned I = 0, E = C->getNumAttrs(); I < E; ++I) { + const HTMLStartTagComment::Attribute &Attr = C->getAttr(I); + CurrentCI.AttrKeys.push_back(Attr.Name); + CurrentCI.AttrValues.push_back(Attr.Value); + } +} + +void ClangDocCommentVisitor::visitHTMLEndTagComment( + const HTMLEndTagComment *C) { + CurrentCI.Name = C->getTagName(); + CurrentCI.SelfClosing = true; +} + +void ClangDocCommentVisitor::visitBlockCommandComment( + const BlockCommandComment *C) { + CurrentCI.Name = getCommandName(C->getCommandID()); + for (unsigned I = 0, E = C->getNumArgs(); I < E; ++I) + CurrentCI.Args.push_back(C->getArgText(I)); +} + +void ClangDocCommentVisitor::visitParamCommandComment( + const ParamCommandComment *C) { + CurrentCI.Direction = + ParamCommandComment::getDirectionAsString(C->getDirection()); + CurrentCI.Explicit = C->isDirectionExplicit(); + if (C->hasParamName()) + CurrentCI.ParamName = C->getParamNameAsWritten(); +} + +void ClangDocCommentVisitor::visitTParamCommandComment( + const TParamCommandComment *C) { + if (C->hasParamName()) + CurrentCI.ParamName = C->getParamNameAsWritten(); +} + +void ClangDocCommentVisitor::visitVerbatimBlockComment( + const VerbatimBlockComment *C) { + CurrentCI.Name = getCommandName(C->getCommandID()); + CurrentCI.CloseName = C->getCloseName(); +} + +void ClangDocCommentVisitor::visitVerbatimBlockLineComment( + const VerbatimBlockLineComment *C) { + if (!isWhitespaceOnly(C->getText())) + CurrentCI.Text = C->getText(); +} -// Dispatch function. -llvm::Expected> -mergeInfos(std::vector> &Values) { - if (Values.empty() || !Values[0]) - return llvm::createStringError(llvm::inconvertibleErrorCode(), - "no info values to merge"); +void ClangDocCommentVisitor::visitVerbatimLineComment( + const VerbatimLineComment *C) { + if (!isWhitespaceOnly(C->getText())) + CurrentCI.Text = C->getText(); +} + +bool ClangDocCommentVisitor::isWhitespaceOnly(llvm::StringRef S) const { + return llvm::all_of(S, isspace); +} - switch (Values[0]->IT) { +std::string ClangDocCommentVisitor::getCommandName(unsigned CommandID) const { + const CommandInfo *Info = CommandTraits::getBuiltinCommandInfo(CommandID); + if (Info) + return Info->Name; + // TODO: Add parsing for \file command. + return ""; +} + +// Serializing functions. + +std::string getSourceCode(const Decl *D, const SourceRange &R) { + return Lexer::getSourceText(CharSourceRange::getTokenRange(R), + D->getASTContext().getSourceManager(), + D->getASTContext().getLangOpts()) + .str(); +} + +template static std::string serialize(T &I) { + SmallString<2048> Buffer; + llvm::BitstreamWriter Stream(Buffer); + ClangDocBitcodeWriter Writer(Stream); + Writer.emitBlock(I); + return Buffer.str().str(); +} + +std::string serialize(std::unique_ptr &I) { + switch (I->IT) { case InfoType::IT_namespace: - return reduce(Values); + return serialize(*static_cast(I.get())); case InfoType::IT_record: - return reduce(Values); + return serialize(*static_cast(I.get())); case InfoType::IT_enum: - return reduce(Values); + return serialize(*static_cast(I.get())); case InfoType::IT_function: - return reduce(Values); - case InfoType::IT_typedef: - return reduce(Values); + return serialize(*static_cast(I.get())); default: - return llvm::createStringError(llvm::inconvertibleErrorCode(), - "unexpected info type"); + return ""; } } -bool CommentInfo::operator==(const CommentInfo &Other) const { - auto FirstCI = std::tie(Kind, Text, Name, Direction, ParamName, CloseName, - SelfClosing, Explicit, AttrKeys, AttrValues, Args); - auto SecondCI = - std::tie(Other.Kind, Other.Text, Other.Name, Other.Direction, - Other.ParamName, Other.CloseName, Other.SelfClosing, - Other.Explicit, Other.AttrKeys, Other.AttrValues, Other.Args); +static void parseFullComment(const FullComment *C, CommentInfo &CI) { + ClangDocCommentVisitor Visitor(CI); + Visitor.parseComment(C); +} - if (FirstCI != SecondCI || Children.size() != Other.Children.size()) - return false; +static SymbolID getUSRForDecl(const Decl *D) { + llvm::SmallString<128> USR; + if (index::generateUSRForDecl(D, USR)) + return SymbolID(); + return hashUSR(USR); +} + +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; +} - return std::equal(Children.begin(), Children.end(), Other.Children.begin(), - llvm::deref>{}); +static RecordDecl *getRecordDeclForType(const QualType &T) { + if (const RecordDecl *D = T->getAsRecordDecl()) + return D->getDefinition(); + return nullptr; } -bool CommentInfo::operator<(const CommentInfo &Other) const { - auto FirstCI = std::tie(Kind, Text, Name, Direction, ParamName, CloseName, - SelfClosing, Explicit, AttrKeys, AttrValues, Args); - auto SecondCI = - std::tie(Other.Kind, Other.Text, Other.Name, Other.Direction, - Other.ParamName, Other.CloseName, Other.SelfClosing, - Other.Explicit, Other.AttrKeys, Other.AttrValues, Other.Args); +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 (isa(TD)) + IT = InfoType::IT_enum; + else if (isa(TD)) + IT = InfoType::IT_record; + else + IT = InfoType::IT_default; + + 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; +} - if (FirstCI < SecondCI) +static bool isPublic(const clang::AccessSpecifier AS, + const clang::Linkage Link) { + if (AS == clang::AccessSpecifier::AS_private) + return false; + if ((Link == clang::Linkage::Module) || + (Link == clang::Linkage::External)) return true; + return false; // otherwise, linkage is some form of internal linkage +} + +static bool shouldSerializeInfo(bool PublicOnly, bool IsInAnonymousNamespace, + const NamedDecl *D) { + bool IsAnonymousNamespace = false; + if (const auto *N = dyn_cast(D)) + IsAnonymousNamespace = N->isAnonymousNamespace(); + return !PublicOnly || + (!IsInAnonymousNamespace && !IsAnonymousNamespace && + isPublic(D->getAccessUnsafe(), D->getLinkageInternal())); +} + +// The InsertChild functions insert the given info into the given scope using +// the method appropriate for that type. Some types are moved into the +// appropriate vector, while other types have Reference objects generated to +// refer to them. +// +// See MakeAndInsertIntoParent(). +static void InsertChild(ScopeChildren &Scope, const NamespaceInfo &Info) { + Scope.Namespaces.emplace_back(Info.USR, Info.Name, InfoType::IT_namespace, + Info.Name, getInfoRelativePath(Info.Namespace)); +} + +static void InsertChild(ScopeChildren &Scope, const RecordInfo &Info) { + Scope.Records.emplace_back(Info.USR, Info.Name, InfoType::IT_record, + Info.Name, getInfoRelativePath(Info.Namespace)); +} + +static void InsertChild(ScopeChildren &Scope, EnumInfo Info) { + Scope.Enums.push_back(std::move(Info)); +} - if (FirstCI == SecondCI) { - return std::lexicographical_compare( - Children.begin(), Children.end(), Other.Children.begin(), - Other.Children.end(), llvm::deref>()); +static void InsertChild(ScopeChildren &Scope, FunctionInfo Info) { + Scope.Functions.push_back(std::move(Info)); +} + +static void InsertChild(ScopeChildren &Scope, TypedefInfo Info) { + Scope.Typedefs.push_back(std::move(Info)); +} + +// Creates a parent of the correct type for the given child and inserts it into +// that parent. +// +// This is complicated by the fact that namespaces and records are inserted by +// reference (constructing a "Reference" object with that namespace/record's +// info), while everything else is inserted by moving it directly into the child +// vectors. +// +// For namespaces and records, explicitly specify a const& template parameter +// when invoking this function: +// MakeAndInsertIntoParent(...); +// Otherwise, specify an rvalue reference and move into the +// parameter. Since each variant is used once, it's not worth having a more +// elaborate system to automatically deduce this information. +template +std::unique_ptr MakeAndInsertIntoParent(ChildType Child) { + if (Child.Namespace.empty()) { + // Insert into unnamed parent namespace. + auto ParentNS = std::make_unique(); + InsertChild(ParentNS->Children, std::forward(Child)); + return ParentNS; } - return false; + switch (Child.Namespace[0].RefType) { + case InfoType::IT_namespace: { + auto ParentNS = std::make_unique(); + ParentNS->USR = Child.Namespace[0].USR; + InsertChild(ParentNS->Children, std::forward(Child)); + return ParentNS; + } + case InfoType::IT_record: { + auto ParentRec = std::make_unique(); + ParentRec->USR = Child.Namespace[0].USR; + InsertChild(ParentRec->Children, std::forward(Child)); + return ParentRec; + } + default: + llvm_unreachable("Invalid reference type for parent namespace"); + } +} + +// There are two uses for this function. +// 1) Getting the resulting mode of inheritance of a record. +// Example: class A {}; class B : private A {}; class C : public B {}; +// It's explicit that C is publicly inherited from C and B is privately +// inherited from A. It's not explicit but C is also privately inherited from +// A. This is the AS that this function calculates. FirstAS is the +// inheritance mode of `class C : B` and SecondAS is the inheritance mode of +// `class B : A`. +// 2) Getting the inheritance mode of an inherited attribute / method. +// Example : class A { public: int M; }; class B : private A {}; +// Class B is inherited from class A, which has a public attribute. This +// attribute is now part of the derived class B but it's not public. This +// will be private because the inheritance is private. This is the AS that +// this function calculates. FirstAS is the inheritance mode and SecondAS is +// the AS of the attribute / method. +static AccessSpecifier getFinalAccessSpecifier(AccessSpecifier FirstAS, + AccessSpecifier SecondAS) { + if (FirstAS == AccessSpecifier::AS_none || + SecondAS == AccessSpecifier::AS_none) + return AccessSpecifier::AS_none; + if (FirstAS == AccessSpecifier::AS_private || + SecondAS == AccessSpecifier::AS_private) + return AccessSpecifier::AS_private; + if (FirstAS == AccessSpecifier::AS_protected || + SecondAS == AccessSpecifier::AS_protected) + return AccessSpecifier::AS_protected; + return AccessSpecifier::AS_public; } -static llvm::SmallString<64> -calculateRelativeFilePath(const InfoType &Type, const StringRef &Path, - const StringRef &Name, const StringRef &CurrentPath) { - llvm::SmallString<64> FilePath; +// The Access parameter is only provided when parsing the field of an inherited +// record, the access specification of the field depends on the inheritance mode +static void parseFields(RecordInfo &I, const RecordDecl *D, bool PublicOnly, + AccessSpecifier Access = AccessSpecifier::AS_public) { + for (const FieldDecl *F : D->fields()) { + if (!shouldSerializeInfo(PublicOnly, /*IsInAnonymousNamespace=*/false, F)) + continue; - if (CurrentPath != Path) { - // iterate back to the top - for (llvm::sys::path::const_iterator I = - llvm::sys::path::begin(CurrentPath); - I != llvm::sys::path::end(CurrentPath); ++I) - llvm::sys::path::append(FilePath, ".."); - llvm::sys::path::append(FilePath, Path); + // 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()), + F->getNameAsString(), + getFinalAccessSpecifier(Access, F->getAccessUnsafe())); + populateMemberTypeInfo(NewMember, F); } +} - // Namespace references have a Path to the parent namespace, but - // the file is actually in the subdirectory for the namespace. - if (Type == doc::InfoType::IT_namespace) - llvm::sys::path::append(FilePath, Name); +static void parseEnumerators(EnumInfo &I, const EnumDecl *D) { + for (const EnumConstantDecl *E : D->enumerators()) { + std::string ValueExpr; + if (const Expr *InitExpr = E->getInitExpr()) + ValueExpr = getSourceCode(D, InitExpr->getSourceRange()); + SmallString<16> ValueStr; + E->getInitVal().toString(ValueStr); + I.Members.emplace_back(E->getNameAsString(), ValueStr.str(), ValueExpr); + ASTContext &Context = E->getASTContext(); + if (RawComment *Comment = + E->getASTContext().getRawCommentForDeclNoCache(E)) { + Comment->setAttached(); + if (comments::FullComment *Fc = Comment->parse(Context, nullptr, E)) { + EnumValueInfo &Member = I.Members.back(); + Member.Description.emplace_back(); + parseFullComment(Fc, Member.Description.back()); + } + } + } +} - return llvm::sys::path::relative_path(FilePath); +static void parseParameters(FunctionInfo &I, const FunctionDecl *D) { + for (const ParmVarDecl *P : D->parameters()) { + FieldTypeInfo &FieldInfo = I.Params.emplace_back( + getTypeInfoForType(P->getOriginalType()), P->getNameAsString()); + FieldInfo.DefaultValue = getSourceCode(D, P->getDefaultArgRange()); + } } -llvm::SmallString<64> -Reference::getRelativeFilePath(const StringRef &CurrentPath) const { - return calculateRelativeFilePath(RefType, Path, Name, CurrentPath); +// TODO: Remove the serialization of Parents and VirtualParents, this +// information is also extracted in the other definition of parseBases. +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; + if (const auto *Ty = B.getType()->getAs()) { + const TemplateDecl *D = Ty->getTemplateName().getAsTemplateDecl(); + I.Parents.emplace_back(getUSRForDecl(D), B.getType().getAsString(), + InfoType::IT_record, B.getType().getAsString()); + } else if (const RecordDecl *P = getRecordDeclForType(B.getType())) + I.Parents.emplace_back(getUSRForDecl(P), P->getNameAsString(), + InfoType::IT_record, P->getQualifiedNameAsString(), + getInfoRelativePath(P)); + else + I.Parents.emplace_back(SymbolID(), B.getType().getAsString()); + } + for (const CXXBaseSpecifier &B : D->vbases()) { + if (const RecordDecl *P = getRecordDeclForType(B.getType())) + I.VirtualParents.emplace_back( + getUSRForDecl(P), P->getNameAsString(), InfoType::IT_record, + P->getQualifiedNameAsString(), getInfoRelativePath(P)); + else + I.VirtualParents.emplace_back(SymbolID(), B.getType().getAsString()); + } } -llvm::SmallString<16> Reference::getFileBaseName() const { - if (RefType == InfoType::IT_namespace) - return llvm::SmallString<16>("index"); +template +static void +populateParentNamespaces(llvm::SmallVector &Namespaces, + const T *D, bool &IsInAnonymousNamespace) { + const DeclContext *DC = D->getDeclContext(); + do { + if (const auto *N = dyn_cast(DC)) { + std::string Namespace; + if (N->isAnonymousNamespace()) { + Namespace = "@nonymous_namespace"; + IsInAnonymousNamespace = true; + } else + Namespace = N->getNameAsString(); + Namespaces.emplace_back(getUSRForDecl(N), Namespace, + InfoType::IT_namespace, + N->getQualifiedNameAsString()); + } else if (const auto *N = dyn_cast(DC)) + Namespaces.emplace_back(getUSRForDecl(N), N->getNameAsString(), + InfoType::IT_record, + N->getQualifiedNameAsString()); + else if (const auto *N = dyn_cast(DC)) + Namespaces.emplace_back(getUSRForDecl(N), N->getNameAsString(), + InfoType::IT_function, + N->getQualifiedNameAsString()); + else if (const auto *N = dyn_cast(DC)) + Namespaces.emplace_back(getUSRForDecl(N), N->getNameAsString(), + InfoType::IT_enum, N->getQualifiedNameAsString()); + } while ((DC = DC->getParent())); + // The global namespace should be added to the list of namespaces if the decl + // corresponds to a Record and if it doesn't have any namespace (because this + // means it's in the global namespace). Also if its outermost namespace is a + // record because that record matches the previous condition mentioned. + if ((Namespaces.empty() && isa(D)) || + (!Namespaces.empty() && Namespaces.back().RefType == InfoType::IT_record)) + Namespaces.emplace_back(SymbolID(), "GlobalNamespace", + InfoType::IT_namespace); +} - return Name; +void populateTemplateParameters(std::optional &TemplateInfo, + const clang::Decl *D) { + if (const TemplateParameterList *ParamList = + D->getDescribedTemplateParams()) { + if (!TemplateInfo) { + TemplateInfo.emplace(); + } + for (const NamedDecl *ND : *ParamList) { + TemplateInfo->Params.emplace_back( + getSourceCode(ND, ND->getSourceRange())); + } + } } -llvm::SmallString<64> -Info::getRelativeFilePath(const StringRef &CurrentPath) const { - return calculateRelativeFilePath(IT, Path, extractName(), CurrentPath); +TemplateParamInfo TemplateArgumentToInfo(const clang::Decl *D, + const TemplateArgument &Arg) { + // The TemplateArgument's pretty printing handles all the normal cases + // well enough for our requirements. + std::string Str; + llvm::raw_string_ostream Stream(Str); + Arg.print(PrintingPolicy(D->getLangOpts()), Stream, false); + return TemplateParamInfo(Str); } -llvm::SmallString<16> Info::getFileBaseName() const { - if (IT == InfoType::IT_namespace) - return llvm::SmallString<16>("index"); +template +static void populateInfo(Info &I, const T *D, const FullComment *C, + bool &IsInAnonymousNamespace) { + I.USR = getUSRForDecl(D); + I.Name = D->getNameAsString(); + populateParentNamespaces(I.Namespace, D, IsInAnonymousNamespace); + if (C) { + I.Description.emplace_back(); + parseFullComment(C, I.Description.back()); + } +} - return extractName(); +template +static void populateSymbolInfo(SymbolInfo &I, const T *D, const FullComment *C, + Location Loc, + bool &IsInAnonymousNamespace) { + populateInfo(I, D, C, IsInAnonymousNamespace); + if (D->isThisDeclarationADefinition()) + I.DefLoc = Loc; + else + I.Loc.emplace_back(Loc); } -bool Reference::mergeable(const Reference &Other) { - return RefType == Other.RefType && USR == Other.USR; +static void populateFunctionInfo(FunctionInfo &I, const FunctionDecl *D, + const FullComment *FC, + Location Loc, + bool &IsInAnonymousNamespace) { + populateSymbolInfo(I, D, FC, Loc, IsInAnonymousNamespace); + I.ReturnType = getTypeInfoForType(D->getReturnType()); + I.ProtoType = getFunctionPrototype(D); + parseParameters(I, D); + populateTemplateParameters(I.Template, D); + + // Handle function template specializations. + if (const FunctionTemplateSpecializationInfo *FTSI = + D->getTemplateSpecializationInfo()) { + if (!I.Template) + I.Template.emplace(); + I.Template->Specialization.emplace(); + auto &Specialization = *I.Template->Specialization; + + Specialization.SpecializationOf = getUSRForDecl(FTSI->getTemplate()); + + // Template parameters to the specialization. + if (FTSI->TemplateArguments) { + for (const TemplateArgument &Arg : FTSI->TemplateArguments->asArray()) { + Specialization.Params.push_back(TemplateArgumentToInfo(D, Arg)); + } + } + } } - -void Reference::merge(Reference &&Other) { - assert(mergeable(Other)); - if (Name.empty()) - Name = Other.Name; - if (Path.empty()) - Path = Other.Path; -} - -void Info::mergeBase(Info &&Other) { - assert(mergeable(Other)); - if (USR == EmptySID) - USR = Other.USR; - if (Name == "") - Name = Other.Name; - if (Path == "") - Path = Other.Path; - if (Namespace.empty()) - Namespace = std::move(Other.Namespace); - // Unconditionally extend the description, since each decl may have a comment. - std::move(Other.Description.begin(), Other.Description.end(), - std::back_inserter(Description)); - llvm::sort(Description); - auto Last = std::unique(Description.begin(), Description.end()); - Description.erase(Last, Description.end()); -} - -bool Info::mergeable(const Info &Other) { - return IT == Other.IT && USR == Other.USR; -} - -void SymbolInfo::merge(SymbolInfo &&Other) { - assert(mergeable(Other)); - if (!DefLoc) - DefLoc = std::move(Other.DefLoc); - // Unconditionally extend the list of locations, since we want all of them. - std::move(Other.Loc.begin(), Other.Loc.end(), std::back_inserter(Loc)); - llvm::sort(Loc); - auto Last = std::unique(Loc.begin(), Loc.end()); - Loc.erase(Last, Loc.end()); - mergeBase(std::move(Other)); -} - -NamespaceInfo::NamespaceInfo(SymbolID USR, StringRef Name, StringRef Path) - : Info(InfoType::IT_namespace, USR, Name, Path) {} - -void NamespaceInfo::merge(NamespaceInfo &&Other) { - assert(mergeable(Other)); - // Reduce children if necessary. - reduceChildren(Children.Namespaces, std::move(Other.Children.Namespaces)); - reduceChildren(Children.Records, std::move(Other.Children.Records)); - reduceChildren(Children.Functions, std::move(Other.Children.Functions)); - reduceChildren(Children.Enums, std::move(Other.Children.Enums)); - reduceChildren(Children.Typedefs, std::move(Other.Children.Typedefs)); - mergeBase(std::move(Other)); -} - -RecordInfo::RecordInfo(SymbolID USR, StringRef Name, StringRef Path) - : SymbolInfo(InfoType::IT_record, USR, Name, 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; - if (Members.empty()) - Members = std::move(Other.Members); - if (Bases.empty()) - Bases = std::move(Other.Bases); - if (Parents.empty()) - Parents = std::move(Other.Parents); - if (VirtualParents.empty()) - VirtualParents = std::move(Other.VirtualParents); - // Reduce children if necessary. - reduceChildren(Children.Records, std::move(Other.Children.Records)); - reduceChildren(Children.Functions, std::move(Other.Children.Functions)); - reduceChildren(Children.Enums, std::move(Other.Children.Enums)); - reduceChildren(Children.Typedefs, std::move(Other.Children.Typedefs)); - SymbolInfo::merge(std::move(Other)); - if (!Template) - Template = Other.Template; -} - -void EnumInfo::merge(EnumInfo &&Other) { - assert(mergeable(Other)); - if (!Scoped) - Scoped = Other.Scoped; - if (Members.empty()) - Members = std::move(Other.Members); - SymbolInfo::merge(std::move(Other)); -} - -void FunctionInfo::merge(FunctionInfo &&Other) { - assert(mergeable(Other)); - if (!IsMethod) - IsMethod = Other.IsMethod; - if (!Access) - Access = Other.Access; - if (ReturnType.Type.USR == EmptySID && ReturnType.Type.Name == "") - ReturnType = std::move(Other.ReturnType); - if (Parent.USR == EmptySID && Parent.Name == "") - Parent = std::move(Other.Parent); - if (Params.empty()) - Params = std::move(Other.Params); - SymbolInfo::merge(std::move(Other)); - if (!Template) - Template = Other.Template; -} - -void TypedefInfo::merge(TypedefInfo &&Other) { - assert(mergeable(Other)); - if (!IsUsing) - IsUsing = Other.IsUsing; - if (Underlying.Type.Name == "") - Underlying = Other.Underlying; - SymbolInfo::merge(std::move(Other)); -} - -BaseRecordInfo::BaseRecordInfo() : RecordInfo() {} - -BaseRecordInfo::BaseRecordInfo(SymbolID USR, StringRef Name, StringRef Path, - bool IsVirtual, AccessSpecifier Access, - bool IsParent) - : RecordInfo(USR, Name, Path), IsVirtual(IsVirtual), Access(Access), - IsParent(IsParent) {} - -llvm::SmallString<16> Info::extractName() const { - if (!Name.empty()) - return Name; - - switch (IT) { - case InfoType::IT_namespace: - // Cover the case where the project contains a base namespace called - // 'GlobalNamespace' (i.e. a namespace at the same level as the global - // namespace, which would conflict with the hard-coded global namespace name - // below.) - if (Name == "GlobalNamespace" && Namespace.empty()) - return llvm::SmallString<16>("@GlobalNamespace"); - // The case of anonymous namespaces is taken care of in serialization, - // so here we can safely assume an unnamed namespace is the global - // one. - return llvm::SmallString<16>("GlobalNamespace"); - case InfoType::IT_record: - return llvm::SmallString<16>("@nonymous_record_" + - toHex(llvm::toStringRef(USR))); - case InfoType::IT_enum: - return llvm::SmallString<16>("@nonymous_enum_" + - toHex(llvm::toStringRef(USR))); - case InfoType::IT_typedef: - return llvm::SmallString<16>("@nonymous_typedef_" + - toHex(llvm::toStringRef(USR))); - case InfoType::IT_function: - return llvm::SmallString<16>("@nonymous_function_" + - toHex(llvm::toStringRef(USR))); - case InfoType::IT_default: - return llvm::SmallString<16>("@nonymous_" + toHex(llvm::toStringRef(USR))); - } - llvm_unreachable("Invalid InfoType."); - return llvm::SmallString<16>(""); -} - -// Order is based on the Name attribute: case insensitive order -bool Index::operator<(const Index &Other) const { - // Loop through each character of both strings - for (unsigned I = 0; I < Name.size() && I < Other.Name.size(); ++I) { - // Compare them after converting both to lower case - int D = tolower(Name[I]) - tolower(Other.Name[I]); - if (D == 0) - continue; - return D < 0; - } - // If both strings have the size it means they would be equal if changed to - // lower case. In here, lower case will be smaller than upper case - // Example: string < stRing = true - // This is the opposite of how operator < handles strings - if (Name.size() == Other.Name.size()) - return Name > Other.Name; - // If they are not the same size; the shorter string is smaller - return Name.size() < Other.Name.size(); -} - -void Index::sort() { - llvm::sort(Children); - for (auto &C : Children) - C.sort(); -} - -ClangDocContext::ClangDocContext(tooling::ExecutionContext *ECtx, - StringRef ProjectName, bool PublicOnly, - StringRef OutDirectory, StringRef SourceRoot, - StringRef RepositoryUrl, - std::vector UserStylesheets) - : ECtx(ECtx), ProjectName(ProjectName), PublicOnly(PublicOnly), - OutDirectory(OutDirectory), UserStylesheets(UserStylesheets) { - llvm::SmallString<128> SourceRootDir(SourceRoot); - if (SourceRoot.empty()) - // If no SourceRoot was provided the current path is used as the default - llvm::sys::fs::current_path(SourceRootDir); - this->SourceRoot = std::string(SourceRootDir); - if (!RepositoryUrl.empty()) { - this->RepositoryUrl = std::string(RepositoryUrl); - if (!RepositoryUrl.empty() && !RepositoryUrl.starts_with("http://") && - !RepositoryUrl.starts_with("https://")) - this->RepositoryUrl->insert(0, "https://"); - } -} - -void ScopeChildren::sort() { - llvm::sort(Namespaces.begin(), Namespaces.end()); - llvm::sort(Records.begin(), Records.end()); - llvm::sort(Functions.begin(), Functions.end()); - llvm::sort(Enums.begin(), Enums.end()); - llvm::sort(Typedefs.begin(), Typedefs.end()); + +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. + RawComment *Comment = Context.getRawCommentForDeclNoCache(D); + if (!Comment) + return; + + Comment->setAttached(); + if (comments::FullComment *Fc = Comment->parse(Context, nullptr, D)) { + I.Description.emplace_back(); + parseFullComment(Fc, I.Description.back()); + } +} + +static void +parseBases(RecordInfo &I, const CXXRecordDecl *D, bool IsFileInRootDir, + bool PublicOnly, bool IsParent, + AccessSpecifier ParentAccess = AccessSpecifier::AS_public) { + // Don't parse bases if this isn't a definition. + if (!D->isThisDeclarationADefinition()) + return; + for (const CXXBaseSpecifier &B : D->bases()) { + if (const RecordType *Ty = B.getType()->getAs()) { + if (const CXXRecordDecl *Base = + cast_or_null(Ty->getDecl()->getDefinition())) { + // Initialized without USR and name, this will be set in the following + // if-else stmt. + BaseRecordInfo BI( + {}, "", getInfoRelativePath(Base), B.isVirtual(), + getFinalAccessSpecifier(ParentAccess, B.getAccessSpecifier()), + IsParent); + if (const auto *Ty = B.getType()->getAs()) { + const TemplateDecl *D = Ty->getTemplateName().getAsTemplateDecl(); + BI.USR = getUSRForDecl(D); + BI.Name = B.getType().getAsString(); + } else { + BI.USR = getUSRForDecl(Base); + BI.Name = Base->getNameAsString(); + } + parseFields(BI, Base, PublicOnly, BI.Access); + for (const auto &Decl : Base->decls()) + if (const auto *MD = dyn_cast(Decl)) { + // Don't serialize private methods + if (MD->getAccessUnsafe() == AccessSpecifier::AS_private || + !MD->isUserProvided()) + continue; + FunctionInfo FI; + FI.IsMethod = true; + // The seventh arg in populateFunctionInfo is a boolean passed by + // reference, its value is not relevant in here so it's not used + // anywhere besides the function call. + bool IsInAnonymousNamespace; + populateFunctionInfo(FI, MD, /*FullComment=*/{}, /*Location=*/{}, + IsInAnonymousNamespace); + FI.Access = + getFinalAccessSpecifier(BI.Access, MD->getAccessUnsafe()); + BI.Children.Functions.emplace_back(std::move(FI)); + } + I.Bases.emplace_back(std::move(BI)); + // Call this function recursively to get the inherited classes of + // this base; these new bases will also get stored in the original + // RecordInfo: I. + parseBases(I, Base, IsFileInRootDir, PublicOnly, false, + I.Bases.back().Access); + } + } + } +} + +std::pair, std::unique_ptr> +emitInfo(const NamespaceDecl *D, const FullComment *FC, Location Loc, + bool PublicOnly) { + auto I = std::make_unique(); + bool IsInAnonymousNamespace = false; + populateInfo(*I, D, FC, IsInAnonymousNamespace); + if (!shouldSerializeInfo(PublicOnly, IsInAnonymousNamespace, D)) + return {}; + + I->Name = D->isAnonymousNamespace() + ? llvm::SmallString<16>("@nonymous_namespace") + : I->Name; + I->Path = getInfoRelativePath(I->Namespace); + if (I->Namespace.empty() && I->USR == SymbolID()) + return {std::unique_ptr{std::move(I)}, nullptr}; + + // Namespaces are inserted into the parent by reference, so we need to return + // both the parent and the record itself. + return {std::move(I), MakeAndInsertIntoParent(*I)}; +} + +std::pair, std::unique_ptr> +emitInfo(const RecordDecl *D, const FullComment *FC, + Location Loc, bool PublicOnly) { + auto I = std::make_unique(); + bool IsInAnonymousNamespace = false; + 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, true, PublicOnly, true); + } + I->Path = getInfoRelativePath(I->Namespace); + + populateTemplateParameters(I->Template, D); + + // Full and partial specializations. + if (auto *CTSD = dyn_cast(D)) { + if (!I->Template) + I->Template.emplace(); + I->Template->Specialization.emplace(); + auto &Specialization = *I->Template->Specialization; + + // What this is a specialization of. + auto SpecOf = CTSD->getSpecializedTemplateOrPartial(); + 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 + // because the non-explicit template parameters will have generated internal + // placeholder names rather than the names the user typed that match the + // template parameters. + if (const ClassTemplatePartialSpecializationDecl *CTPSD = + dyn_cast(D)) { + if (const ASTTemplateArgumentListInfo *AsWritten = + CTPSD->getTemplateArgsAsWritten()) { + for (unsigned i = 0; i < AsWritten->getNumTemplateArgs(); i++) { + Specialization.Params.emplace_back( + getSourceCode(D, (*AsWritten)[i].getSourceRange())); + } + } + } else { + for (const TemplateArgument &Arg : CTSD->getTemplateArgs().asArray()) { + Specialization.Params.push_back(TemplateArgumentToInfo(D, Arg)); + } + } + } + // Records are inserted into the parent by reference, so we need to return + // both the parent and the record itself. + auto Parent = MakeAndInsertIntoParent(*I); + return {std::move(I), std::move(Parent)}; } + +std::pair, std::unique_ptr> +emitInfo(const FunctionDecl *D, const FullComment *FC, + Location Loc, bool PublicOnly) { + FunctionInfo Func; + bool IsInAnonymousNamespace = false; + populateFunctionInfo(Func, D, FC, Loc, IsInAnonymousNamespace); + Func.Access = clang::AccessSpecifier::AS_none; + if (!shouldSerializeInfo(PublicOnly, IsInAnonymousNamespace, D)) + return {}; + + // Info is wrapped in its parent scope so is returned in the second position. + return {nullptr, MakeAndInsertIntoParent(std::move(Func))}; +} + +std::pair, std::unique_ptr> +emitInfo(const CXXMethodDecl *D, const FullComment *FC, + Location Loc, bool PublicOnly) { + FunctionInfo Func; + bool IsInAnonymousNamespace = false; + populateFunctionInfo(Func, D, FC, Loc, IsInAnonymousNamespace); + if (!shouldSerializeInfo(PublicOnly, IsInAnonymousNamespace, D)) + return {}; + + Func.IsMethod = true; + + const NamedDecl *Parent = nullptr; + if (const auto *SD = + dyn_cast(D->getParent())) + Parent = SD->getSpecializedTemplate(); + else + Parent = D->getParent(); + + SymbolID ParentUSR = getUSRForDecl(Parent); + Func.Parent = + Reference{ParentUSR, Parent->getNameAsString(), InfoType::IT_record, + Parent->getQualifiedNameAsString()}; + Func.Access = D->getAccess(); + + // Info is wrapped in its parent scope so is returned in the second position. + return {nullptr, MakeAndInsertIntoParent(std::move(Func))}; +} + +std::pair, std::unique_ptr> +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 = 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 + // a record with that name, so we don't want to emit a duplicate here. + 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))}; +} + +// 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, + 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 = 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, Location Loc, + bool PublicOnly) { + EnumInfo Enum; + bool IsInAnonymousNamespace = false; + populateSymbolInfo(Enum, D, FC, Loc, IsInAnonymousNamespace); + + if (!shouldSerializeInfo(PublicOnly, IsInAnonymousNamespace, D)) + return {}; + + Enum.Scoped = D->isScoped(); + if (D->isFixed()) { + auto Name = D->getIntegerType().getAsString(); + Enum.BaseType = TypeInfo(Name, Name); + } + parseEnumerators(Enum, D); + + // Info is wrapped in its parent scope so is returned in the second position. + return {nullptr, MakeAndInsertIntoParent(std::move(Enum))}; +} + +} // namespace serialize } // namespace doc } // namespace clang From 233b3c19c23addb7e73bb98322c301a99dad29e7 Mon Sep 17 00:00:00 2001 From: PeterChou1 Date: Mon, 12 Aug 2024 19:45:02 -0400 Subject: [PATCH 06/24] [clang-doc] clang-format --- clang-tools-extra/clang-doc/HTMLGenerator.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/clang-tools-extra/clang-doc/HTMLGenerator.cpp b/clang-tools-extra/clang-doc/HTMLGenerator.cpp index 24aa2383abdad..09ddd648c5c1b 100644 --- a/clang-tools-extra/clang-doc/HTMLGenerator.cpp +++ b/clang-tools-extra/clang-doc/HTMLGenerator.cpp @@ -743,6 +743,7 @@ genHTML(const EnumInfo &I, const ClangDocContext &CDCtx) { TRow->Children.emplace_back(std::move(TD)); THead->Children.emplace_back(std::move(TRow)); Table->Children.emplace_back(std::move(THead)); + if (std::unique_ptr Node = genEnumMembersBlock(I.Members)) Table->Children.emplace_back(std::move(Node)); From af48431eea1960058713fa68d9ad2fe49718c185 Mon Sep 17 00:00:00 2001 From: PeterChou1 Date: Fri, 23 Aug 2024 17:39:16 -0400 Subject: [PATCH 07/24] [llvm] implement support for mustache template language --- llvm/include/llvm/Support/Mustache.h | 13 +- llvm/lib/Support/Mustache.cpp | 196 +++++++++++--------------- llvm/unittests/Support/CMakeLists.txt | 2 +- 3 files changed, 91 insertions(+), 120 deletions(-) diff --git a/llvm/include/llvm/Support/Mustache.h b/llvm/include/llvm/Support/Mustache.h index 41173b96d1a9a..9cd96739f2227 100644 --- a/llvm/include/llvm/Support/Mustache.h +++ b/llvm/include/llvm/Support/Mustache.h @@ -83,6 +83,7 @@ using Lambda = std::function; using SectionLambda = std::function; class ASTNode; +using AstPtr = std::unique_ptr; // A Template represents the container for the AST and the partials // and Lambdas that are registered with it. @@ -96,6 +97,10 @@ class Template { Template(Template &&Other) noexcept; + // Define this in the cpp file to work around ASTNode being an incomplete + // type. + ~Template(); + Template &operator=(Template &&Other) noexcept; void render(const llvm::json::Value &Data, llvm::raw_ostream &OS); @@ -112,15 +117,11 @@ class Template { void overrideEscapeCharacters(DenseMap Escapes); private: - StringMap Partials; + StringMap Partials; StringMap Lambdas; StringMap SectionLambdas; DenseMap Escapes; - // The allocator for the ASTNode Tree - llvm::BumpPtrAllocator AstAllocator; - // Allocator for each render call resets after each render - llvm::BumpPtrAllocator RenderAllocator; - ASTNode *Tree; + AstPtr Tree; }; } // namespace llvm::mustache diff --git a/llvm/lib/Support/Mustache.cpp b/llvm/lib/Support/Mustache.cpp index 2735bf2ca3b9b..2560619538f9a 100644 --- a/llvm/lib/Support/Mustache.cpp +++ b/llvm/lib/Support/Mustache.cpp @@ -122,35 +122,31 @@ class ASTNode { InvertSection, }; - ASTNode(llvm::BumpPtrAllocator &Alloc, llvm::StringMap &Partials, - llvm::StringMap &Lambdas, + ASTNode(llvm::StringMap &Partials, llvm::StringMap &Lambdas, llvm::StringMap &SectionLambdas, llvm::DenseMap &Escapes) - : Allocator(Alloc), Partials(Partials), Lambdas(Lambdas), - SectionLambdas(SectionLambdas), Escapes(Escapes), Ty(Type::Root), - Parent(nullptr), ParentContext(nullptr) {} + : Partials(Partials), Lambdas(Lambdas), SectionLambdas(SectionLambdas), + Escapes(Escapes), Ty(Type::Root), Parent(nullptr), + ParentContext(nullptr) {} - ASTNode(std::string Body, ASTNode *Parent, llvm::BumpPtrAllocator &Alloc, - llvm::StringMap &Partials, + ASTNode(std::string Body, ASTNode *Parent, llvm::StringMap &Partials, llvm::StringMap &Lambdas, llvm::StringMap &SectionLambdas, llvm::DenseMap &Escapes) - : Allocator(Alloc), Partials(Partials), Lambdas(Lambdas), - SectionLambdas(SectionLambdas), Escapes(Escapes), Ty(Type::Text), - Body(std::move(Body)), Parent(Parent), ParentContext(nullptr) {} + : Partials(Partials), Lambdas(Lambdas), SectionLambdas(SectionLambdas), + Escapes(Escapes), Ty(Type::Text), Body(std::move(Body)), Parent(Parent), + ParentContext(nullptr) {} // Constructor for Section/InvertSection/Variable/UnescapeVariable Nodes ASTNode(Type Ty, Accessor Accessor, ASTNode *Parent, - llvm::BumpPtrAllocator &Alloc, llvm::StringMap &Partials, - llvm::StringMap &Lambdas, + llvm::StringMap &Partials, llvm::StringMap &Lambdas, llvm::StringMap &SectionLambdas, llvm::DenseMap &Escapes) - : Allocator(Alloc), Partials(Partials), Lambdas(Lambdas), - SectionLambdas(SectionLambdas), Escapes(Escapes), Ty(Ty), - Parent(Parent), AccessorValue(std::move(Accessor)), - ParentContext(nullptr) {} + : Partials(Partials), Lambdas(Lambdas), SectionLambdas(SectionLambdas), + Escapes(Escapes), Ty(Ty), Parent(Parent), + AccessorValue(std::move(Accessor)), ParentContext(nullptr) {} - void addChild(ASTNode *Child) { Children.emplace_back(Child); }; + void addChild(AstPtr Child) { Children.emplace_back(std::move(Child)); }; void setRawBody(std::string NewBody) { RawBody = std::move(NewBody); }; @@ -172,8 +168,7 @@ class ASTNode { const llvm::json::Value *findContext(); - llvm::BumpPtrAllocator &Allocator; - StringMap &Partials; + StringMap &Partials; StringMap &Lambdas; StringMap &SectionLambdas; DenseMap &Escapes; @@ -183,38 +178,35 @@ class ASTNode { std::string Body; ASTNode *Parent; // TODO: switch implementation to SmallVector - std::vector Children; + std::vector Children; const Accessor AccessorValue; const llvm::json::Value *ParentContext; }; // A wrapper for arena allocator for ASTNodes -ASTNode *createRootNode(void *Node, llvm::BumpPtrAllocator &Alloc, - llvm::StringMap &Partials, - llvm::StringMap &Lambdas, - llvm::StringMap &SectionLambdas, - llvm::DenseMap &Escapes) { - return new (Node) ASTNode(Alloc, Partials, Lambdas, SectionLambdas, Escapes); +AstPtr createRootNode(llvm::StringMap &Partials, + llvm::StringMap &Lambdas, + llvm::StringMap &SectionLambdas, + llvm::DenseMap &Escapes) { + return std::make_unique(Partials, Lambdas, SectionLambdas, Escapes); } -ASTNode *createNode(void *Node, ASTNode::Type T, Accessor A, ASTNode *Parent, - llvm::BumpPtrAllocator &Alloc, - llvm::StringMap &Partials, - llvm::StringMap &Lambdas, - llvm::StringMap &SectionLambdas, - llvm::DenseMap &Escapes) { - return new (Node) ASTNode(T, std::move(A), Parent, Alloc, Partials, Lambdas, - SectionLambdas, Escapes); +AstPtr createNode(ASTNode::Type T, Accessor A, ASTNode *Parent, + llvm::StringMap &Partials, + llvm::StringMap &Lambdas, + llvm::StringMap &SectionLambdas, + llvm::DenseMap &Escapes) { + return std::make_unique(T, std::move(A), Parent, Partials, Lambdas, + SectionLambdas, Escapes); } -ASTNode *createTextNode(void *Node, std::string Body, ASTNode *Parent, - llvm::BumpPtrAllocator &Alloc, - llvm::StringMap &Partials, - llvm::StringMap &Lambdas, - llvm::StringMap &SectionLambdas, - llvm::DenseMap &Escapes) { - return new (Node) ASTNode(std::move(Body), Parent, Alloc, Partials, Lambdas, - SectionLambdas, Escapes); +AstPtr createTextNode(std::string Body, ASTNode *Parent, + llvm::StringMap &Partials, + llvm::StringMap &Lambdas, + llvm::StringMap &SectionLambdas, + llvm::DenseMap &Escapes) { + return std::make_unique(std::move(Body), Parent, Partials, Lambdas, + SectionLambdas, Escapes); } // Function to check if there is meaningful text behind. @@ -436,45 +428,36 @@ class AddIndentationStringStream : public raw_ostream { class Parser { public: - Parser(StringRef TemplateStr, BumpPtrAllocator &Allocator) - : ASTAllocator(Allocator), TemplateStr(TemplateStr) {} + Parser(StringRef TemplateStr) : TemplateStr(TemplateStr) {} - ASTNode *parse(llvm::BumpPtrAllocator &RenderAlloc, - llvm::StringMap &Partials, - llvm::StringMap &Lambdas, - llvm::StringMap &SectionLambdas, - llvm::DenseMap &Escapes); + AstPtr parse(llvm::StringMap &Partials, + llvm::StringMap &Lambdas, + llvm::StringMap &SectionLambdas, + llvm::DenseMap &Escapes); private: - void parseMustache(ASTNode *Parent, llvm::BumpPtrAllocator &Alloc, - llvm::StringMap &Partials, + void parseMustache(ASTNode *Parent, llvm::StringMap &Partials, llvm::StringMap &Lambdas, llvm::StringMap &SectionLambdas, llvm::DenseMap &Escapes); - BumpPtrAllocator &ASTAllocator; SmallVector Tokens; size_t CurrentPtr; StringRef TemplateStr; }; -ASTNode *Parser::parse(llvm::BumpPtrAllocator &RenderAlloc, - llvm::StringMap &Partials, - llvm::StringMap &Lambdas, - llvm::StringMap &SectionLambdas, - llvm::DenseMap &Escapes) { +AstPtr Parser::parse(llvm::StringMap &Partials, + llvm::StringMap &Lambdas, + llvm::StringMap &SectionLambdas, + llvm::DenseMap &Escapes) { Tokens = tokenize(TemplateStr); CurrentPtr = 0; - void *Root = ASTAllocator.Allocate(sizeof(ASTNode), alignof(ASTNode)); - ASTNode *RootNode = createRootNode(Root, RenderAlloc, Partials, Lambdas, - SectionLambdas, Escapes); - parseMustache(RootNode, RenderAlloc, Partials, Lambdas, SectionLambdas, - Escapes); + AstPtr RootNode = createRootNode(Partials, Lambdas, SectionLambdas, Escapes); + parseMustache(RootNode.get(), Partials, Lambdas, SectionLambdas, Escapes); return RootNode; } -void Parser::parseMustache(ASTNode *Parent, llvm::BumpPtrAllocator &Alloc, - llvm::StringMap &Partials, +void Parser::parseMustache(ASTNode *Parent, llvm::StringMap &Partials, llvm::StringMap &Lambdas, llvm::StringMap &SectionLambdas, llvm::DenseMap &Escapes) { @@ -483,65 +466,60 @@ void Parser::parseMustache(ASTNode *Parent, llvm::BumpPtrAllocator &Alloc, Token CurrentToken = Tokens[CurrentPtr]; CurrentPtr++; Accessor A = CurrentToken.getAccessor(); - ASTNode *CurrentNode; - void *Node = ASTAllocator.Allocate(sizeof(ASTNode), alignof(ASTNode)); + AstPtr CurrentNode; switch (CurrentToken.getType()) { case Token::Type::Text: { - CurrentNode = - createTextNode(Node, std::move(CurrentToken.TokenBody), Parent, Alloc, - Partials, Lambdas, SectionLambdas, Escapes); - Parent->addChild(CurrentNode); + CurrentNode = createTextNode(std::move(CurrentToken.TokenBody), Parent, + Partials, Lambdas, SectionLambdas, Escapes); + Parent->addChild(std::move(CurrentNode)); break; } case Token::Type::Variable: { - CurrentNode = - createNode(Node, ASTNode::Variable, std::move(A), Parent, Alloc, - Partials, Lambdas, SectionLambdas, Escapes); - Parent->addChild(CurrentNode); + CurrentNode = createNode(ASTNode::Variable, std::move(A), Parent, + Partials, Lambdas, SectionLambdas, Escapes); + Parent->addChild(std::move(CurrentNode)); break; } case Token::Type::UnescapeVariable: { - CurrentNode = - createNode(Node, ASTNode::UnescapeVariable, std::move(A), Parent, - Alloc, Partials, Lambdas, SectionLambdas, Escapes); - Parent->addChild(CurrentNode); + CurrentNode = createNode(ASTNode::UnescapeVariable, std::move(A), Parent, + Partials, Lambdas, SectionLambdas, Escapes); + Parent->addChild(std::move(CurrentNode)); break; } case Token::Type::Partial: { - CurrentNode = - createNode(Node, ASTNode::Partial, std::move(A), Parent, Alloc, - Partials, Lambdas, SectionLambdas, Escapes); + CurrentNode = createNode(ASTNode::Partial, std::move(A), Parent, Partials, + Lambdas, SectionLambdas, Escapes); CurrentNode->setIndentation(CurrentToken.getIndentation()); - Parent->addChild(CurrentNode); + Parent->addChild(std::move(CurrentNode)); break; } case Token::Type::SectionOpen: { - CurrentNode = createNode(Node, ASTNode::Section, A, Parent, Alloc, - Partials, Lambdas, SectionLambdas, Escapes); + CurrentNode = createNode(ASTNode::Section, A, Parent, Partials, Lambdas, + SectionLambdas, Escapes); size_t Start = CurrentPtr; - parseMustache(CurrentNode, Alloc, Partials, Lambdas, SectionLambdas, + parseMustache(CurrentNode.get(), Partials, Lambdas, SectionLambdas, Escapes); const size_t End = CurrentPtr - 1; std::string RawBody; for (std::size_t I = Start; I < End; I++) RawBody += Tokens[I].RawBody; CurrentNode->setRawBody(std::move(RawBody)); - Parent->addChild(CurrentNode); + Parent->addChild(std::move(CurrentNode)); break; } case Token::Type::InvertSectionOpen: { - CurrentNode = createNode(Node, ASTNode::InvertSection, A, Parent, Alloc, - Partials, Lambdas, SectionLambdas, Escapes); + CurrentNode = createNode(ASTNode::InvertSection, A, Parent, Partials, + Lambdas, SectionLambdas, Escapes); size_t Start = CurrentPtr; - parseMustache(CurrentNode, Alloc, Partials, Lambdas, SectionLambdas, + parseMustache(CurrentNode.get(), Partials, Lambdas, SectionLambdas, Escapes); const size_t End = CurrentPtr - 1; std::string RawBody; for (size_t Idx = Start; Idx < End; Idx++) RawBody += Tokens[Idx].RawBody; CurrentNode->setRawBody(std::move(RawBody)); - Parent->addChild(CurrentNode); + Parent->addChild(std::move(CurrentNode)); break; } case Token::Type::Comment: @@ -598,7 +576,7 @@ void ASTNode::render(const json::Value &Data, raw_ostream &OS) { case Partial: { auto Partial = Partials.find(AccessorValue[0]); if (Partial != Partials.end()) - renderPartial(Data, OS, Partial->getValue()); + renderPartial(Data, OS, Partial->getValue().get()); return; } case Variable: { @@ -691,7 +669,7 @@ const json::Value *ASTNode::findContext() { } void ASTNode::renderChild(const json::Value &Contexts, llvm::raw_ostream &OS) { - for (ASTNode *Child : Children) + for (AstPtr &Child : Children) Child->render(Contexts, OS); } @@ -707,9 +685,8 @@ void ASTNode::renderLambdas(const json::Value &Contexts, llvm::raw_ostream &OS, std::string LambdaStr; raw_string_ostream Output(LambdaStr); toMustacheString(LambdaResult, Output); - Parser P = Parser(LambdaStr, Allocator); - ASTNode *LambdaNode = - P.parse(Allocator, Partials, Lambdas, SectionLambdas, Escapes); + Parser P = Parser(LambdaStr); + AstPtr LambdaNode = P.parse(Partials, Lambdas, SectionLambdas, Escapes); EscapeStringStream ES(OS, Escapes); if (Ty == Variable) { @@ -727,23 +704,20 @@ void ASTNode::renderSectionLambdas(const json::Value &Contexts, std::string LambdaStr; raw_string_ostream Output(LambdaStr); toMustacheString(Return, Output); - Parser P = Parser(LambdaStr, Allocator); - ASTNode *LambdaNode = - P.parse(Allocator, Partials, Lambdas, SectionLambdas, Escapes); + Parser P = Parser(LambdaStr); + AstPtr LambdaNode = P.parse(Partials, Lambdas, SectionLambdas, Escapes); LambdaNode->render(Contexts, OS); return; } void Template::render(const json::Value &Data, llvm::raw_ostream &OS) { Tree->render(Data, OS); - RenderAllocator.Reset(); } void Template::registerPartial(std::string Name, std::string Partial) { - Parser P = Parser(Partial, AstAllocator); - ASTNode *PartialTree = - P.parse(RenderAllocator, Partials, Lambdas, SectionLambdas, Escapes); - Partials.insert(std::make_pair(Name, PartialTree)); + Parser P = Parser(Partial); + AstPtr PartialTree = P.parse(Partials, Lambdas, SectionLambdas, Escapes); + Partials.insert(std::make_pair(Name, std::move(PartialTree))); } void Template::registerLambda(std::string Name, Lambda L) { Lambdas[Name] = L; } @@ -757,8 +731,8 @@ void Template::overrideEscapeCharacters(DenseMap E) { } Template::Template(StringRef TemplateStr) { - Parser P = Parser(TemplateStr, AstAllocator); - Tree = P.parse(RenderAllocator, Partials, Lambdas, SectionLambdas, Escapes); + Parser P = Parser(TemplateStr); + Tree = P.parse(Partials, Lambdas, SectionLambdas, Escapes); // The default behavior is to escape html entities. DenseMap HtmlEntities = {{'&', "&"}, {'<', "<"}, @@ -771,11 +745,9 @@ Template::Template(StringRef TemplateStr) { Template::Template(Template &&Other) noexcept : Partials(std::move(Other.Partials)), Lambdas(std::move(Other.Lambdas)), SectionLambdas(std::move(Other.SectionLambdas)), - Escapes(std::move(Other.Escapes)), - AstAllocator(std::move(Other.AstAllocator)), - RenderAllocator(std::move(Other.RenderAllocator)), Tree(Other.Tree) { - Other.Tree = nullptr; -} + Escapes(std::move(Other.Escapes)), Tree(std::move(Other.Tree)) {} + +Template::~Template() = default; Template &Template::operator=(Template &&Other) noexcept { if (this != &Other) { @@ -783,9 +755,7 @@ Template &Template::operator=(Template &&Other) noexcept { Lambdas = std::move(Other.Lambdas); SectionLambdas = std::move(Other.SectionLambdas); Escapes = std::move(Other.Escapes); - AstAllocator = std::move(Other.AstAllocator); - RenderAllocator = std::move(Other.RenderAllocator); - Tree = Other.Tree; + Tree = std::move(Other.Tree); Other.Tree = nullptr; } return *this; 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 From 85cb28e55786122a405e591403350bfbe7764b34 Mon Sep 17 00:00:00 2001 From: PeterChou1 Date: Fri, 6 Sep 2024 17:34:43 -0400 Subject: [PATCH 08/24] [llvm][support] clang-format --- llvm/include/llvm/Support/Mustache.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/llvm/include/llvm/Support/Mustache.h b/llvm/include/llvm/Support/Mustache.h index 9cd96739f2227..15c669688a2cf 100644 --- a/llvm/include/llvm/Support/Mustache.h +++ b/llvm/include/llvm/Support/Mustache.h @@ -125,4 +125,4 @@ class Template { }; } // namespace llvm::mustache -#endif // LLVM_SUPPORT_MUSTACHE +#endif // LLVM_SUPPORT_MUSTACHE \ No newline at end of file From c685493105842abcf95844c4ae27e972e11ab4b2 Mon Sep 17 00:00:00 2001 From: PeterChou1 Date: Fri, 6 Sep 2024 17:50:37 -0400 Subject: [PATCH 09/24] [llvm][support] clang-format --- llvm/include/llvm/Support/Mustache.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/llvm/include/llvm/Support/Mustache.h b/llvm/include/llvm/Support/Mustache.h index 15c669688a2cf..9cd96739f2227 100644 --- a/llvm/include/llvm/Support/Mustache.h +++ b/llvm/include/llvm/Support/Mustache.h @@ -125,4 +125,4 @@ class Template { }; } // namespace llvm::mustache -#endif // LLVM_SUPPORT_MUSTACHE \ No newline at end of file +#endif // LLVM_SUPPORT_MUSTACHE From 8c85923b9c0117e4ce7f1fb15d49ca8d371b563f Mon Sep 17 00:00:00 2001 From: PeterChou1 Date: Tue, 8 Oct 2024 02:10:26 -0400 Subject: [PATCH 10/24] [llvm] add mustache verification tool --- llvm/tools/mustache/CMakeLists.txt | 3 + llvm/tools/mustache/mustache.cpp | 102 +++++++++++++++++++++++++++++ 2 files changed, 105 insertions(+) create mode 100644 llvm/tools/mustache/CMakeLists.txt create mode 100644 llvm/tools/mustache/mustache.cpp diff --git a/llvm/tools/mustache/CMakeLists.txt b/llvm/tools/mustache/CMakeLists.txt new file mode 100644 index 0000000000000..721b553741d05 --- /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..07b7c0dc52869 --- /dev/null +++ b/llvm/tools/mustache/mustache.cpp @@ -0,0 +1,102 @@ +//===- 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); + } + } + StringRef ActualStr = T.render(*Data); + 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 From 3c19f18826971a5b8eaf094b1477259ffe5e9048 Mon Sep 17 00:00:00 2001 From: PeterChou1 Date: Sat, 12 Oct 2024 03:41:22 -0400 Subject: [PATCH 11/24] [llvm] modify mustache tool --- llvm/tools/mustache/CMakeLists.txt | 2 +- llvm/tools/mustache/mustache.cpp | 10 ++++++---- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/llvm/tools/mustache/CMakeLists.txt b/llvm/tools/mustache/CMakeLists.txt index 721b553741d05..81e42264d4830 100644 --- a/llvm/tools/mustache/CMakeLists.txt +++ b/llvm/tools/mustache/CMakeLists.txt @@ -1,3 +1,3 @@ set(LLVM_LINK_COMPONENTS Support) - add_llvm_tool(mustache mustache.cpp) \ No newline at end of file +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 index 07b7c0dc52869..1fd106418d9cc 100644 --- a/llvm/tools/mustache/mustache.cpp +++ b/llvm/tools/mustache/mustache.cpp @@ -82,12 +82,14 @@ void runThroughTest(StringRef InputFile) { T.registerPartial(Str, Partial); } } - StringRef ActualStr = T.render(*Data); - if (ExpectedStr == ActualStr) { + std::string ActualStr; + llvm::raw_string_ostream Stream(ActualStr); + T.render(*Data, Stream); + if (ExpectedStr == ActualStr) Success++; - } else { + else llvm::outs() << "Test Failed: " << Name << "\n"; - } + Total++; } From 0d459f8836e65a0c5de40e14666fdec14990545a Mon Sep 17 00:00:00 2001 From: PeterChou1 Date: Tue, 8 Oct 2024 02:10:26 -0400 Subject: [PATCH 12/24] [llvm] add mustache verification tool --- llvm/tools/mustache/mustache.cpp | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/llvm/tools/mustache/mustache.cpp b/llvm/tools/mustache/mustache.cpp index 1fd106418d9cc..4c893437419e0 100644 --- a/llvm/tools/mustache/mustache.cpp +++ b/llvm/tools/mustache/mustache.cpp @@ -83,22 +83,19 @@ void runThroughTest(StringRef InputFile) { } } std::string ActualStr; - llvm::raw_string_ostream Stream(ActualStr); - T.render(*Data, Stream); + 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) { + for (const auto &FileName : InputFiles) runThroughTest(FileName); - } return 0; } \ No newline at end of file From 75925a41fce8d9bca7db015ff22d5969e780fa8a Mon Sep 17 00:00:00 2001 From: PeterChou1 Date: Tue, 8 Oct 2024 02:10:26 -0400 Subject: [PATCH 13/24] [llvm] add mustache verification tool --- llvm/tools/mustache/mustache.cpp | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/llvm/tools/mustache/mustache.cpp b/llvm/tools/mustache/mustache.cpp index 4c893437419e0..6d01d6b88ec9e 100644 --- a/llvm/tools/mustache/mustache.cpp +++ b/llvm/tools/mustache/mustache.cpp @@ -85,17 +85,20 @@ void runThroughTest(StringRef InputFile) { std::string ActualStr; llvm::raw_string_ostream OS(ActualStr); T.render(*Data, OS); - if (ExpectedStr == ActualStr) + if (ExpectedStr == ActualStr) { Success++; - else + } 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) + for (const auto &FileName : InputFiles) { runThroughTest(FileName); + } return 0; } \ No newline at end of file From c753075dc57b60c0e4303cb72d6110e512421319 Mon Sep 17 00:00:00 2001 From: PeterChou1 Date: Fri, 13 Sep 2024 02:26:22 -0400 Subject: [PATCH 14/24] [clang-doc] init mustache implementation --- clang-tools-extra/clang-doc/CMakeLists.txt | 1 + clang-tools-extra/clang-doc/HTMLGenerator.cpp | 3 +- .../clang-doc/HTMLMustacheGenerator.cpp | 349 ++++++++++++++++++ .../clang-doc/assets/template.mustache | 23 ++ .../clang-doc/tool/CMakeLists.txt | 1 + .../clang-doc/tool/ClangDocMain.cpp | 31 ++ 6 files changed, 407 insertions(+), 1 deletion(-) create mode 100644 clang-tools-extra/clang-doc/HTMLMustacheGenerator.cpp create mode 100644 clang-tools-extra/clang-doc/assets/template.mustache diff --git a/clang-tools-extra/clang-doc/CMakeLists.txt b/clang-tools-extra/clang-doc/CMakeLists.txt index 520fe58cbe68e..7e5055e6b58b9 100644 --- a/clang-tools-extra/clang-doc/CMakeLists.txt +++ b/clang-tools-extra/clang-doc/CMakeLists.txt @@ -15,6 +15,7 @@ add_clang_library(clangDoc STATIC Representation.cpp Serialize.cpp YAMLGenerator.cpp + HTMLMustacheGenerator.cpp DEPENDS omp_gen diff --git a/clang-tools-extra/clang-doc/HTMLGenerator.cpp b/clang-tools-extra/clang-doc/HTMLGenerator.cpp index 09ddd648c5c1b..6b0efc9d4f37c 100644 --- a/clang-tools-extra/clang-doc/HTMLGenerator.cpp +++ b/clang-tools-extra/clang-doc/HTMLGenerator.cpp @@ -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..63def07c5fa80 --- /dev/null +++ b/clang-tools-extra/clang-doc/HTMLMustacheGenerator.cpp @@ -0,0 +1,349 @@ +//===-- 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 "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); + } + + MustacheTemplateFile(StringRef TemplateStr) : Template(TemplateStr) {} +}; + +static std::unique_ptr NamespaceTemplate = nullptr; + +static std::unique_ptr RecordTemplate = nullptr; + + +llvm::Error +setupTemplateFiles(const clang::doc::ClangDocContext &CDCtx) { + auto TemplateFilePath = CDCtx.MustacheTemplates.lookup("template"); + auto Template = MustacheTemplateFile::createMustacheFile(TemplateFilePath); + if (auto EC = Template.getError()) { + return llvm::createFileError("cannot open file", EC); + } + NamespaceTemplate = std::move(Template.get()); +} + +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; + } + } + } +} + +Value extractValue(const Location &L, + std::optional RepositoryUrl = std::nullopt) { + Object Obj = Object(); + if (!L.IsFileInRootDir || !RepositoryUrl) { + Obj.insert({"LineNumber", L.LineNumber}); + Obj.insert({"Filename", L.Filename}); + } + SmallString<128> FileURL(*RepositoryUrl); + llvm::sys::path::append(FileURL, llvm::sys::path::Style::posix, L.Filename); + Obj.insert({"FileURL", FileURL}); +} + +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}); + 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 &P : I.Params) { + ParamArr.getAsArray()->emplace_back(extractValue(P.Type, ParentInfoDir)); + } + 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) { + if (!CDCtx.RepositoryUrl) + Obj.insert({"Location", extractValue(*I.DefLoc)}); + else + Obj.insert({"Location", extractValue(*I.DefLoc, + StringRef{*CDCtx.RepositoryUrl})}); + } + 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) { + if (!CDCtx.RepositoryUrl) + Obj.insert({"Location", extractValue(*I.DefLoc)}); + else + Obj.insert({"Location", extractValue(*I.DefLoc, + StringRef{*CDCtx.RepositoryUrl})}); + } + + return Obj; +} + +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 }); + } + + Value ArrNamespace = Array(); + for (const Reference& Child : I.Children.Namespaces) + ArrNamespace.getAsArray()->emplace_back(extractValue(Child, BasePath)); + NamespaceValue.insert({"Namespace", ArrNamespace}); + + Value ArrRecord = Array(); + for (const Reference& Child : I.Children.Records) + ArrRecord.getAsArray()->emplace_back(extractValue(Child, BasePath)); + NamespaceValue.insert({"Record", ArrRecord}); + + Value ArrFunction = Array(); + for (const FunctionInfo& Child : I.Children.Functions) + ArrFunction.getAsArray()->emplace_back(extractValue(Child, BasePath, + CDCtx)); + NamespaceValue.insert({"Function", ArrRecord}); + + Value ArrEnum = Array(); + for (const EnumInfo& Child : I.Children.Enums) + ArrEnum.getAsArray()->emplace_back(extractValue(Child, CDCtx)); + NamespaceValue.insert({"Enums", ArrEnum }); + + Value ArrTypedefs = Array(); + for (const TypedefInfo& Child : I.Children.Typedefs) + ArrTypedefs.getAsArray()->emplace_back(extractValue(Child)); + NamespaceValue.insert({"Typedefs", ArrTypedefs }); + +} + + + +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); + OS << NamespaceTemplate->render(V); + break; + } + case InfoType::IT_record: + break; + case InfoType::IT_enum: + break; + case InfoType::IT_function: + break; + case InfoType::IT_typedef: + break; + case InfoType::IT_default: + return createStringError(llvm::inconvertibleErrorCode(), + "unexpected InfoType"); + } + return llvm::Error::success(); +} + +llvm::Error MustacheHTMLGenerator::createResources(ClangDocContext &CDCtx) { + +} + +const char *MustacheHTMLGenerator::Format = "mustache"; + + +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 HTMLGeneratorAnchorSource = 0; + +} // namespace doc +} // namespace clang \ 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..af4c60182ae52 --- /dev/null +++ b/clang-tools-extra/clang-doc/assets/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 +}} + + + + + {{NamespaceTitle}} + + {{#NamespaceComments}} +

Namespace Comment Present!

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

Namespace Present!

+ {{/Namespace}} + {{#Record}} +

Record Present!

+ {{/Record}} + \ 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..8eb067dbe6de8 100644 --- a/clang-tools-extra/clang-doc/tool/CMakeLists.txt +++ b/clang-tools-extra/clang-doc/tool/CMakeLists.txt @@ -21,6 +21,7 @@ target_link_libraries(clang-doc set(assets index.js + template.mustache 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..1897db3e7549f 100644 --- a/clang-tools-extra/clang-doc/tool/ClangDocMain.cpp +++ b/clang-tools-extra/clang-doc/tool/ClangDocMain.cpp @@ -205,6 +205,30 @@ 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> MustacheTemplate; + llvm::sys::path::native(AssetsPath, MustacheTemplate); + llvm::sys::path::append(MustacheTemplate, "template.mustache"); + CDCtx.MustacheTemplates.insert({"template", MustacheTemplate.c_str()}); +} + /// Make the output of clang-doc deterministic by sorting the children of /// namespaces and records. void sortUsrToInfo(llvm::StringMap> &USRToInfo) { @@ -277,6 +301,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"; From 19bed2c82968a66fd4557af17f419e6b22f62a9a Mon Sep 17 00:00:00 2001 From: PeterChou1 Date: Fri, 13 Sep 2024 17:50:03 -0400 Subject: [PATCH 15/24] [clang-doc] add a mustache backend init implementation --- clang-tools-extra/clang-doc/Generators.cpp | 4 +- .../clang-doc/HTMLMustacheGenerator.cpp | 32 ++++++++++++---- .../clang-doc/assets/template.mustache | 37 +++++++++++++++++-- .../clang-doc/tool/ClangDocMain.cpp | 9 ++++- .../Inputs/basic-project/include/Shape.h | 2 - 5 files changed, 68 insertions(+), 16 deletions(-) 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/HTMLMustacheGenerator.cpp b/clang-tools-extra/clang-doc/HTMLMustacheGenerator.cpp index 63def07c5fa80..bfa563ebdc7b3 100644 --- a/clang-tools-extra/clang-doc/HTMLMustacheGenerator.cpp +++ b/clang-tools-extra/clang-doc/HTMLMustacheGenerator.cpp @@ -54,6 +54,7 @@ class MustacheTemplateFile : public Template { 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) {} @@ -72,6 +73,7 @@ setupTemplateFiles(const clang::doc::ClangDocContext &CDCtx) { return llvm::createFileError("cannot open file", EC); } NamespaceTemplate = std::move(Template.get()); + return llvm::Error::success(); } llvm::Error @@ -118,6 +120,7 @@ MustacheHTMLGenerator::generateDocs(llvm::StringRef RootDir, } } } + return llvm::Error::success(); } Value extractValue(const Location &L, @@ -130,6 +133,8 @@ Value extractValue(const Location &L, SmallString<128> FileURL(*RepositoryUrl); llvm::sys::path::append(FileURL, llvm::sys::path::Style::posix, L.Filename); Obj.insert({"FileURL", FileURL}); + + return Obj; } Value extractValue(const Reference &I, StringRef CurrentDirectory) { @@ -280,29 +285,39 @@ Value extractValue(const NamespaceInfo &I, const ClangDocContext &CDCtx) { Value ArrNamespace = Array(); for (const Reference& Child : I.Children.Namespaces) ArrNamespace.getAsArray()->emplace_back(extractValue(Child, BasePath)); - NamespaceValue.insert({"Namespace", ArrNamespace}); + + if (!ArrNamespace.getAsArray()->empty()) + NamespaceValue.insert({"Namespace", Object{{"Links", ArrNamespace}}}); Value ArrRecord = Array(); for (const Reference& Child : I.Children.Records) ArrRecord.getAsArray()->emplace_back(extractValue(Child, BasePath)); - NamespaceValue.insert({"Record", ArrRecord}); + + if (!ArrRecord.getAsArray()->empty()) + NamespaceValue.insert({"Record", Object{{"Links", ArrRecord}}}); Value ArrFunction = Array(); for (const FunctionInfo& Child : I.Children.Functions) ArrFunction.getAsArray()->emplace_back(extractValue(Child, BasePath, CDCtx)); - NamespaceValue.insert({"Function", ArrRecord}); + if (!ArrFunction.getAsArray()->empty()) + NamespaceValue.insert({"Function", Object{{"Obj", ArrFunction}}}); Value ArrEnum = Array(); for (const EnumInfo& Child : I.Children.Enums) ArrEnum.getAsArray()->emplace_back(extractValue(Child, CDCtx)); - NamespaceValue.insert({"Enums", ArrEnum }); + + if (!ArrEnum.getAsArray()->empty()) + NamespaceValue.insert({"Enums", Object{{"Obj", ArrEnum }}}); Value ArrTypedefs = Array(); for (const TypedefInfo& Child : I.Children.Typedefs) ArrTypedefs.getAsArray()->emplace_back(extractValue(Child)); - NamespaceValue.insert({"Typedefs", ArrTypedefs }); + if (!ArrTypedefs.getAsArray()->empty()) + NamespaceValue.insert({"Typedefs", Object{{"Obj", ArrTypedefs }}}); + + return NamespaceValue; } @@ -313,6 +328,7 @@ MustacheHTMLGenerator::generateDocForInfo(Info *I, llvm::raw_ostream &OS, switch (I->IT) { case InfoType::IT_namespace: { Value V = extractValue(*static_cast(I), CDCtx); + llvm::outs() << V << "\n"; OS << NamespaceTemplate->render(V); break; } @@ -332,10 +348,10 @@ MustacheHTMLGenerator::generateDocForInfo(Info *I, llvm::raw_ostream &OS, } llvm::Error MustacheHTMLGenerator::createResources(ClangDocContext &CDCtx) { - + return llvm::Error::success(); } -const char *MustacheHTMLGenerator::Format = "mustache"; +const char *MustacheHTMLGenerator::Format = "mhtml"; static GeneratorRegistry::Add MHTML(MustacheHTMLGenerator::Format, @@ -343,7 +359,7 @@ static GeneratorRegistry::Add MHTML(MustacheHTMLGenerator // This anchor is used to force the linker to link in the generated object // file and thus register the generator. -volatile int HTMLGeneratorAnchorSource = 0; +volatile int MHTMLGeneratorAnchorSource = 0; } // namespace doc } // namespace clang \ 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 index af4c60182ae52..1d3407f8b5292 100644 --- a/clang-tools-extra/clang-doc/assets/template.mustache +++ b/clang-tools-extra/clang-doc/assets/template.mustache @@ -3,7 +3,7 @@ See https://llvm.org/LICENSE.txt for license information. SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception - This file defines the template + This file defines the template for generating Namespaces }} @@ -11,13 +11,42 @@ {{NamespaceTitle}} +

{{NamespaceTitle}}

{{#NamespaceComments}} -

Namespace Comment Present!

+

Namespace Comment

{{/NamespaceComments}} {{#Namespace}} -

Namespace Present!

+

Namespace

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

Record Present!

+

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/ClangDocMain.cpp b/clang-tools-extra/clang-doc/tool/ClangDocMain.cpp index 1897db3e7549f..6f743c6603d46 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"); } @@ -227,6 +232,8 @@ llvm::Error getMustacheHtmlFiles(const char *Argv0, llvm::sys::path::native(AssetsPath, MustacheTemplate); llvm::sys::path::append(MustacheTemplate, "template.mustache"); CDCtx.MustacheTemplates.insert({"template", MustacheTemplate.c_str()}); + + return llvm::Error::success(); } /// Make the output of clang-doc deterministic by sorting the children of 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; }; - - From 2a3c40e3640ae57813764d47e2d6a403a9549031 Mon Sep 17 00:00:00 2001 From: PeterChou1 Date: Tue, 24 Sep 2024 16:48:29 -0400 Subject: [PATCH 16/24] [clang-doc] update --- .../clang-doc/HTMLMustacheGenerator.cpp | 16 ++++++++--- .../clang-doc/assets/comments.mustache | 27 +++++++++++++++++++ .../clang-doc/assets/template.mustache | 7 +++-- .../Inputs/basic-project/include/Shape.h | 2 ++ 4 files changed, 46 insertions(+), 6 deletions(-) create mode 100644 clang-tools-extra/clang-doc/assets/comments.mustache diff --git a/clang-tools-extra/clang-doc/HTMLMustacheGenerator.cpp b/clang-tools-extra/clang-doc/HTMLMustacheGenerator.cpp index bfa563ebdc7b3..96a685dbe2ae8 100644 --- a/clang-tools-extra/clang-doc/HTMLMustacheGenerator.cpp +++ b/clang-tools-extra/clang-doc/HTMLMustacheGenerator.cpp @@ -194,8 +194,12 @@ Value extractValue(const FunctionInfo &I, StringRef ParentInfoDir, Obj.insert({"ReturnType", extractValue(I.ReturnType.Type, ParentInfoDir)}); Value ParamArr = Array(); - for (const auto &P : I.Params) { - ParamArr.getAsArray()->emplace_back(extractValue(P.Type, ParentInfoDir)); + 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}); @@ -328,17 +332,21 @@ MustacheHTMLGenerator::generateDocForInfo(Info *I, llvm::raw_ostream &OS, switch (I->IT) { case InfoType::IT_namespace: { Value V = extractValue(*static_cast(I), CDCtx); - llvm::outs() << V << "\n"; - OS << NamespaceTemplate->render(V); + llvm::raw_ostream &OS = llvm::outs(); + llvm::json::OStream J(OS, /*IndentSize=*/2); + J.value(V); break; } case InfoType::IT_record: 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(), diff --git a/clang-tools-extra/clang-doc/assets/comments.mustache b/clang-tools-extra/clang-doc/assets/comments.mustache new file mode 100644 index 0000000000000..1eac4de91836a --- /dev/null +++ b/clang-tools-extra/clang-doc/assets/comments.mustache @@ -0,0 +1,27 @@ +{{! + 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 +}} + +{{#FullComments}} + {{#Children}} + {{>Comment}} + {{/Children}} +{{/FullComments}} +{{#ParagraphComment}} + {{#Children}} + {{>Comment}} + {{/Children}} +{{/ParagraphComment}} +{{#BlockCommandComment}} +
{{Command}}
+ {{#Children}} + {{>Comment}} + {{/Children}} +{{/BlockCommandComment}} +{{#TextComment}} +

{{TextComment}}

+{{/TextComment}} \ 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 index 1d3407f8b5292..3b77e0189f4e2 100644 --- a/clang-tools-extra/clang-doc/assets/template.mustache +++ b/clang-tools-extra/clang-doc/assets/template.mustache @@ -3,7 +3,7 @@ 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 + This file defines the template for generating namespaces }} @@ -13,7 +13,9 @@

{{NamespaceTitle}}

{{#NamespaceComments}} -

Namespace Comment

+
+ {{>Comments}} +
{{/NamespaceComments}} {{#Namespace}}

Namespace

@@ -39,6 +41,7 @@

Function

{{#Obj}} + {{/Obj}}
{{/Function}} 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 5354032f4d832..e5c5d4c9e4412 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,3 +26,5 @@ class Shape { */ virtual double perimeter() const = 0; }; + + From 1b0755349be184c6200eaf224251a7116ff5bdd3 Mon Sep 17 00:00:00 2001 From: PeterChou1 Date: Mon, 7 Oct 2024 15:07:41 -0400 Subject: [PATCH 17/24] [clang-doc] add more templates --- clang-tools-extra/clang-doc/CMakeLists.txt | 5 +- .../clang-doc/FileHelpersClangDoc.cpp | 75 +++ .../clang-doc/FileHelpersClangDoc.h | 26 + clang-tools-extra/clang-doc/HTMLGenerator.cpp | 59 +-- .../clang-doc/HTMLMustacheGenerator.cpp | 281 ++++++++--- .../clang-doc/assets/clang-doc-mustache.css | 476 ++++++++++++++++++ .../clang-doc/assets/class-template.mustache | 227 +++++++++ .../assets/comments-template.mustache | 34 ++ .../clang-doc/assets/comments.mustache | 27 - .../clang-doc/assets/enum-template.mustache | 47 ++ .../assets/function-template.mustache | 23 + .../clang-doc/assets/mustache-index.js | 33 ++ .../assets/namespace-template.mustache | 87 ++++ .../clang-doc/assets/template.mustache | 55 -- .../clang-doc/tool/CMakeLists.txt | 8 +- .../clang-doc/tool/ClangDocMain.cpp | 53 +- 16 files changed, 1296 insertions(+), 220 deletions(-) create mode 100644 clang-tools-extra/clang-doc/FileHelpersClangDoc.cpp create mode 100644 clang-tools-extra/clang-doc/FileHelpersClangDoc.h create mode 100644 clang-tools-extra/clang-doc/assets/clang-doc-mustache.css create mode 100644 clang-tools-extra/clang-doc/assets/class-template.mustache create mode 100644 clang-tools-extra/clang-doc/assets/comments-template.mustache delete mode 100644 clang-tools-extra/clang-doc/assets/comments.mustache create mode 100644 clang-tools-extra/clang-doc/assets/enum-template.mustache create mode 100644 clang-tools-extra/clang-doc/assets/function-template.mustache create mode 100644 clang-tools-extra/clang-doc/assets/mustache-index.js create mode 100644 clang-tools-extra/clang-doc/assets/namespace-template.mustache delete mode 100644 clang-tools-extra/clang-doc/assets/template.mustache diff --git a/clang-tools-extra/clang-doc/CMakeLists.txt b/clang-tools-extra/clang-doc/CMakeLists.txt index 7e5055e6b58b9..cc07742bbb420 100644 --- a/clang-tools-extra/clang-doc/CMakeLists.txt +++ b/clang-tools-extra/clang-doc/CMakeLists.txt @@ -8,15 +8,16 @@ add_clang_library(clangDoc STATIC BitcodeReader.cpp BitcodeWriter.cpp ClangDoc.cpp + FileHelpersClangDoc.cpp Generators.cpp HTMLGenerator.cpp + HTMLMustacheGenerator.cpp Mapper.cpp MDGenerator.cpp Representation.cpp Serialize.cpp YAMLGenerator.cpp - HTMLMustacheGenerator.cpp - + DEPENDS omp_gen ClangDriverOptions 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/HTMLGenerator.cpp b/clang-tools-extra/clang-doc/HTMLGenerator.cpp index 6b0efc9d4f37c..a4a763100eb97 100644 --- a/clang-tools-extra/clang-doc/HTMLGenerator.cpp +++ b/clang-tools-extra/clang-doc/HTMLGenerator.cpp @@ -8,6 +8,7 @@ #include "Generators.h" #include "Representation.h" +#include "FileHelpersClangDoc.h" #include "clang/Basic/Version.h" #include "llvm/ADT/StringExtras.h" #include "llvm/ADT/StringRef.h" @@ -251,46 +252,6 @@ static void appendVector(std::vector &&New, std::move(New.begin(), New.end(), std::back_inserter(Original)); } -// Compute the relative path from an Origin directory to a Destination directory -static SmallString<128> computeRelativePath(StringRef Destination, - 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; - } - 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; -} // HTML generation @@ -1146,24 +1107,6 @@ static llvm::Error genIndex(const ClangDocContext &CDCtx) { return llvm::Error::success(); } -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)); - 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::Error HTMLGenerator::createResources(ClangDocContext &CDCtx) { auto Err = serializeIndex(CDCtx); if (Err) diff --git a/clang-tools-extra/clang-doc/HTMLMustacheGenerator.cpp b/clang-tools-extra/clang-doc/HTMLMustacheGenerator.cpp index 96a685dbe2ae8..6be4c795a6865 100644 --- a/clang-tools-extra/clang-doc/HTMLMustacheGenerator.cpp +++ b/clang-tools-extra/clang-doc/HTMLMustacheGenerator.cpp @@ -7,6 +7,7 @@ //===----------------------------------------------------------------------===// #include "Generators.h" #include "Representation.h" +#include "FileHelpersClangDoc.h" #include "llvm/Support/MemoryBuffer.h" #include "llvm/Support/Mustache.h" @@ -48,9 +49,8 @@ class MustacheTemplateFile : public Template { Error registerPartialFile(StringRef Name, StringRef FileName) { ErrorOr> BufferOrError = MemoryBuffer::getFile(FileName); - if (auto EC = BufferOrError.getError()) { + 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); @@ -65,24 +65,54 @@ 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 TemplateFilePath = CDCtx.MustacheTemplates.lookup("template"); - auto Template = MustacheTemplateFile::createMustacheFile(TemplateFilePath); - if (auto EC = Template.getError()) { - return llvm::createFileError("cannot open file", EC); - } - NamespaceTemplate = std::move(Template.get()); + 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)) { + 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. @@ -95,10 +125,9 @@ MustacheHTMLGenerator::generateDocs(llvm::StringRef RootDir, 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()) { + Err != std::error_code()) return llvm::createStringError(Err, "Failed to create directory '%s'.", Path.c_str()); - } CreatedDirs.insert(Path); } @@ -110,14 +139,13 @@ MustacheHTMLGenerator::generateDocs(llvm::StringRef RootDir, std::error_code FileErr; llvm::raw_fd_ostream InfoOS(Group.getKey(), FileErr, llvm::sys::fs::OF_None); - if (FileErr) { + 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)) { + if (llvm::Error Err = generateDocForInfo(Info, InfoOS, CDCtx)) return Err; - } } } return llvm::Error::success(); @@ -126,12 +154,15 @@ MustacheHTMLGenerator::generateDocs(llvm::StringRef RootDir, 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) { - Obj.insert({"LineNumber", L.LineNumber}); - Obj.insert({"Filename", L.Filename}); + 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; @@ -144,6 +175,8 @@ Value extractValue(const Reference &I, StringRef CurrentDirectory) { 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; } @@ -209,12 +242,13 @@ Value extractValue(const FunctionInfo &I, StringRef ParentInfoDir, ArrDesc.getAsArray()->emplace_back(extractValue(Child)); Obj.insert({"FunctionComments", ArrDesc}); } - if (I.DefLoc) { - if (!CDCtx.RepositoryUrl) - Obj.insert({"Location", extractValue(*I.DefLoc)}); + 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(*I.DefLoc, - StringRef{*CDCtx.RepositoryUrl})}); + Obj.insert({"Location", extractValue(L)}); } return Obj; } @@ -229,7 +263,6 @@ Value extractValue(const EnumInfo &I, const ClangDocContext &CDCtx) { 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(); @@ -256,75 +289,175 @@ Value extractValue(const EnumInfo &I, const ClangDocContext &CDCtx) { Obj.insert({"EnumComments", ArrDesc}); } - if (I.DefLoc) { - if (!CDCtx.RepositoryUrl) - Obj.insert({"Location", extractValue(*I.DefLoc)}); + 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(*I.DefLoc, - StringRef{*CDCtx.RepositoryUrl})}); + Obj.insert({"Location", extractValue(L)}); } return Obj; } -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 }); - } - +void extractScopeChildren(const ScopeChildren &S, Object &Obj, + StringRef ParentInfoDir, + const ClangDocContext &CDCtx) { Value ArrNamespace = Array(); - for (const Reference& Child : I.Children.Namespaces) - ArrNamespace.getAsArray()->emplace_back(extractValue(Child, BasePath)); + for (const Reference& Child : S.Namespaces) + ArrNamespace.getAsArray()->emplace_back(extractValue(Child, ParentInfoDir)); if (!ArrNamespace.getAsArray()->empty()) - NamespaceValue.insert({"Namespace", Object{{"Links", ArrNamespace}}}); + Obj.insert({"Namespace", Object{{"Links", ArrNamespace}}}); Value ArrRecord = Array(); - for (const Reference& Child : I.Children.Records) - ArrRecord.getAsArray()->emplace_back(extractValue(Child, BasePath)); + for (const Reference& Child : S.Records) + ArrRecord.getAsArray()->emplace_back(extractValue(Child, ParentInfoDir)); if (!ArrRecord.getAsArray()->empty()) - NamespaceValue.insert({"Record", Object{{"Links", ArrRecord}}}); + Obj.insert({"Record", Object{{"Links", ArrRecord}}}); Value ArrFunction = Array(); - for (const FunctionInfo& Child : I.Children.Functions) - ArrFunction.getAsArray()->emplace_back(extractValue(Child, BasePath, - CDCtx)); + 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()) - NamespaceValue.insert({"Function", Object{{"Obj", ArrFunction}}}); + 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 : I.Children.Enums) + for (const EnumInfo& Child : S.Enums) ArrEnum.getAsArray()->emplace_back(extractValue(Child, CDCtx)); if (!ArrEnum.getAsArray()->empty()) - NamespaceValue.insert({"Enums", Object{{"Obj", ArrEnum }}}); + Obj.insert({"Enums", Object{{"Obj", ArrEnum }}}); Value ArrTypedefs = Array(); - for (const TypedefInfo& Child : I.Children.Typedefs) + for (const TypedefInfo& Child : S.Typedefs) ArrTypedefs.getAsArray()->emplace_back(extractValue(Child)); if (!ArrTypedefs.getAsArray()->empty()) - NamespaceValue.insert({"Typedefs", Object{{"Obj", ArrTypedefs }}}); + 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, @@ -332,13 +465,18 @@ MustacheHTMLGenerator::generateDocForInfo(Info *I, llvm::raw_ostream &OS, switch (I->IT) { case InfoType::IT_namespace: { Value V = extractValue(*static_cast(I), CDCtx); - llvm::raw_ostream &OS = llvm::outs(); - llvm::json::OStream J(OS, /*IndentSize=*/2); - J.value(V); + setupTemplateValue(CDCtx, V, I); + OS << NamespaceTemplate->render(V); break; } - case InfoType::IT_record: + 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() << llvm::formatv("{0:2}", V); + OS << RecordTemplate->render(V); break; + } case InfoType::IT_enum: llvm::outs() << "IT_enum\n"; break; @@ -356,6 +494,17 @@ MustacheHTMLGenerator::generateDocForInfo(Info *I, llvm::raw_ostream &OS, } 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(); } 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..2f07baa7ca0a1 --- /dev/null +++ b/clang-tools-extra/clang-doc/assets/clang-doc-mustache.css @@ -0,0 +1,476 @@ +@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 +} + + +.links-wrapper { + display:grid; + gap:1rem; + grid-template-columns:1fr 1fr 1fr 1fr +} +@media(max-width:768px) { + .links-wrapper { + grid-template-columns:1fr 1fr + } +} +@media(max-width:480px) { + .links-wrapper { + grid-template-columns:1fr + } +} +.links-wrapper .link { + display:flex; + flex-direction:column; + align-items:center; + padding:1rem; + border:1px solid var(--text1); + border-radius:.25rem +} +.links-wrapper .link__icon { + font-size:2rem +} +.links-wrapper .link__title { + font-size:1rem +} + +.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/comments.mustache b/clang-tools-extra/clang-doc/assets/comments.mustache deleted file mode 100644 index 1eac4de91836a..0000000000000 --- a/clang-tools-extra/clang-doc/assets/comments.mustache +++ /dev/null @@ -1,27 +0,0 @@ -{{! - 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 -}} - -{{#FullComments}} - {{#Children}} - {{>Comment}} - {{/Children}} -{{/FullComments}} -{{#ParagraphComment}} - {{#Children}} - {{>Comment}} - {{/Children}} -{{/ParagraphComment}} -{{#BlockCommandComment}} -
{{Command}}
- {{#Children}} - {{>Comment}} - {{/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..4061fd026886e --- /dev/null +++ b/clang-tools-extra/clang-doc/assets/namespace-template.mustache @@ -0,0 +1,87 @@ +{{! + 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 deleted file mode 100644 index 3b77e0189f4e2..0000000000000 --- a/clang-tools-extra/clang-doc/assets/template.mustache +++ /dev/null @@ -1,55 +0,0 @@ -{{! - 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}} -
- {{>Comments}} -
- {{/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 8eb067dbe6de8..eccbc99a7ecc4 100644 --- a/clang-tools-extra/clang-doc/tool/CMakeLists.txt +++ b/clang-tools-extra/clang-doc/tool/CMakeLists.txt @@ -21,7 +21,13 @@ target_link_libraries(clang-doc set(assets index.js - template.mustache + 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 6f743c6603d46..0bde27eb2a75e 100644 --- a/clang-tools-extra/clang-doc/tool/ClangDocMain.cpp +++ b/clang-tools-extra/clang-doc/tool/ClangDocMain.cpp @@ -163,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; @@ -173,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(), @@ -210,6 +216,7 @@ llvm::Error getHtmlAssetFiles(const char *Argv0, return getDefaultAssetFiles(Argv0, CDCtx); } + llvm::Error getMustacheHtmlFiles(const char *Argv0, clang::doc::ClangDocContext &CDCtx) { if (!UserAssetPath.empty() && @@ -228,10 +235,34 @@ llvm::Error getMustacheHtmlFiles(const char *Argv0, AssetsPath = llvm::sys::path::parent_path(NativeClangDocPath); llvm::sys::path::append(AssetsPath, "..", "share", "clang-doc"); - llvm::SmallString<128> MustacheTemplate; - llvm::sys::path::native(AssetsPath, MustacheTemplate); - llvm::sys::path::append(MustacheTemplate, "template.mustache"); - CDCtx.MustacheTemplates.insert({"template", MustacheTemplate.c_str()}); + 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(); } From 1cf5eb0f77dad76905f780f286dec28f3883070a Mon Sep 17 00:00:00 2001 From: PeterChou1 Date: Fri, 13 Sep 2024 02:26:22 -0400 Subject: [PATCH 18/24] [clang-doc] init mustache implementation --- clang-tools-extra/clang-doc/CMakeLists.txt | 5 +- clang-tools-extra/clang-doc/HTMLGenerator.cpp | 59 +++- .../clang-doc/HTMLMustacheGenerator.cpp | 289 ++++-------------- .../clang-doc/assets/template.mustache | 23 ++ .../clang-doc/tool/CMakeLists.txt | 8 +- .../clang-doc/tool/ClangDocMain.cpp | 62 +--- 6 files changed, 154 insertions(+), 292 deletions(-) create mode 100644 clang-tools-extra/clang-doc/assets/template.mustache diff --git a/clang-tools-extra/clang-doc/CMakeLists.txt b/clang-tools-extra/clang-doc/CMakeLists.txt index cc07742bbb420..7e5055e6b58b9 100644 --- a/clang-tools-extra/clang-doc/CMakeLists.txt +++ b/clang-tools-extra/clang-doc/CMakeLists.txt @@ -8,16 +8,15 @@ add_clang_library(clangDoc STATIC BitcodeReader.cpp BitcodeWriter.cpp ClangDoc.cpp - FileHelpersClangDoc.cpp Generators.cpp HTMLGenerator.cpp - HTMLMustacheGenerator.cpp Mapper.cpp MDGenerator.cpp Representation.cpp Serialize.cpp YAMLGenerator.cpp - + HTMLMustacheGenerator.cpp + DEPENDS omp_gen ClangDriverOptions diff --git a/clang-tools-extra/clang-doc/HTMLGenerator.cpp b/clang-tools-extra/clang-doc/HTMLGenerator.cpp index a4a763100eb97..6b0efc9d4f37c 100644 --- a/clang-tools-extra/clang-doc/HTMLGenerator.cpp +++ b/clang-tools-extra/clang-doc/HTMLGenerator.cpp @@ -8,7 +8,6 @@ #include "Generators.h" #include "Representation.h" -#include "FileHelpersClangDoc.h" #include "clang/Basic/Version.h" #include "llvm/ADT/StringExtras.h" #include "llvm/ADT/StringRef.h" @@ -252,6 +251,46 @@ static void appendVector(std::vector &&New, std::move(New.begin(), New.end(), std::back_inserter(Original)); } +// Compute the relative path from an Origin directory to a Destination directory +static SmallString<128> computeRelativePath(StringRef Destination, + 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; + } + 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; +} // HTML generation @@ -1107,6 +1146,24 @@ static llvm::Error genIndex(const ClangDocContext &CDCtx) { return llvm::Error::success(); } +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)); + 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::Error HTMLGenerator::createResources(ClangDocContext &CDCtx) { auto Err = serializeIndex(CDCtx); if (Err) diff --git a/clang-tools-extra/clang-doc/HTMLMustacheGenerator.cpp b/clang-tools-extra/clang-doc/HTMLMustacheGenerator.cpp index 6be4c795a6865..63def07c5fa80 100644 --- a/clang-tools-extra/clang-doc/HTMLMustacheGenerator.cpp +++ b/clang-tools-extra/clang-doc/HTMLMustacheGenerator.cpp @@ -7,7 +7,6 @@ //===----------------------------------------------------------------------===// #include "Generators.h" #include "Representation.h" -#include "FileHelpersClangDoc.h" #include "llvm/Support/MemoryBuffer.h" #include "llvm/Support/Mustache.h" @@ -49,12 +48,12 @@ class MustacheTemplateFile : public Template { Error registerPartialFile(StringRef Name, StringRef FileName) { ErrorOr> BufferOrError = MemoryBuffer::getFile(FileName); - if (auto EC = BufferOrError.getError()) + 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) {} @@ -65,54 +64,23 @@ 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(); + auto TemplateFilePath = CDCtx.MustacheTemplates.lookup("template"); + auto Template = MustacheTemplateFile::createMustacheFile(TemplateFilePath); + if (auto EC = Template.getError()) { + return llvm::createFileError("cannot open file", EC); + } + NamespaceTemplate = std::move(Template.get()); } - llvm::Error MustacheHTMLGenerator::generateDocs(llvm::StringRef RootDir, llvm::StringMap> Infos, const clang::doc::ClangDocContext &CDCtx) { - if (auto Err = setupTemplateFiles(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. @@ -125,9 +93,10 @@ MustacheHTMLGenerator::generateDocs(llvm::StringRef RootDir, 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()) + Err != std::error_code()) { return llvm::createStringError(Err, "Failed to create directory '%s'.", Path.c_str()); + } CreatedDirs.insert(Path); } @@ -139,33 +108,28 @@ MustacheHTMLGenerator::generateDocs(llvm::StringRef RootDir, std::error_code FileErr; llvm::raw_fd_ostream InfoOS(Group.getKey(), FileErr, llvm::sys::fs::OF_None); - if (FileErr) + 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)) + 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; + Obj.insert({"LineNumber", L.LineNumber}); + Obj.insert({"Filename", L.Filename}); } 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) { @@ -175,8 +139,6 @@ Value extractValue(const Reference &I, StringRef CurrentDirectory) { 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; } @@ -227,12 +189,8 @@ Value extractValue(const FunctionInfo &I, StringRef ParentInfoDir, 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); + for (const auto &P : I.Params) { + ParamArr.getAsArray()->emplace_back(extractValue(P.Type, ParentInfoDir)); } Obj.insert({"Params", ParamArr}); @@ -242,13 +200,12 @@ Value extractValue(const FunctionInfo &I, StringRef ParentInfoDir, 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})}); + if (I.DefLoc) { + if (!CDCtx.RepositoryUrl) + Obj.insert({"Location", extractValue(*I.DefLoc)}); else - Obj.insert({"Location", extractValue(L)}); + Obj.insert({"Location", extractValue(*I.DefLoc, + StringRef{*CDCtx.RepositoryUrl})}); } return Obj; } @@ -263,6 +220,7 @@ Value extractValue(const EnumInfo &I, const ClangDocContext &CDCtx) { 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(); @@ -289,75 +247,17 @@ Value extractValue(const EnumInfo &I, const ClangDocContext &CDCtx) { 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})}); + if (I.DefLoc) { + if (!CDCtx.RepositoryUrl) + Obj.insert({"Location", extractValue(*I.DefLoc)}); else - Obj.insert({"Location", extractValue(L)}); + Obj.insert({"Location", extractValue(*I.DefLoc, + StringRef{*CDCtx.RepositoryUrl})}); } 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; @@ -376,88 +276,36 @@ Value extractValue(const NamespaceInfo &I, const ClangDocContext &CDCtx) { 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(); + Value ArrNamespace = Array(); + for (const Reference& Child : I.Children.Namespaces) + ArrNamespace.getAsArray()->emplace_back(extractValue(Child, BasePath)); + NamespaceValue.insert({"Namespace", ArrNamespace}); - 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)}); + Value ArrRecord = Array(); + for (const Reference& Child : I.Children.Records) + ArrRecord.getAsArray()->emplace_back(extractValue(Child, BasePath)); + NamespaceValue.insert({"Record", ArrRecord}); - 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)}); - } + Value ArrFunction = Array(); + for (const FunctionInfo& Child : I.Children.Functions) + ArrFunction.getAsArray()->emplace_back(extractValue(Child, BasePath, + CDCtx)); + NamespaceValue.insert({"Function", ArrRecord}); - 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}}}); + Value ArrEnum = Array(); + for (const EnumInfo& Child : I.Children.Enums) + ArrEnum.getAsArray()->emplace_back(extractValue(Child, CDCtx)); + NamespaceValue.insert({"Enums", ArrEnum }); - 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 ArrTypedefs = Array(); + for (const TypedefInfo& Child : I.Children.Typedefs) + ArrTypedefs.getAsArray()->emplace_back(extractValue(Child)); + NamespaceValue.insert({"Typedefs", ArrTypedefs }); - 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, @@ -465,26 +313,16 @@ MustacheHTMLGenerator::generateDocForInfo(Info *I, llvm::raw_ostream &OS, 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() << llvm::formatv("{0:2}", V); - OS << RecordTemplate->render(V); + case InfoType::IT_record: 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(), @@ -494,21 +332,10 @@ MustacheHTMLGenerator::generateDocForInfo(Info *I, llvm::raw_ostream &OS, } 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"; +const char *MustacheHTMLGenerator::Format = "mustache"; static GeneratorRegistry::Add MHTML(MustacheHTMLGenerator::Format, @@ -516,7 +343,7 @@ static GeneratorRegistry::Add MHTML(MustacheHTMLGenerator // This anchor is used to force the linker to link in the generated object // file and thus register the generator. -volatile int MHTMLGeneratorAnchorSource = 0; +volatile int HTMLGeneratorAnchorSource = 0; } // namespace doc } // namespace clang \ 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..af4c60182ae52 --- /dev/null +++ b/clang-tools-extra/clang-doc/assets/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 +}} + + + + + {{NamespaceTitle}} + + {{#NamespaceComments}} +

Namespace Comment Present!

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

Namespace Present!

+ {{/Namespace}} + {{#Record}} +

Record Present!

+ {{/Record}} + \ 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 eccbc99a7ecc4..8eb067dbe6de8 100644 --- a/clang-tools-extra/clang-doc/tool/CMakeLists.txt +++ b/clang-tools-extra/clang-doc/tool/CMakeLists.txt @@ -21,13 +21,7 @@ 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 + template.mustache 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 0bde27eb2a75e..1897db3e7549f 100644 --- a/clang-tools-extra/clang-doc/tool/ClangDocMain.cpp +++ b/clang-tools-extra/clang-doc/tool/ClangDocMain.cpp @@ -103,7 +103,6 @@ enum OutputFormatTy { md, yaml, html, - mhtml }; static llvm::cl::opt @@ -113,9 +112,7 @@ static llvm::cl::opt clEnumValN(OutputFormatTy::md, "md", "Documentation in MD format."), clEnumValN(OutputFormatTy::html, "html", - "Documentation in HTML format."), - clEnumValN(OutputFormatTy::mhtml, "mhtml", - "Documentation in mHTML format")), + "Documentation in HTML format.")), llvm::cl::init(OutputFormatTy::yaml), llvm::cl::cat(ClangDocCategory)); @@ -127,8 +124,6 @@ std::string getFormatString() { return "md"; case OutputFormatTy::html: return "html"; - case OutputFormatTy::mhtml: - return "mhtml"; } llvm_unreachable("Unknown OutputFormatTy"); } @@ -163,15 +158,6 @@ 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; @@ -182,10 +168,13 @@ 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 = - appendPathNative(AssetsPath, "clang-doc-default-stylesheet.css"); - llvm::SmallString<128> IndexJS = - appendPathNative(AssetsPath, "index.js"); + 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"); if (!llvm::sys::fs::is_regular_file(IndexJS)) return llvm::createStringError(llvm::inconvertibleErrorCode(), @@ -216,7 +205,6 @@ llvm::Error getHtmlAssetFiles(const char *Argv0, return getDefaultAssetFiles(Argv0, CDCtx); } - llvm::Error getMustacheHtmlFiles(const char *Argv0, clang::doc::ClangDocContext &CDCtx) { if (!UserAssetPath.empty() && @@ -235,36 +223,10 @@ llvm::Error getMustacheHtmlFiles(const char *Argv0, 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(); + llvm::SmallString<128> MustacheTemplate; + llvm::sys::path::native(AssetsPath, MustacheTemplate); + llvm::sys::path::append(MustacheTemplate, "template.mustache"); + CDCtx.MustacheTemplates.insert({"template", MustacheTemplate.c_str()}); } /// Make the output of clang-doc deterministic by sorting the children of From 8ba6a5d2647617131f197bc8fe2e1f4fa0397726 Mon Sep 17 00:00:00 2001 From: PeterChou1 Date: Fri, 13 Sep 2024 17:50:03 -0400 Subject: [PATCH 19/24] [clang-doc] add a mustache backend init implementation --- .../clang-doc/HTMLMustacheGenerator.cpp | 32 ++++++++++++---- .../clang-doc/assets/template.mustache | 37 +++++++++++++++++-- .../clang-doc/tool/ClangDocMain.cpp | 9 ++++- .../Inputs/basic-project/include/Shape.h | 2 - 4 files changed, 65 insertions(+), 15 deletions(-) diff --git a/clang-tools-extra/clang-doc/HTMLMustacheGenerator.cpp b/clang-tools-extra/clang-doc/HTMLMustacheGenerator.cpp index 63def07c5fa80..bfa563ebdc7b3 100644 --- a/clang-tools-extra/clang-doc/HTMLMustacheGenerator.cpp +++ b/clang-tools-extra/clang-doc/HTMLMustacheGenerator.cpp @@ -54,6 +54,7 @@ class MustacheTemplateFile : public Template { 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) {} @@ -72,6 +73,7 @@ setupTemplateFiles(const clang::doc::ClangDocContext &CDCtx) { return llvm::createFileError("cannot open file", EC); } NamespaceTemplate = std::move(Template.get()); + return llvm::Error::success(); } llvm::Error @@ -118,6 +120,7 @@ MustacheHTMLGenerator::generateDocs(llvm::StringRef RootDir, } } } + return llvm::Error::success(); } Value extractValue(const Location &L, @@ -130,6 +133,8 @@ Value extractValue(const Location &L, SmallString<128> FileURL(*RepositoryUrl); llvm::sys::path::append(FileURL, llvm::sys::path::Style::posix, L.Filename); Obj.insert({"FileURL", FileURL}); + + return Obj; } Value extractValue(const Reference &I, StringRef CurrentDirectory) { @@ -280,29 +285,39 @@ Value extractValue(const NamespaceInfo &I, const ClangDocContext &CDCtx) { Value ArrNamespace = Array(); for (const Reference& Child : I.Children.Namespaces) ArrNamespace.getAsArray()->emplace_back(extractValue(Child, BasePath)); - NamespaceValue.insert({"Namespace", ArrNamespace}); + + if (!ArrNamespace.getAsArray()->empty()) + NamespaceValue.insert({"Namespace", Object{{"Links", ArrNamespace}}}); Value ArrRecord = Array(); for (const Reference& Child : I.Children.Records) ArrRecord.getAsArray()->emplace_back(extractValue(Child, BasePath)); - NamespaceValue.insert({"Record", ArrRecord}); + + if (!ArrRecord.getAsArray()->empty()) + NamespaceValue.insert({"Record", Object{{"Links", ArrRecord}}}); Value ArrFunction = Array(); for (const FunctionInfo& Child : I.Children.Functions) ArrFunction.getAsArray()->emplace_back(extractValue(Child, BasePath, CDCtx)); - NamespaceValue.insert({"Function", ArrRecord}); + if (!ArrFunction.getAsArray()->empty()) + NamespaceValue.insert({"Function", Object{{"Obj", ArrFunction}}}); Value ArrEnum = Array(); for (const EnumInfo& Child : I.Children.Enums) ArrEnum.getAsArray()->emplace_back(extractValue(Child, CDCtx)); - NamespaceValue.insert({"Enums", ArrEnum }); + + if (!ArrEnum.getAsArray()->empty()) + NamespaceValue.insert({"Enums", Object{{"Obj", ArrEnum }}}); Value ArrTypedefs = Array(); for (const TypedefInfo& Child : I.Children.Typedefs) ArrTypedefs.getAsArray()->emplace_back(extractValue(Child)); - NamespaceValue.insert({"Typedefs", ArrTypedefs }); + if (!ArrTypedefs.getAsArray()->empty()) + NamespaceValue.insert({"Typedefs", Object{{"Obj", ArrTypedefs }}}); + + return NamespaceValue; } @@ -313,6 +328,7 @@ MustacheHTMLGenerator::generateDocForInfo(Info *I, llvm::raw_ostream &OS, switch (I->IT) { case InfoType::IT_namespace: { Value V = extractValue(*static_cast(I), CDCtx); + llvm::outs() << V << "\n"; OS << NamespaceTemplate->render(V); break; } @@ -332,10 +348,10 @@ MustacheHTMLGenerator::generateDocForInfo(Info *I, llvm::raw_ostream &OS, } llvm::Error MustacheHTMLGenerator::createResources(ClangDocContext &CDCtx) { - + return llvm::Error::success(); } -const char *MustacheHTMLGenerator::Format = "mustache"; +const char *MustacheHTMLGenerator::Format = "mhtml"; static GeneratorRegistry::Add MHTML(MustacheHTMLGenerator::Format, @@ -343,7 +359,7 @@ static GeneratorRegistry::Add MHTML(MustacheHTMLGenerator // This anchor is used to force the linker to link in the generated object // file and thus register the generator. -volatile int HTMLGeneratorAnchorSource = 0; +volatile int MHTMLGeneratorAnchorSource = 0; } // namespace doc } // namespace clang \ 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 index af4c60182ae52..1d3407f8b5292 100644 --- a/clang-tools-extra/clang-doc/assets/template.mustache +++ b/clang-tools-extra/clang-doc/assets/template.mustache @@ -3,7 +3,7 @@ See https://llvm.org/LICENSE.txt for license information. SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception - This file defines the template + This file defines the template for generating Namespaces }} @@ -11,13 +11,42 @@ {{NamespaceTitle}} +

{{NamespaceTitle}}

{{#NamespaceComments}} -

Namespace Comment Present!

+

Namespace Comment

{{/NamespaceComments}} {{#Namespace}} -

Namespace Present!

+

Namespace

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

Record Present!

+

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/ClangDocMain.cpp b/clang-tools-extra/clang-doc/tool/ClangDocMain.cpp index 1897db3e7549f..6f743c6603d46 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"); } @@ -227,6 +232,8 @@ llvm::Error getMustacheHtmlFiles(const char *Argv0, llvm::sys::path::native(AssetsPath, MustacheTemplate); llvm::sys::path::append(MustacheTemplate, "template.mustache"); CDCtx.MustacheTemplates.insert({"template", MustacheTemplate.c_str()}); + + return llvm::Error::success(); } /// Make the output of clang-doc deterministic by sorting the children of 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; }; - - From 79ea4bc44c7d2d14a9f3ddb9ac2bc7374df3296b Mon Sep 17 00:00:00 2001 From: PeterChou1 Date: Tue, 24 Sep 2024 16:48:29 -0400 Subject: [PATCH 20/24] [clang-doc] update --- .../clang-doc/HTMLMustacheGenerator.cpp | 16 ++++++++--- .../clang-doc/assets/comments.mustache | 27 +++++++++++++++++++ .../clang-doc/assets/template.mustache | 7 +++-- .../Inputs/basic-project/include/Shape.h | 2 ++ 4 files changed, 46 insertions(+), 6 deletions(-) create mode 100644 clang-tools-extra/clang-doc/assets/comments.mustache diff --git a/clang-tools-extra/clang-doc/HTMLMustacheGenerator.cpp b/clang-tools-extra/clang-doc/HTMLMustacheGenerator.cpp index bfa563ebdc7b3..96a685dbe2ae8 100644 --- a/clang-tools-extra/clang-doc/HTMLMustacheGenerator.cpp +++ b/clang-tools-extra/clang-doc/HTMLMustacheGenerator.cpp @@ -194,8 +194,12 @@ Value extractValue(const FunctionInfo &I, StringRef ParentInfoDir, Obj.insert({"ReturnType", extractValue(I.ReturnType.Type, ParentInfoDir)}); Value ParamArr = Array(); - for (const auto &P : I.Params) { - ParamArr.getAsArray()->emplace_back(extractValue(P.Type, ParentInfoDir)); + 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}); @@ -328,17 +332,21 @@ MustacheHTMLGenerator::generateDocForInfo(Info *I, llvm::raw_ostream &OS, switch (I->IT) { case InfoType::IT_namespace: { Value V = extractValue(*static_cast(I), CDCtx); - llvm::outs() << V << "\n"; - OS << NamespaceTemplate->render(V); + llvm::raw_ostream &OS = llvm::outs(); + llvm::json::OStream J(OS, /*IndentSize=*/2); + J.value(V); break; } case InfoType::IT_record: 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(), diff --git a/clang-tools-extra/clang-doc/assets/comments.mustache b/clang-tools-extra/clang-doc/assets/comments.mustache new file mode 100644 index 0000000000000..1eac4de91836a --- /dev/null +++ b/clang-tools-extra/clang-doc/assets/comments.mustache @@ -0,0 +1,27 @@ +{{! + 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 +}} + +{{#FullComments}} + {{#Children}} + {{>Comment}} + {{/Children}} +{{/FullComments}} +{{#ParagraphComment}} + {{#Children}} + {{>Comment}} + {{/Children}} +{{/ParagraphComment}} +{{#BlockCommandComment}} +
{{Command}}
+ {{#Children}} + {{>Comment}} + {{/Children}} +{{/BlockCommandComment}} +{{#TextComment}} +

{{TextComment}}

+{{/TextComment}} \ 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 index 1d3407f8b5292..3b77e0189f4e2 100644 --- a/clang-tools-extra/clang-doc/assets/template.mustache +++ b/clang-tools-extra/clang-doc/assets/template.mustache @@ -3,7 +3,7 @@ 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 + This file defines the template for generating namespaces }} @@ -13,7 +13,9 @@

{{NamespaceTitle}}

{{#NamespaceComments}} -

Namespace Comment

+
+ {{>Comments}} +
{{/NamespaceComments}} {{#Namespace}}

Namespace

@@ -39,6 +41,7 @@

Function

{{#Obj}} + {{/Obj}}
{{/Function}} 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 5354032f4d832..e5c5d4c9e4412 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,3 +26,5 @@ class Shape { */ virtual double perimeter() const = 0; }; + + From 6010fb2a19b88ca692bb029ce7e975fb54704ef7 Mon Sep 17 00:00:00 2001 From: PeterChou1 Date: Mon, 7 Oct 2024 15:07:41 -0400 Subject: [PATCH 21/24] [clang-doc] add more templates --- clang-tools-extra/clang-doc/CMakeLists.txt | 5 +- clang-tools-extra/clang-doc/HTMLGenerator.cpp | 59 +--- .../clang-doc/HTMLMustacheGenerator.cpp | 281 ++++++++++++++---- .../clang-doc/assets/comments.mustache | 27 -- .../clang-doc/assets/template.mustache | 55 ---- .../clang-doc/tool/CMakeLists.txt | 8 +- .../clang-doc/tool/ClangDocMain.cpp | 53 +++- 7 files changed, 268 insertions(+), 220 deletions(-) delete mode 100644 clang-tools-extra/clang-doc/assets/comments.mustache delete mode 100644 clang-tools-extra/clang-doc/assets/template.mustache diff --git a/clang-tools-extra/clang-doc/CMakeLists.txt b/clang-tools-extra/clang-doc/CMakeLists.txt index 7e5055e6b58b9..cc07742bbb420 100644 --- a/clang-tools-extra/clang-doc/CMakeLists.txt +++ b/clang-tools-extra/clang-doc/CMakeLists.txt @@ -8,15 +8,16 @@ add_clang_library(clangDoc STATIC BitcodeReader.cpp BitcodeWriter.cpp ClangDoc.cpp + FileHelpersClangDoc.cpp Generators.cpp HTMLGenerator.cpp + HTMLMustacheGenerator.cpp Mapper.cpp MDGenerator.cpp Representation.cpp Serialize.cpp YAMLGenerator.cpp - HTMLMustacheGenerator.cpp - + DEPENDS omp_gen ClangDriverOptions diff --git a/clang-tools-extra/clang-doc/HTMLGenerator.cpp b/clang-tools-extra/clang-doc/HTMLGenerator.cpp index 6b0efc9d4f37c..a4a763100eb97 100644 --- a/clang-tools-extra/clang-doc/HTMLGenerator.cpp +++ b/clang-tools-extra/clang-doc/HTMLGenerator.cpp @@ -8,6 +8,7 @@ #include "Generators.h" #include "Representation.h" +#include "FileHelpersClangDoc.h" #include "clang/Basic/Version.h" #include "llvm/ADT/StringExtras.h" #include "llvm/ADT/StringRef.h" @@ -251,46 +252,6 @@ static void appendVector(std::vector &&New, std::move(New.begin(), New.end(), std::back_inserter(Original)); } -// Compute the relative path from an Origin directory to a Destination directory -static SmallString<128> computeRelativePath(StringRef Destination, - 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; - } - 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; -} // HTML generation @@ -1146,24 +1107,6 @@ static llvm::Error genIndex(const ClangDocContext &CDCtx) { return llvm::Error::success(); } -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)); - 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::Error HTMLGenerator::createResources(ClangDocContext &CDCtx) { auto Err = serializeIndex(CDCtx); if (Err) diff --git a/clang-tools-extra/clang-doc/HTMLMustacheGenerator.cpp b/clang-tools-extra/clang-doc/HTMLMustacheGenerator.cpp index 96a685dbe2ae8..6be4c795a6865 100644 --- a/clang-tools-extra/clang-doc/HTMLMustacheGenerator.cpp +++ b/clang-tools-extra/clang-doc/HTMLMustacheGenerator.cpp @@ -7,6 +7,7 @@ //===----------------------------------------------------------------------===// #include "Generators.h" #include "Representation.h" +#include "FileHelpersClangDoc.h" #include "llvm/Support/MemoryBuffer.h" #include "llvm/Support/Mustache.h" @@ -48,9 +49,8 @@ class MustacheTemplateFile : public Template { Error registerPartialFile(StringRef Name, StringRef FileName) { ErrorOr> BufferOrError = MemoryBuffer::getFile(FileName); - if (auto EC = BufferOrError.getError()) { + 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); @@ -65,24 +65,54 @@ 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 TemplateFilePath = CDCtx.MustacheTemplates.lookup("template"); - auto Template = MustacheTemplateFile::createMustacheFile(TemplateFilePath); - if (auto EC = Template.getError()) { - return llvm::createFileError("cannot open file", EC); - } - NamespaceTemplate = std::move(Template.get()); + 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)) { + 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. @@ -95,10 +125,9 @@ MustacheHTMLGenerator::generateDocs(llvm::StringRef RootDir, 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()) { + Err != std::error_code()) return llvm::createStringError(Err, "Failed to create directory '%s'.", Path.c_str()); - } CreatedDirs.insert(Path); } @@ -110,14 +139,13 @@ MustacheHTMLGenerator::generateDocs(llvm::StringRef RootDir, std::error_code FileErr; llvm::raw_fd_ostream InfoOS(Group.getKey(), FileErr, llvm::sys::fs::OF_None); - if (FileErr) { + 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)) { + if (llvm::Error Err = generateDocForInfo(Info, InfoOS, CDCtx)) return Err; - } } } return llvm::Error::success(); @@ -126,12 +154,15 @@ MustacheHTMLGenerator::generateDocs(llvm::StringRef RootDir, 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) { - Obj.insert({"LineNumber", L.LineNumber}); - Obj.insert({"Filename", L.Filename}); + 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; @@ -144,6 +175,8 @@ Value extractValue(const Reference &I, StringRef CurrentDirectory) { 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; } @@ -209,12 +242,13 @@ Value extractValue(const FunctionInfo &I, StringRef ParentInfoDir, ArrDesc.getAsArray()->emplace_back(extractValue(Child)); Obj.insert({"FunctionComments", ArrDesc}); } - if (I.DefLoc) { - if (!CDCtx.RepositoryUrl) - Obj.insert({"Location", extractValue(*I.DefLoc)}); + 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(*I.DefLoc, - StringRef{*CDCtx.RepositoryUrl})}); + Obj.insert({"Location", extractValue(L)}); } return Obj; } @@ -229,7 +263,6 @@ Value extractValue(const EnumInfo &I, const ClangDocContext &CDCtx) { 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(); @@ -256,75 +289,175 @@ Value extractValue(const EnumInfo &I, const ClangDocContext &CDCtx) { Obj.insert({"EnumComments", ArrDesc}); } - if (I.DefLoc) { - if (!CDCtx.RepositoryUrl) - Obj.insert({"Location", extractValue(*I.DefLoc)}); + 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(*I.DefLoc, - StringRef{*CDCtx.RepositoryUrl})}); + Obj.insert({"Location", extractValue(L)}); } return Obj; } -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 }); - } - +void extractScopeChildren(const ScopeChildren &S, Object &Obj, + StringRef ParentInfoDir, + const ClangDocContext &CDCtx) { Value ArrNamespace = Array(); - for (const Reference& Child : I.Children.Namespaces) - ArrNamespace.getAsArray()->emplace_back(extractValue(Child, BasePath)); + for (const Reference& Child : S.Namespaces) + ArrNamespace.getAsArray()->emplace_back(extractValue(Child, ParentInfoDir)); if (!ArrNamespace.getAsArray()->empty()) - NamespaceValue.insert({"Namespace", Object{{"Links", ArrNamespace}}}); + Obj.insert({"Namespace", Object{{"Links", ArrNamespace}}}); Value ArrRecord = Array(); - for (const Reference& Child : I.Children.Records) - ArrRecord.getAsArray()->emplace_back(extractValue(Child, BasePath)); + for (const Reference& Child : S.Records) + ArrRecord.getAsArray()->emplace_back(extractValue(Child, ParentInfoDir)); if (!ArrRecord.getAsArray()->empty()) - NamespaceValue.insert({"Record", Object{{"Links", ArrRecord}}}); + Obj.insert({"Record", Object{{"Links", ArrRecord}}}); Value ArrFunction = Array(); - for (const FunctionInfo& Child : I.Children.Functions) - ArrFunction.getAsArray()->emplace_back(extractValue(Child, BasePath, - CDCtx)); + 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()) - NamespaceValue.insert({"Function", Object{{"Obj", ArrFunction}}}); + 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 : I.Children.Enums) + for (const EnumInfo& Child : S.Enums) ArrEnum.getAsArray()->emplace_back(extractValue(Child, CDCtx)); if (!ArrEnum.getAsArray()->empty()) - NamespaceValue.insert({"Enums", Object{{"Obj", ArrEnum }}}); + Obj.insert({"Enums", Object{{"Obj", ArrEnum }}}); Value ArrTypedefs = Array(); - for (const TypedefInfo& Child : I.Children.Typedefs) + for (const TypedefInfo& Child : S.Typedefs) ArrTypedefs.getAsArray()->emplace_back(extractValue(Child)); if (!ArrTypedefs.getAsArray()->empty()) - NamespaceValue.insert({"Typedefs", Object{{"Obj", ArrTypedefs }}}); + 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, @@ -332,13 +465,18 @@ MustacheHTMLGenerator::generateDocForInfo(Info *I, llvm::raw_ostream &OS, switch (I->IT) { case InfoType::IT_namespace: { Value V = extractValue(*static_cast(I), CDCtx); - llvm::raw_ostream &OS = llvm::outs(); - llvm::json::OStream J(OS, /*IndentSize=*/2); - J.value(V); + setupTemplateValue(CDCtx, V, I); + OS << NamespaceTemplate->render(V); break; } - case InfoType::IT_record: + 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() << llvm::formatv("{0:2}", V); + OS << RecordTemplate->render(V); break; + } case InfoType::IT_enum: llvm::outs() << "IT_enum\n"; break; @@ -356,6 +494,17 @@ MustacheHTMLGenerator::generateDocForInfo(Info *I, llvm::raw_ostream &OS, } 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(); } diff --git a/clang-tools-extra/clang-doc/assets/comments.mustache b/clang-tools-extra/clang-doc/assets/comments.mustache deleted file mode 100644 index 1eac4de91836a..0000000000000 --- a/clang-tools-extra/clang-doc/assets/comments.mustache +++ /dev/null @@ -1,27 +0,0 @@ -{{! - 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 -}} - -{{#FullComments}} - {{#Children}} - {{>Comment}} - {{/Children}} -{{/FullComments}} -{{#ParagraphComment}} - {{#Children}} - {{>Comment}} - {{/Children}} -{{/ParagraphComment}} -{{#BlockCommandComment}} -
{{Command}}
- {{#Children}} - {{>Comment}} - {{/Children}} -{{/BlockCommandComment}} -{{#TextComment}} -

{{TextComment}}

-{{/TextComment}} \ 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 deleted file mode 100644 index 3b77e0189f4e2..0000000000000 --- a/clang-tools-extra/clang-doc/assets/template.mustache +++ /dev/null @@ -1,55 +0,0 @@ -{{! - 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}} -
- {{>Comments}} -
- {{/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 8eb067dbe6de8..eccbc99a7ecc4 100644 --- a/clang-tools-extra/clang-doc/tool/CMakeLists.txt +++ b/clang-tools-extra/clang-doc/tool/CMakeLists.txt @@ -21,7 +21,13 @@ target_link_libraries(clang-doc set(assets index.js - template.mustache + 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 6f743c6603d46..0bde27eb2a75e 100644 --- a/clang-tools-extra/clang-doc/tool/ClangDocMain.cpp +++ b/clang-tools-extra/clang-doc/tool/ClangDocMain.cpp @@ -163,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; @@ -173,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(), @@ -210,6 +216,7 @@ llvm::Error getHtmlAssetFiles(const char *Argv0, return getDefaultAssetFiles(Argv0, CDCtx); } + llvm::Error getMustacheHtmlFiles(const char *Argv0, clang::doc::ClangDocContext &CDCtx) { if (!UserAssetPath.empty() && @@ -228,10 +235,34 @@ llvm::Error getMustacheHtmlFiles(const char *Argv0, AssetsPath = llvm::sys::path::parent_path(NativeClangDocPath); llvm::sys::path::append(AssetsPath, "..", "share", "clang-doc"); - llvm::SmallString<128> MustacheTemplate; - llvm::sys::path::native(AssetsPath, MustacheTemplate); - llvm::sys::path::append(MustacheTemplate, "template.mustache"); - CDCtx.MustacheTemplates.insert({"template", MustacheTemplate.c_str()}); + 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(); } From 85c4c2134aa38a78c4dde3f344e70a52fcfec966 Mon Sep 17 00:00:00 2001 From: PeterChou1 Date: Fri, 13 Sep 2024 02:26:22 -0400 Subject: [PATCH 22/24] [clang-doc] init mustache implementation --- clang-tools-extra/clang-doc/CMakeLists.txt | 5 +- clang-tools-extra/clang-doc/HTMLGenerator.cpp | 59 +++- .../clang-doc/HTMLMustacheGenerator.cpp | 289 ++++-------------- .../clang-doc/assets/template.mustache | 23 ++ .../clang-doc/tool/CMakeLists.txt | 8 +- .../clang-doc/tool/ClangDocMain.cpp | 62 +--- 6 files changed, 154 insertions(+), 292 deletions(-) create mode 100644 clang-tools-extra/clang-doc/assets/template.mustache diff --git a/clang-tools-extra/clang-doc/CMakeLists.txt b/clang-tools-extra/clang-doc/CMakeLists.txt index cc07742bbb420..7e5055e6b58b9 100644 --- a/clang-tools-extra/clang-doc/CMakeLists.txt +++ b/clang-tools-extra/clang-doc/CMakeLists.txt @@ -8,16 +8,15 @@ add_clang_library(clangDoc STATIC BitcodeReader.cpp BitcodeWriter.cpp ClangDoc.cpp - FileHelpersClangDoc.cpp Generators.cpp HTMLGenerator.cpp - HTMLMustacheGenerator.cpp Mapper.cpp MDGenerator.cpp Representation.cpp Serialize.cpp YAMLGenerator.cpp - + HTMLMustacheGenerator.cpp + DEPENDS omp_gen ClangDriverOptions diff --git a/clang-tools-extra/clang-doc/HTMLGenerator.cpp b/clang-tools-extra/clang-doc/HTMLGenerator.cpp index a4a763100eb97..6b0efc9d4f37c 100644 --- a/clang-tools-extra/clang-doc/HTMLGenerator.cpp +++ b/clang-tools-extra/clang-doc/HTMLGenerator.cpp @@ -8,7 +8,6 @@ #include "Generators.h" #include "Representation.h" -#include "FileHelpersClangDoc.h" #include "clang/Basic/Version.h" #include "llvm/ADT/StringExtras.h" #include "llvm/ADT/StringRef.h" @@ -252,6 +251,46 @@ static void appendVector(std::vector &&New, std::move(New.begin(), New.end(), std::back_inserter(Original)); } +// Compute the relative path from an Origin directory to a Destination directory +static SmallString<128> computeRelativePath(StringRef Destination, + 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; + } + 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; +} // HTML generation @@ -1107,6 +1146,24 @@ static llvm::Error genIndex(const ClangDocContext &CDCtx) { return llvm::Error::success(); } +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)); + 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::Error HTMLGenerator::createResources(ClangDocContext &CDCtx) { auto Err = serializeIndex(CDCtx); if (Err) diff --git a/clang-tools-extra/clang-doc/HTMLMustacheGenerator.cpp b/clang-tools-extra/clang-doc/HTMLMustacheGenerator.cpp index 6be4c795a6865..63def07c5fa80 100644 --- a/clang-tools-extra/clang-doc/HTMLMustacheGenerator.cpp +++ b/clang-tools-extra/clang-doc/HTMLMustacheGenerator.cpp @@ -7,7 +7,6 @@ //===----------------------------------------------------------------------===// #include "Generators.h" #include "Representation.h" -#include "FileHelpersClangDoc.h" #include "llvm/Support/MemoryBuffer.h" #include "llvm/Support/Mustache.h" @@ -49,12 +48,12 @@ class MustacheTemplateFile : public Template { Error registerPartialFile(StringRef Name, StringRef FileName) { ErrorOr> BufferOrError = MemoryBuffer::getFile(FileName); - if (auto EC = BufferOrError.getError()) + 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) {} @@ -65,54 +64,23 @@ 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(); + auto TemplateFilePath = CDCtx.MustacheTemplates.lookup("template"); + auto Template = MustacheTemplateFile::createMustacheFile(TemplateFilePath); + if (auto EC = Template.getError()) { + return llvm::createFileError("cannot open file", EC); + } + NamespaceTemplate = std::move(Template.get()); } - llvm::Error MustacheHTMLGenerator::generateDocs(llvm::StringRef RootDir, llvm::StringMap> Infos, const clang::doc::ClangDocContext &CDCtx) { - if (auto Err = setupTemplateFiles(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. @@ -125,9 +93,10 @@ MustacheHTMLGenerator::generateDocs(llvm::StringRef RootDir, 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()) + Err != std::error_code()) { return llvm::createStringError(Err, "Failed to create directory '%s'.", Path.c_str()); + } CreatedDirs.insert(Path); } @@ -139,33 +108,28 @@ MustacheHTMLGenerator::generateDocs(llvm::StringRef RootDir, std::error_code FileErr; llvm::raw_fd_ostream InfoOS(Group.getKey(), FileErr, llvm::sys::fs::OF_None); - if (FileErr) + 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)) + 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; + Obj.insert({"LineNumber", L.LineNumber}); + Obj.insert({"Filename", L.Filename}); } 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) { @@ -175,8 +139,6 @@ Value extractValue(const Reference &I, StringRef CurrentDirectory) { 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; } @@ -227,12 +189,8 @@ Value extractValue(const FunctionInfo &I, StringRef ParentInfoDir, 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); + for (const auto &P : I.Params) { + ParamArr.getAsArray()->emplace_back(extractValue(P.Type, ParentInfoDir)); } Obj.insert({"Params", ParamArr}); @@ -242,13 +200,12 @@ Value extractValue(const FunctionInfo &I, StringRef ParentInfoDir, 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})}); + if (I.DefLoc) { + if (!CDCtx.RepositoryUrl) + Obj.insert({"Location", extractValue(*I.DefLoc)}); else - Obj.insert({"Location", extractValue(L)}); + Obj.insert({"Location", extractValue(*I.DefLoc, + StringRef{*CDCtx.RepositoryUrl})}); } return Obj; } @@ -263,6 +220,7 @@ Value extractValue(const EnumInfo &I, const ClangDocContext &CDCtx) { 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(); @@ -289,75 +247,17 @@ Value extractValue(const EnumInfo &I, const ClangDocContext &CDCtx) { 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})}); + if (I.DefLoc) { + if (!CDCtx.RepositoryUrl) + Obj.insert({"Location", extractValue(*I.DefLoc)}); else - Obj.insert({"Location", extractValue(L)}); + Obj.insert({"Location", extractValue(*I.DefLoc, + StringRef{*CDCtx.RepositoryUrl})}); } 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; @@ -376,88 +276,36 @@ Value extractValue(const NamespaceInfo &I, const ClangDocContext &CDCtx) { 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(); + Value ArrNamespace = Array(); + for (const Reference& Child : I.Children.Namespaces) + ArrNamespace.getAsArray()->emplace_back(extractValue(Child, BasePath)); + NamespaceValue.insert({"Namespace", ArrNamespace}); - 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)}); + Value ArrRecord = Array(); + for (const Reference& Child : I.Children.Records) + ArrRecord.getAsArray()->emplace_back(extractValue(Child, BasePath)); + NamespaceValue.insert({"Record", ArrRecord}); - 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)}); - } + Value ArrFunction = Array(); + for (const FunctionInfo& Child : I.Children.Functions) + ArrFunction.getAsArray()->emplace_back(extractValue(Child, BasePath, + CDCtx)); + NamespaceValue.insert({"Function", ArrRecord}); - 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}}}); + Value ArrEnum = Array(); + for (const EnumInfo& Child : I.Children.Enums) + ArrEnum.getAsArray()->emplace_back(extractValue(Child, CDCtx)); + NamespaceValue.insert({"Enums", ArrEnum }); - 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 ArrTypedefs = Array(); + for (const TypedefInfo& Child : I.Children.Typedefs) + ArrTypedefs.getAsArray()->emplace_back(extractValue(Child)); + NamespaceValue.insert({"Typedefs", ArrTypedefs }); - 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, @@ -465,26 +313,16 @@ MustacheHTMLGenerator::generateDocForInfo(Info *I, llvm::raw_ostream &OS, 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() << llvm::formatv("{0:2}", V); - OS << RecordTemplate->render(V); + case InfoType::IT_record: 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(), @@ -494,21 +332,10 @@ MustacheHTMLGenerator::generateDocForInfo(Info *I, llvm::raw_ostream &OS, } 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"; +const char *MustacheHTMLGenerator::Format = "mustache"; static GeneratorRegistry::Add MHTML(MustacheHTMLGenerator::Format, @@ -516,7 +343,7 @@ static GeneratorRegistry::Add MHTML(MustacheHTMLGenerator // This anchor is used to force the linker to link in the generated object // file and thus register the generator. -volatile int MHTMLGeneratorAnchorSource = 0; +volatile int HTMLGeneratorAnchorSource = 0; } // namespace doc } // namespace clang \ 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..af4c60182ae52 --- /dev/null +++ b/clang-tools-extra/clang-doc/assets/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 +}} + + + + + {{NamespaceTitle}} + + {{#NamespaceComments}} +

Namespace Comment Present!

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

Namespace Present!

+ {{/Namespace}} + {{#Record}} +

Record Present!

+ {{/Record}} + \ 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 eccbc99a7ecc4..8eb067dbe6de8 100644 --- a/clang-tools-extra/clang-doc/tool/CMakeLists.txt +++ b/clang-tools-extra/clang-doc/tool/CMakeLists.txt @@ -21,13 +21,7 @@ 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 + template.mustache 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 0bde27eb2a75e..1897db3e7549f 100644 --- a/clang-tools-extra/clang-doc/tool/ClangDocMain.cpp +++ b/clang-tools-extra/clang-doc/tool/ClangDocMain.cpp @@ -103,7 +103,6 @@ enum OutputFormatTy { md, yaml, html, - mhtml }; static llvm::cl::opt @@ -113,9 +112,7 @@ static llvm::cl::opt clEnumValN(OutputFormatTy::md, "md", "Documentation in MD format."), clEnumValN(OutputFormatTy::html, "html", - "Documentation in HTML format."), - clEnumValN(OutputFormatTy::mhtml, "mhtml", - "Documentation in mHTML format")), + "Documentation in HTML format.")), llvm::cl::init(OutputFormatTy::yaml), llvm::cl::cat(ClangDocCategory)); @@ -127,8 +124,6 @@ std::string getFormatString() { return "md"; case OutputFormatTy::html: return "html"; - case OutputFormatTy::mhtml: - return "mhtml"; } llvm_unreachable("Unknown OutputFormatTy"); } @@ -163,15 +158,6 @@ 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; @@ -182,10 +168,13 @@ 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 = - appendPathNative(AssetsPath, "clang-doc-default-stylesheet.css"); - llvm::SmallString<128> IndexJS = - appendPathNative(AssetsPath, "index.js"); + 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"); if (!llvm::sys::fs::is_regular_file(IndexJS)) return llvm::createStringError(llvm::inconvertibleErrorCode(), @@ -216,7 +205,6 @@ llvm::Error getHtmlAssetFiles(const char *Argv0, return getDefaultAssetFiles(Argv0, CDCtx); } - llvm::Error getMustacheHtmlFiles(const char *Argv0, clang::doc::ClangDocContext &CDCtx) { if (!UserAssetPath.empty() && @@ -235,36 +223,10 @@ llvm::Error getMustacheHtmlFiles(const char *Argv0, 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(); + llvm::SmallString<128> MustacheTemplate; + llvm::sys::path::native(AssetsPath, MustacheTemplate); + llvm::sys::path::append(MustacheTemplate, "template.mustache"); + CDCtx.MustacheTemplates.insert({"template", MustacheTemplate.c_str()}); } /// Make the output of clang-doc deterministic by sorting the children of From 356217c434dabcf6731d73e23532461e2dedae4b Mon Sep 17 00:00:00 2001 From: PeterChou1 Date: Fri, 13 Sep 2024 17:50:03 -0400 Subject: [PATCH 23/24] [clang-doc] add a mustache backend init implementation --- .../clang-doc/HTMLMustacheGenerator.cpp | 32 ++++++++++++---- .../clang-doc/assets/template.mustache | 37 +++++++++++++++++-- .../clang-doc/tool/ClangDocMain.cpp | 9 ++++- .../Inputs/basic-project/include/Shape.h | 2 - 4 files changed, 65 insertions(+), 15 deletions(-) diff --git a/clang-tools-extra/clang-doc/HTMLMustacheGenerator.cpp b/clang-tools-extra/clang-doc/HTMLMustacheGenerator.cpp index 63def07c5fa80..bfa563ebdc7b3 100644 --- a/clang-tools-extra/clang-doc/HTMLMustacheGenerator.cpp +++ b/clang-tools-extra/clang-doc/HTMLMustacheGenerator.cpp @@ -54,6 +54,7 @@ class MustacheTemplateFile : public Template { 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) {} @@ -72,6 +73,7 @@ setupTemplateFiles(const clang::doc::ClangDocContext &CDCtx) { return llvm::createFileError("cannot open file", EC); } NamespaceTemplate = std::move(Template.get()); + return llvm::Error::success(); } llvm::Error @@ -118,6 +120,7 @@ MustacheHTMLGenerator::generateDocs(llvm::StringRef RootDir, } } } + return llvm::Error::success(); } Value extractValue(const Location &L, @@ -130,6 +133,8 @@ Value extractValue(const Location &L, SmallString<128> FileURL(*RepositoryUrl); llvm::sys::path::append(FileURL, llvm::sys::path::Style::posix, L.Filename); Obj.insert({"FileURL", FileURL}); + + return Obj; } Value extractValue(const Reference &I, StringRef CurrentDirectory) { @@ -280,29 +285,39 @@ Value extractValue(const NamespaceInfo &I, const ClangDocContext &CDCtx) { Value ArrNamespace = Array(); for (const Reference& Child : I.Children.Namespaces) ArrNamespace.getAsArray()->emplace_back(extractValue(Child, BasePath)); - NamespaceValue.insert({"Namespace", ArrNamespace}); + + if (!ArrNamespace.getAsArray()->empty()) + NamespaceValue.insert({"Namespace", Object{{"Links", ArrNamespace}}}); Value ArrRecord = Array(); for (const Reference& Child : I.Children.Records) ArrRecord.getAsArray()->emplace_back(extractValue(Child, BasePath)); - NamespaceValue.insert({"Record", ArrRecord}); + + if (!ArrRecord.getAsArray()->empty()) + NamespaceValue.insert({"Record", Object{{"Links", ArrRecord}}}); Value ArrFunction = Array(); for (const FunctionInfo& Child : I.Children.Functions) ArrFunction.getAsArray()->emplace_back(extractValue(Child, BasePath, CDCtx)); - NamespaceValue.insert({"Function", ArrRecord}); + if (!ArrFunction.getAsArray()->empty()) + NamespaceValue.insert({"Function", Object{{"Obj", ArrFunction}}}); Value ArrEnum = Array(); for (const EnumInfo& Child : I.Children.Enums) ArrEnum.getAsArray()->emplace_back(extractValue(Child, CDCtx)); - NamespaceValue.insert({"Enums", ArrEnum }); + + if (!ArrEnum.getAsArray()->empty()) + NamespaceValue.insert({"Enums", Object{{"Obj", ArrEnum }}}); Value ArrTypedefs = Array(); for (const TypedefInfo& Child : I.Children.Typedefs) ArrTypedefs.getAsArray()->emplace_back(extractValue(Child)); - NamespaceValue.insert({"Typedefs", ArrTypedefs }); + if (!ArrTypedefs.getAsArray()->empty()) + NamespaceValue.insert({"Typedefs", Object{{"Obj", ArrTypedefs }}}); + + return NamespaceValue; } @@ -313,6 +328,7 @@ MustacheHTMLGenerator::generateDocForInfo(Info *I, llvm::raw_ostream &OS, switch (I->IT) { case InfoType::IT_namespace: { Value V = extractValue(*static_cast(I), CDCtx); + llvm::outs() << V << "\n"; OS << NamespaceTemplate->render(V); break; } @@ -332,10 +348,10 @@ MustacheHTMLGenerator::generateDocForInfo(Info *I, llvm::raw_ostream &OS, } llvm::Error MustacheHTMLGenerator::createResources(ClangDocContext &CDCtx) { - + return llvm::Error::success(); } -const char *MustacheHTMLGenerator::Format = "mustache"; +const char *MustacheHTMLGenerator::Format = "mhtml"; static GeneratorRegistry::Add MHTML(MustacheHTMLGenerator::Format, @@ -343,7 +359,7 @@ static GeneratorRegistry::Add MHTML(MustacheHTMLGenerator // This anchor is used to force the linker to link in the generated object // file and thus register the generator. -volatile int HTMLGeneratorAnchorSource = 0; +volatile int MHTMLGeneratorAnchorSource = 0; } // namespace doc } // namespace clang \ 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 index af4c60182ae52..1d3407f8b5292 100644 --- a/clang-tools-extra/clang-doc/assets/template.mustache +++ b/clang-tools-extra/clang-doc/assets/template.mustache @@ -3,7 +3,7 @@ See https://llvm.org/LICENSE.txt for license information. SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception - This file defines the template + This file defines the template for generating Namespaces }} @@ -11,13 +11,42 @@ {{NamespaceTitle}} +

{{NamespaceTitle}}

{{#NamespaceComments}} -

Namespace Comment Present!

+

Namespace Comment

{{/NamespaceComments}} {{#Namespace}} -

Namespace Present!

+

Namespace

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

Record Present!

+

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/ClangDocMain.cpp b/clang-tools-extra/clang-doc/tool/ClangDocMain.cpp index 1897db3e7549f..6f743c6603d46 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"); } @@ -227,6 +232,8 @@ llvm::Error getMustacheHtmlFiles(const char *Argv0, llvm::sys::path::native(AssetsPath, MustacheTemplate); llvm::sys::path::append(MustacheTemplate, "template.mustache"); CDCtx.MustacheTemplates.insert({"template", MustacheTemplate.c_str()}); + + return llvm::Error::success(); } /// Make the output of clang-doc deterministic by sorting the children of 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; }; - - From 450a6b12e828c1eba0ca326d3ba321eccc52315e Mon Sep 17 00:00:00 2001 From: PeterChou1 Date: Fri, 11 Oct 2024 17:58:48 -0400 Subject: [PATCH 24/24] [clang-doc] add class template --- clang-tools-extra/clang-doc/CMakeLists.txt | 1 + .../clang-doc/HTMLMustacheGenerator.cpp | 292 ++++++++++++++---- .../clang-doc/assets/clang-doc-mustache.css | 63 ++-- .../assets/namespace-template.mustache | 42 +-- .../clang-doc/tool/CMakeLists.txt | 8 +- .../clang-doc/tool/ClangDocMain.cpp | 53 +++- 6 files changed, 305 insertions(+), 154 deletions(-) diff --git a/clang-tools-extra/clang-doc/CMakeLists.txt b/clang-tools-extra/clang-doc/CMakeLists.txt index 7e5055e6b58b9..d361e928860a6 100644 --- a/clang-tools-extra/clang-doc/CMakeLists.txt +++ b/clang-tools-extra/clang-doc/CMakeLists.txt @@ -16,6 +16,7 @@ add_clang_library(clangDoc STATIC Serialize.cpp YAMLGenerator.cpp HTMLMustacheGenerator.cpp + FileHelpersClangDoc.cpp DEPENDS omp_gen diff --git a/clang-tools-extra/clang-doc/HTMLMustacheGenerator.cpp b/clang-tools-extra/clang-doc/HTMLMustacheGenerator.cpp index bfa563ebdc7b3..1f96202f1f6de 100644 --- a/clang-tools-extra/clang-doc/HTMLMustacheGenerator.cpp +++ b/clang-tools-extra/clang-doc/HTMLMustacheGenerator.cpp @@ -7,6 +7,7 @@ //===----------------------------------------------------------------------===// #include "Generators.h" #include "Representation.h" +#include "FileHelpersClangDoc.h" #include "llvm/Support/MemoryBuffer.h" #include "llvm/Support/Mustache.h" @@ -48,9 +49,8 @@ class MustacheTemplateFile : public Template { Error registerPartialFile(StringRef Name, StringRef FileName) { ErrorOr> BufferOrError = MemoryBuffer::getFile(FileName); - if (auto EC = BufferOrError.getError()) { + 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); @@ -65,24 +65,54 @@ static std::unique_ptr NamespaceTemplate = nullptr; static std::unique_ptr RecordTemplate = nullptr; -llvm::Error -setupTemplateFiles(const clang::doc::ClangDocContext &CDCtx) { - auto TemplateFilePath = CDCtx.MustacheTemplates.lookup("template"); - auto Template = MustacheTemplateFile::createMustacheFile(TemplateFilePath); - if (auto EC = Template.getError()) { +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; } - NamespaceTemplate = std::move(Template.get()); 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)) { + 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. @@ -95,10 +125,9 @@ MustacheHTMLGenerator::generateDocs(llvm::StringRef RootDir, 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()) { + Err != std::error_code()) return llvm::createStringError(Err, "Failed to create directory '%s'.", Path.c_str()); - } CreatedDirs.insert(Path); } @@ -110,14 +139,13 @@ MustacheHTMLGenerator::generateDocs(llvm::StringRef RootDir, std::error_code FileErr; llvm::raw_fd_ostream InfoOS(Group.getKey(), FileErr, llvm::sys::fs::OF_None); - if (FileErr) { + 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)) { + if (llvm::Error Err = generateDocForInfo(Info, InfoOS, CDCtx)) return Err; - } } } return llvm::Error::success(); @@ -126,12 +154,15 @@ MustacheHTMLGenerator::generateDocs(llvm::StringRef RootDir, 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) { - Obj.insert({"LineNumber", L.LineNumber}); - Obj.insert({"Filename", L.Filename}); + 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; @@ -144,6 +175,8 @@ Value extractValue(const Reference &I, StringRef CurrentDirectory) { 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; } @@ -194,8 +227,12 @@ Value extractValue(const FunctionInfo &I, StringRef ParentInfoDir, Obj.insert({"ReturnType", extractValue(I.ReturnType.Type, ParentInfoDir)}); Value ParamArr = Array(); - for (const auto &P : I.Params) { - ParamArr.getAsArray()->emplace_back(extractValue(P.Type, ParentInfoDir)); + 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}); @@ -205,12 +242,13 @@ Value extractValue(const FunctionInfo &I, StringRef ParentInfoDir, ArrDesc.getAsArray()->emplace_back(extractValue(Child)); Obj.insert({"FunctionComments", ArrDesc}); } - if (I.DefLoc) { - if (!CDCtx.RepositoryUrl) - Obj.insert({"Location", extractValue(*I.DefLoc)}); + 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(*I.DefLoc, - StringRef{*CDCtx.RepositoryUrl})}); + Obj.insert({"Location", extractValue(L)}); } return Obj; } @@ -225,7 +263,6 @@ Value extractValue(const EnumInfo &I, const ClangDocContext &CDCtx) { 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(); @@ -252,75 +289,175 @@ Value extractValue(const EnumInfo &I, const ClangDocContext &CDCtx) { Obj.insert({"EnumComments", ArrDesc}); } - if (I.DefLoc) { - if (!CDCtx.RepositoryUrl) - Obj.insert({"Location", extractValue(*I.DefLoc)}); + 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(*I.DefLoc, - StringRef{*CDCtx.RepositoryUrl})}); + Obj.insert({"Location", extractValue(L)}); } return Obj; } -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 }); - } - +void extractScopeChildren(const ScopeChildren &S, Object &Obj, + StringRef ParentInfoDir, + const ClangDocContext &CDCtx) { Value ArrNamespace = Array(); - for (const Reference& Child : I.Children.Namespaces) - ArrNamespace.getAsArray()->emplace_back(extractValue(Child, BasePath)); + for (const Reference& Child : S.Namespaces) + ArrNamespace.getAsArray()->emplace_back(extractValue(Child, ParentInfoDir)); if (!ArrNamespace.getAsArray()->empty()) - NamespaceValue.insert({"Namespace", Object{{"Links", ArrNamespace}}}); + Obj.insert({"Namespace", Object{{"Links", ArrNamespace}}}); Value ArrRecord = Array(); - for (const Reference& Child : I.Children.Records) - ArrRecord.getAsArray()->emplace_back(extractValue(Child, BasePath)); + for (const Reference& Child : S.Records) + ArrRecord.getAsArray()->emplace_back(extractValue(Child, ParentInfoDir)); if (!ArrRecord.getAsArray()->empty()) - NamespaceValue.insert({"Record", Object{{"Links", ArrRecord}}}); + Obj.insert({"Record", Object{{"Links", ArrRecord}}}); Value ArrFunction = Array(); - for (const FunctionInfo& Child : I.Children.Functions) - ArrFunction.getAsArray()->emplace_back(extractValue(Child, BasePath, - CDCtx)); + 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()) - NamespaceValue.insert({"Function", Object{{"Obj", ArrFunction}}}); + 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 : I.Children.Enums) + for (const EnumInfo& Child : S.Enums) ArrEnum.getAsArray()->emplace_back(extractValue(Child, CDCtx)); if (!ArrEnum.getAsArray()->empty()) - NamespaceValue.insert({"Enums", Object{{"Obj", ArrEnum }}}); + Obj.insert({"Enums", Object{{"Obj", ArrEnum }}}); Value ArrTypedefs = Array(); - for (const TypedefInfo& Child : I.Children.Typedefs) + for (const TypedefInfo& Child : S.Typedefs) ArrTypedefs.getAsArray()->emplace_back(extractValue(Child)); if (!ArrTypedefs.getAsArray()->empty()) - NamespaceValue.insert({"Typedefs", Object{{"Obj", ArrTypedefs }}}); + 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, @@ -328,17 +465,27 @@ MustacheHTMLGenerator::generateDocForInfo(Info *I, llvm::raw_ostream &OS, switch (I->IT) { case InfoType::IT_namespace: { Value V = extractValue(*static_cast(I), CDCtx); - llvm::outs() << V << "\n"; + setupTemplateValue(CDCtx, V, I); OS << NamespaceTemplate->render(V); break; } - case InfoType::IT_record: + 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(), @@ -348,6 +495,17 @@ MustacheHTMLGenerator::generateDocForInfo(Info *I, llvm::raw_ostream &OS, } 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(); } @@ -355,7 +513,7 @@ const char *MustacheHTMLGenerator::Format = "mhtml"; static GeneratorRegistry::Add MHTML(MustacheHTMLGenerator::Format, - "Generator for mustache HTML output."); + "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. diff --git a/clang-tools-extra/clang-doc/assets/clang-doc-mustache.css b/clang-tools-extra/clang-doc/assets/clang-doc-mustache.css index 2f07baa7ca0a1..a885a36cb4a3d 100644 --- a/clang-tools-extra/clang-doc/assets/clang-doc-mustache.css +++ b/clang-tools-extra/clang-doc/assets/clang-doc-mustache.css @@ -1,3 +1,4 @@ +/* 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 { @@ -18,6 +19,7 @@ video { display:block; max-width:100% } + * { --brand-light:#ce6300; --text1-light:#000000; @@ -30,6 +32,7 @@ video { --surface1-dark:#161212; --surface2-dark:#272424 } + :root { color-scheme:light; --brand:var(--brand-light); @@ -40,6 +43,7 @@ video { --surface1:var(--surface1-light); --surface2:var(--surface2-light) } + @media(prefers-color-scheme:dark) { :root { color-scheme:dark; @@ -52,6 +56,7 @@ video { --surface2:var(--surface2-dark) } } + [color-scheme=light] { color-scheme:light; --brand:var(--brand-light); @@ -62,6 +67,7 @@ video { --surface1:var(--surface1-light); --surface2:var(--surface2-light) } + [color-scheme=dark] { color-scheme:dark; --brand:var(--brand-dark); @@ -72,6 +78,7 @@ video { --surface1:var(--surface1-dark); --surface2:var(--surface2-dark) } + html { background-color:var(--surface1) } @@ -200,6 +207,7 @@ body, html { .navbar__close:hover { color:var(--text1) } + @media(min-width:769px) { .navbar__close { display:none @@ -212,22 +220,27 @@ body, html { 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); @@ -237,6 +250,7 @@ body, html { width:2.5rem; height:2.5rem } + .navbar__theme-toggle-button:hover { color:var(--text1) } @@ -248,42 +262,51 @@ body, html { 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 @@ -299,69 +322,42 @@ body, html { 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 } - -.links-wrapper { - display:grid; - gap:1rem; - grid-template-columns:1fr 1fr 1fr 1fr -} -@media(max-width:768px) { - .links-wrapper { - grid-template-columns:1fr 1fr - } -} -@media(max-width:480px) { - .links-wrapper { - grid-template-columns:1fr - } -} -.links-wrapper .link { - display:flex; - flex-direction:column; - align-items:center; - padding:1rem; - border:1px solid var(--text1); - border-radius:.25rem -} -.links-wrapper .link__icon { - font-size:2rem -} -.links-wrapper .link__title { - font-size:1rem -} - .table-wrapper { display:flex; flex-direction:column; @@ -375,7 +371,6 @@ body, html { text-align: left; } - .block-command-command { font-weight: bold; } @@ -442,7 +437,6 @@ body, html { color: var(--brand) } - /* Content */ .content { background-color: var(--text1-inverse); @@ -452,6 +446,7 @@ body, html { width: calc(100% - 250px); height: 100vh; } + .sidebar-item { color: var(--text1); } diff --git a/clang-tools-extra/clang-doc/assets/namespace-template.mustache b/clang-tools-extra/clang-doc/assets/namespace-template.mustache index 4061fd026886e..21cbaa7ec5cf3 100644 --- a/clang-tools-extra/clang-doc/assets/namespace-template.mustache +++ b/clang-tools-extra/clang-doc/assets/namespace-template.mustache @@ -35,47 +35,7 @@ Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit - anim id est laborum. - Lorem ipsum dolor sit amet, consectetur adipiscing elit, - sed do eiusmod tempor incididunt ut labore et dolore magna - aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco - laboris nisi ut aliquip ex ea commodo consequat. - Duis aute irure dolor in reprehenderit in voluptate velit esse - cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat - cupidatat non proident, sunt in culpa qui officia deserunt mollit - anim id est laborum. - Lorem ipsum dolor sit amet, consectetur adipiscing elit, - sed do eiusmod tempor incididunt ut labore et dolore magna - aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco - laboris nisi ut aliquip ex ea commodo consequat. - Duis aute irure dolor in reprehenderit in voluptate velit esse - cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat - cupidatat non proident, sunt in culpa qui officia deserunt mollit - anim id est laborum. - Lorem ipsum dolor sit amet, consectetur adipiscing elit, - sed do eiusmod tempor incididunt ut labore et dolore magna - aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco - laboris nisi ut aliquip ex ea commodo consequat. - Duis aute irure dolor in reprehenderit in voluptate velit esse - cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat - cupidatat non proident, sunt in culpa qui officia deserunt mollit - anim id est laborum. - Lorem ipsum dolor sit amet, consectetur adipiscing elit, - sed do eiusmod tempor incididunt ut labore et dolore magna - aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco - laboris nisi ut aliquip ex ea commodo consequat. - Duis aute irure dolor in reprehenderit in voluptate velit esse - cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat - cupidatat non proident, sunt in culpa qui officia deserunt mollit - anim id est laborum. - Lorem ipsum dolor sit amet, consectetur adipiscing elit, - sed do eiusmod tempor incididunt ut labore et dolore magna - aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco - laboris nisi ut aliquip ex ea commodo consequat. - Duis aute irure dolor in reprehenderit in voluptate velit esse - cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat - cupidatat non proident, sunt in culpa qui officia deserunt mollit - anim id est laborum. + anim id est laborum
diff --git a/clang-tools-extra/clang-doc/tool/CMakeLists.txt b/clang-tools-extra/clang-doc/tool/CMakeLists.txt index 8eb067dbe6de8..eccbc99a7ecc4 100644 --- a/clang-tools-extra/clang-doc/tool/CMakeLists.txt +++ b/clang-tools-extra/clang-doc/tool/CMakeLists.txt @@ -21,7 +21,13 @@ target_link_libraries(clang-doc set(assets index.js - template.mustache + 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 6f743c6603d46..0bde27eb2a75e 100644 --- a/clang-tools-extra/clang-doc/tool/ClangDocMain.cpp +++ b/clang-tools-extra/clang-doc/tool/ClangDocMain.cpp @@ -163,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; @@ -173,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(), @@ -210,6 +216,7 @@ llvm::Error getHtmlAssetFiles(const char *Argv0, return getDefaultAssetFiles(Argv0, CDCtx); } + llvm::Error getMustacheHtmlFiles(const char *Argv0, clang::doc::ClangDocContext &CDCtx) { if (!UserAssetPath.empty() && @@ -228,10 +235,34 @@ llvm::Error getMustacheHtmlFiles(const char *Argv0, AssetsPath = llvm::sys::path::parent_path(NativeClangDocPath); llvm::sys::path::append(AssetsPath, "..", "share", "clang-doc"); - llvm::SmallString<128> MustacheTemplate; - llvm::sys::path::native(AssetsPath, MustacheTemplate); - llvm::sys::path::append(MustacheTemplate, "template.mustache"); - CDCtx.MustacheTemplates.insert({"template", MustacheTemplate.c_str()}); + 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(); }