Diego Astiazaran 3550da79ec [clang-doc] Redesign of generated HTML files
The new design includes a header (contains the project name), a main section, and a footer.
The main section is divided into three subsections. Left, middle, right. The left section contains the general index, the middle contains the info's data, and the right contains the index for the info's content.
The CSS has been updated.
A flag --project-name is added.
The Attributes attribute of the TagNode struct is now a vector of pairs because these attributes should be rendered in the insertion order.
The functions (cpp and js) that converts an Index tree structure into HTML were slightly modified; the first ul tag created is now a ol tag. The inner lists are still ul.

Differential Revision: https://reviews.llvm.org/D66353

llvm-svn: 369139
2019-08-16 18:38:11 +00:00

300 lines
10 KiB
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.
// 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"
namespace clang {
namespace doc {
namespace {
const SymbolID EmptySID = SymbolID();
template <typename T>
llvm::Expected<std::unique_ptr<Info>>
reduce(std::vector<std::unique_ptr<Info>> &Values) {
if (Values.empty())
return llvm::make_error<llvm::StringError>(" No values to reduce.\n",
llvm::inconvertibleErrorCode());
std::unique_ptr<Info> Merged = std::make_unique<T>(Values[0]->USR);
T *Tmp = static_cast<T *>(Merged.get());
for (auto &I : Values)
Tmp->merge(std::move(*static_cast<T *>(I.get())));
return std::move(Merged);
}
// Return the index of the matching child in the vector, or -1 if merge is not
// necessary.
template <typename T>
int getChildIndexIfExists(std::vector<T> &Children, T &ChildToMerge) {
for (unsigned long I = 0; I < Children.size(); I++) {
if (ChildToMerge.USR == Children[I].USR)
return I;
}
return -1;
}
void reduceChildren(std::vector<Reference> &Children,
std::vector<Reference> &&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));
}
}
void reduceChildren(std::vector<FunctionInfo> &Children,
std::vector<FunctionInfo> &&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));
}
}
void reduceChildren(std::vector<EnumInfo> &Children,
std::vector<EnumInfo> &&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<std::unique_ptr<Info>>
mergeInfos(std::vector<std::unique_ptr<Info>> &Values) {
if (Values.empty())
return llvm::make_error<llvm::StringError>("No info values to merge.\n",
llvm::inconvertibleErrorCode());
switch (Values[0]->IT) {
case InfoType::IT_namespace:
return reduce<NamespaceInfo>(Values);
case InfoType::IT_record:
return reduce<RecordInfo>(Values);
case InfoType::IT_enum:
return reduce<EnumInfo>(Values);
case InfoType::IT_function:
return reduce<FunctionInfo>(Values);
default:
return llvm::make_error<llvm::StringError>("Unexpected info type.\n",
llvm::inconvertibleErrorCode());
}
}
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;
if (!IsInGlobalNamespace)
IsInGlobalNamespace = Other.IsInGlobalNamespace;
}
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));
std::sort(Description.begin(), Description.end());
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));
std::sort(Loc.begin(), Loc.end());
auto Last = std::unique(Loc.begin(), Loc.end());
Loc.erase(Last, Loc.end());
mergeBase(std::move(Other));
}
void NamespaceInfo::merge(NamespaceInfo &&Other) {
assert(mergeable(Other));
// Reduce children if necessary.
reduceChildren(ChildNamespaces, std::move(Other.ChildNamespaces));
reduceChildren(ChildRecords, std::move(Other.ChildRecords));
reduceChildren(ChildFunctions, std::move(Other.ChildFunctions));
reduceChildren(ChildEnums, std::move(Other.ChildEnums));
mergeBase(std::move(Other));
}
void RecordInfo::merge(RecordInfo &&Other) {
assert(mergeable(Other));
if (!TagType)
TagType = Other.TagType;
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(ChildRecords, std::move(Other.ChildRecords));
reduceChildren(ChildFunctions, std::move(Other.ChildFunctions));
reduceChildren(ChildEnums, std::move(Other.ChildEnums));
SymbolInfo::merge(std::move(Other));
}
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));
}
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_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() {
std::sort(Children.begin(), Children.end());
for (auto &C : Children)
C.sort();
}
ClangDocContext::ClangDocContext(tooling::ExecutionContext *ECtx,
StringRef ProjectName, bool PublicOnly,
StringRef OutDirectory, StringRef SourceRoot,
StringRef RepositoryUrl,
std::vector<std::string> UserStylesheets,
std::vector<std::string> JsScripts)
: ECtx(ECtx), ProjectName(ProjectName), PublicOnly(PublicOnly),
OutDirectory(OutDirectory), UserStylesheets(UserStylesheets),
JsScripts(JsScripts) {
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 = SourceRootDir.str();
if (!RepositoryUrl.empty()) {
this->RepositoryUrl = RepositoryUrl;
if (!RepositoryUrl.empty() && RepositoryUrl.find("http://") != 0 &&
RepositoryUrl.find("https://") != 0)
this->RepositoryUrl->insert(0, "https://");
}
}
} // namespace doc
} // namespace clang