Index: CMakeLists.txt =================================================================== --- CMakeLists.txt +++ CMakeLists.txt @@ -7,6 +7,7 @@ endif() add_subdirectory(change-namespace) +add_subdirectory(clang-doc) add_subdirectory(clang-query) add_subdirectory(clang-move) add_subdirectory(clangd) Index: clang-doc/CMakeLists.txt =================================================================== --- /dev/null +++ clang-doc/CMakeLists.txt @@ -0,0 +1,21 @@ +set(LLVM_LINK_COMPONENTS + support + ) + +add_clang_library(clangDoc + ClangDoc.cpp + ClangDocMapper.cpp + ClangDocBinary.cpp + + LINK_LIBS + clangAnalysis + clangAST + clangASTMatchers + clangBasic + clangFrontend + clangLex + clangTooling + clangToolingCore + ) + +add_subdirectory(tool) Index: clang-doc/ClangDoc.h =================================================================== --- /dev/null +++ clang-doc/ClangDoc.h @@ -0,0 +1,60 @@ +//===-- ClangDoc.h - ClangDoc -----------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_DOC_CLANGDOC_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_DOC_CLANGDOC_H + +#include +#include +#include "ClangDocBinary.h" +#include "ClangDocMapper.h" +#include "clang/AST/AST.h" +#include "clang/AST/ASTConsumer.h" +#include "clang/AST/ASTContext.h" +#include "clang/AST/Comment.h" +#include "clang/AST/RecursiveASTVisitor.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/ASTMatchers/ASTMatchersInternal.h" +#include "clang/Frontend/ASTConsumers.h" +#include "clang/Frontend/CompilerInstance.h" +#include "clang/Frontend/FrontendActions.h" +#include "clang/Tooling/Tooling.h" + +using namespace clang::ast_matchers; + +namespace clang { +namespace doc { + +/// Callback class for matchers. +/// Parses each match and sends it along to the reporter for serialization. +class ClangDocCallback : public MatchFinder::MatchCallback { + public: + ClangDocCallback(StringRef BoundName, ExecutionContext &ECtx, + ClangDocBinaryWriter &Writer) + : ECtx(ECtx), Mapper(Writer), BoundName(BoundName) {} + virtual void run(const MatchFinder::MatchResult &Result) final; + + private: + template + void processMatchedDecl(const T *D); + int getLine(const NamedDecl *D) const; + StringRef getFile(const NamedDecl *D) const; + comments::FullComment *getComment(const NamedDecl *D) const; + std::string getName(const NamedDecl *D) const; + + ASTContext *Context; + ExecutionContext &ECtx; + ClangDocMapper Mapper; + StringRef BoundName; +}; + +} // namespace doc +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_DOC_CLANGDOC_H Index: clang-doc/ClangDoc.cpp =================================================================== --- /dev/null +++ clang-doc/ClangDoc.cpp @@ -0,0 +1,86 @@ +//===-- ClangDoc.cpp - ClangDoc ---------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "ClangDoc.h" +#include "clang/AST/AST.h" +#include "clang/AST/Comment.h" +#include "clang/AST/Mangle.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/ASTMatchers/ASTMatchersInternal.h" +#include "llvm/Bitcode/BitstreamWriter.h" + +using namespace clang; +using namespace clang::ast_matchers; +using namespace clang::tooling; +using namespace llvm; + +namespace clang { +namespace doc { + +template +void ClangDocCallback::processMatchedDecl(const T *D) { + if (!Context->getSourceManager().isWrittenInMainFile(D->getLocation())) + return; + std::string Name = getName(D); + ECtx.reportResult( + Name, Mapper.emitInfo(D, getComment(D), Name, getLine(D), getFile(D))); +} + +void ClangDocCallback::run(const MatchFinder::MatchResult &Result) { + Context = Result.Context; + if (const auto *M = Result.Nodes.getNodeAs(BoundName)) + processMatchedDecl(M); + else if (const auto *M = Result.Nodes.getNodeAs(BoundName)) + processMatchedDecl(M); + else if (const auto *M = Result.Nodes.getNodeAs(BoundName)) + processMatchedDecl(M); + else if (const auto *M = Result.Nodes.getNodeAs(BoundName)) + processMatchedDecl(M); + else if (const auto *M = Result.Nodes.getNodeAs(BoundName)) + processMatchedDecl(M); +} + +comments::FullComment *ClangDocCallback::getComment(const NamedDecl *D) const { + RawComment *Comment = Context->getRawCommentForDeclNoCache(D); + // FIXME: Move setAttached to the initial comment parsing. + if (Comment) { + Comment->setAttached(); + return Comment->parse(*Context, nullptr, D); + } + return nullptr; +} + +int ClangDocCallback::getLine(const NamedDecl *D) const { + return Context->getSourceManager().getPresumedLoc(D->getLocStart()).getLine(); +} + +StringRef ClangDocCallback::getFile(const NamedDecl *D) const { + return Context->getSourceManager() + .getPresumedLoc(D->getLocStart()) + .getFilename(); +} + +std::string ClangDocCallback::getName(const NamedDecl *D) const { + if (const auto *F = dyn_cast(D)) { + MangleContext *MC = Context->createMangleContext(); + std::string S; + llvm::raw_string_ostream MangledName(S); + if (const auto *Ctor = dyn_cast(F)) + MC->mangleCXXCtor(Ctor, CXXCtorType::Ctor_Complete, MangledName); + else if (const auto *Dtor = dyn_cast(F)) + MC->mangleCXXDtor(Dtor, CXXDtorType::Dtor_Complete, MangledName); + else + MC->mangleName(F, MangledName); + return MangledName.str(); + } + return D->getQualifiedNameAsString(); +} + +} // namespace doc +} // namespace clang Index: clang-doc/ClangDocBinary.h =================================================================== --- /dev/null +++ clang-doc/ClangDocBinary.h @@ -0,0 +1,71 @@ +//===-- ClangDocBinary.h - ClangDoc Binary ---------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_DOC_CLANG_DOC_BINARY_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_DOC_CLANG_DOC_BINARY_H + +#include +#include "ClangDocRepresentation.h" +#include "clang/AST/AST.h" +#include "llvm/ADT/SmallVector.h" +#include "llvm/Bitcode/BitstreamReader.h" +#include "llvm/Bitcode/BitstreamWriter.h" + +using namespace llvm; + +namespace clang { +namespace doc { + +class AbbreviationMap { + llvm::DenseMap Abbrevs; + + public: + AbbreviationMap() {} + void set(unsigned recordID, unsigned abbrevID); + unsigned get(unsigned recordID); + void clear(); +}; + +class ClangDocBinaryWriter { + public: + ClangDocBinaryWriter(bool OmitFilenames = false) + : OmitFilenames(OmitFilenames){}; + + using RecordData = SmallVector; + + template + void writeBitstream(const T &I, BitstreamWriter &Stream, + bool writeBlockInfo = false); + + private: + void emitBlockInfoBlock(BitstreamWriter &Stream); + void emitHeader(BitstreamWriter &Stream); + void emitStringRecord(StringRef Str, unsigned RecordId, + BitstreamWriter &Stream); + void emitLocationRecord(int LineNumber, StringRef File, unsigned RecordId, + BitstreamWriter &Stream); + void emitIntRecord(int Value, unsigned RecordId, BitstreamWriter &Stream); + void emitNamedTypeBlock(const NamedType &N, int ID, BitstreamWriter &Stream); + void emitCommentBlock(const CommentInfo *I, BitstreamWriter &Stream); + + void emitRecordID(unsigned ID, const char *Name, BitstreamWriter &Stream); + void emitBlockID(unsigned ID, const char *Name, BitstreamWriter &Stream); + void emitStringAbbrev(unsigned D, unsigned Block, BitstreamWriter &Stream); + void emitLocationAbbrev(unsigned D, unsigned Block, BitstreamWriter &Stream); + void emitIntAbbrev(unsigned D, unsigned Block, BitstreamWriter &Stream); + + RecordData Record; + bool OmitFilenames; + AbbreviationMap Abbrevs; +}; + +} // namespace doc +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_DOC_CLANG_DOC_BINARY_H Index: clang-doc/ClangDocBinary.cpp =================================================================== --- /dev/null +++ clang-doc/ClangDocBinary.cpp @@ -0,0 +1,410 @@ +//===-- ClangDocBinary.cpp - ClangDoc Binary -------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "ClangDocBinary.h" + +using namespace llvm; + +namespace clang { +namespace doc { + +enum BlockIds { + NAMESPACE_BLOCK_ID = bitc::FIRST_APPLICATION_BLOCKID, + NONDEF_BLOCK_ID, + ENUM_BLOCK_ID, + NAMED_TYPE_BLOCK_ID, + RECORD_BLOCK_ID, + FUNCTION_BLOCK_ID, + COMMENT_BLOCK_ID, + BI_FIRST = NAMESPACE_BLOCK_ID, + BI_LAST = COMMENT_BLOCK_ID +}; + +#define INFODATATYPES(X) X##_FULLY_QUALIFIED_NAME, X##_NAME, X##_NAMESPACE, + +enum DataTypes { + COMMENT_KIND = 1, + COMMENT_TEXT, + COMMENT_NAME, + COMMENT_POSITION, + COMMENT_DIRECTION, + COMMENT_PARAMNAME, + COMMENT_CLOSENAME, + COMMENT_SELFCLOSING, + COMMENT_EXPLICIT, + COMMENT_ATTRKEY, + COMMENT_ATTRVAL, + COMMENT_ARG, + NAMED_TYPE_ID, + NAMED_TYPE_TYPE, + NAMED_TYPE_NAME, + NAMED_TYPE_ACCESS, + INFODATATYPES(NAMESPACE) INFODATATYPES(NONDEF) NONDEF_LOCATION, + NONDEF_TYPE, + INFODATATYPES(ENUM) ENUM_LOCATION, + ENUM_DEFLOCATION, + ENUM_SCOPED, + ENUM_NAMED_TYPE, + ENUM_MEMBER, + INFODATATYPES(RECORD) RECORD_LOCATION, + RECORD_DEFLOCATION, + RECORD_TAG_TYPE, + RECORD_MEMBER, + RECORD_PARENT, + RECORD_VPARENT, + INFODATATYPES(FUNCTION) FUNCTION_LOCATION, + FUNCTION_DEFLOCATION, + FUNCTION_MANGLED_NAME, + FUNCTION_PARENT, + FUNCTION_ACCESS, +}; + +#undef INFODATATYPES + +void AbbreviationMap::set(unsigned recordID, unsigned abbrevID) { + assert(Abbrevs.find(recordID) == Abbrevs.end() && + "Abbreviation already set."); + Abbrevs[recordID] = abbrevID; +} + +unsigned AbbreviationMap::get(unsigned recordID) { + assert(Abbrevs.find(recordID) != Abbrevs.end() && "Abbreviation not set."); + return Abbrevs[recordID]; +} + +void AbbreviationMap::clear() { Abbrevs.clear(); } + +void ClangDocBinaryWriter::emitHeader(BitstreamWriter &Stream) { + // Emit the file header. + Stream.Emit((unsigned)'D', 8); + Stream.Emit((unsigned)'O', 8); + Stream.Emit((unsigned)'C', 8); + Stream.Emit((unsigned)'S', 8); +} + +/// \brief Emits a block ID in the BLOCKINFO block. +void ClangDocBinaryWriter::emitBlockID(unsigned ID, const char *Name, + BitstreamWriter &Stream) { + Record.clear(); + Record.push_back(ID); + Stream.EmitRecord(llvm::bitc::BLOCKINFO_CODE_SETBID, Record); + + // Emit the block name if present. + if (!Name || Name[0] == 0) return; + Record.clear(); + while (*Name) Record.push_back(*Name++); + Stream.EmitRecord(llvm::bitc::BLOCKINFO_CODE_BLOCKNAME, Record); +} + +/// \brief Emits a record ID in the BLOCKINFO block. +void ClangDocBinaryWriter::emitRecordID(unsigned ID, const char *Name, + BitstreamWriter &Stream) { + Record.clear(); + Record.push_back(ID); + while (*Name) Record.push_back(*Name++); + Stream.EmitRecord(llvm::bitc::BLOCKINFO_CODE_SETRECORDNAME, Record); +} + +// Common Abbreviations + +void ClangDocBinaryWriter::emitStringAbbrev(unsigned D, unsigned Block, + BitstreamWriter &Stream) { + auto Abbrev = std::make_shared(); + Abbrev->Add(BitCodeAbbrevOp(D)); + Abbrev->Add(BitCodeAbbrevOp(BitCodeAbbrevOp::Fixed, 16)); // String size + Abbrev->Add(BitCodeAbbrevOp(BitCodeAbbrevOp::Blob)); // String + Abbrevs.set(D, Stream.EmitBlockInfoAbbrev(Block, Abbrev)); +} + +void ClangDocBinaryWriter::emitLocationAbbrev(unsigned D, unsigned Block, + BitstreamWriter &Stream) { + auto Abbrev = std::make_shared(); + Abbrev->Add(BitCodeAbbrevOp(D)); + Abbrev->Add(BitCodeAbbrevOp(BitCodeAbbrevOp::Fixed, 16)); // Line number + Abbrev->Add(BitCodeAbbrevOp(BitCodeAbbrevOp::Fixed, 16)); // Filename size + Abbrev->Add(BitCodeAbbrevOp(BitCodeAbbrevOp::Blob)); // Filename + Abbrevs.set(D, Stream.EmitBlockInfoAbbrev(Block, Abbrev)); +} + +void ClangDocBinaryWriter::emitIntAbbrev(unsigned D, unsigned Block, + BitstreamWriter &Stream) { + auto Abbrev = std::make_shared(); + Abbrev->Add(BitCodeAbbrevOp(D)); + Abbrev->Add(BitCodeAbbrevOp(BitCodeAbbrevOp::Fixed, 16)); // Integer + Abbrevs.set(D, Stream.EmitBlockInfoAbbrev(Block, Abbrev)); +} + +// Common Records + +void ClangDocBinaryWriter::emitStringRecord(StringRef Str, unsigned RecordId, + BitstreamWriter &Stream) { + if (Str.empty()) return; + Record.clear(); + Record.push_back(RecordId); + Record.push_back(Str.size()); + Stream.EmitRecordWithBlob(Abbrevs.get(RecordId), Record, Str); +} + +void ClangDocBinaryWriter::emitLocationRecord(int LineNumber, StringRef File, + unsigned RecordId, + BitstreamWriter &Stream) { + if (OmitFilenames) return; + Record.clear(); + Record.push_back(RecordId); + Record.push_back(LineNumber); + Record.push_back(File.size()); + Stream.EmitRecordWithBlob(Abbrevs.get(RecordId), Record, File); +} + +void ClangDocBinaryWriter::emitIntRecord(int Value, unsigned RecordId, + BitstreamWriter &Stream) { + if (!Value) return; + Record.clear(); + Record.push_back(RecordId); + Record.push_back(Value); + Stream.EmitRecordWithAbbrev(Abbrevs.get(RecordId), Record); +} + +// Common Blocks + +void ClangDocBinaryWriter::emitNamedTypeBlock(const NamedType &N, int ID, + BitstreamWriter &Stream) { + Stream.EnterSubblock(NAMED_TYPE_BLOCK_ID, 5); + emitIntRecord(ID, NAMED_TYPE_ID, Stream); + emitStringRecord(N.Type, NAMED_TYPE_TYPE, Stream); + emitStringRecord(N.Name, NAMED_TYPE_NAME, Stream); + emitIntRecord(N.Access, NAMED_TYPE_ACCESS, Stream); + Stream.ExitBlock(); +} + +void ClangDocBinaryWriter::emitCommentBlock(const CommentInfo *I, + BitstreamWriter &Stream) { + Stream.EnterSubblock(COMMENT_BLOCK_ID, 5); + emitStringRecord(I->Kind, COMMENT_KIND, Stream); + emitStringRecord(I->Text, COMMENT_TEXT, Stream); + emitStringRecord(I->Name, COMMENT_NAME, Stream); + emitStringRecord(I->Direction, COMMENT_DIRECTION, Stream); + emitStringRecord(I->ParamName, COMMENT_PARAMNAME, Stream); + emitStringRecord(I->CloseName, COMMENT_CLOSENAME, Stream); + emitIntRecord(I->SelfClosing, COMMENT_SELFCLOSING, Stream); + emitIntRecord(I->Explicit, COMMENT_EXPLICIT, Stream); + for (const auto &A : I->AttrKeys) + emitStringRecord(A, COMMENT_ATTRKEY, Stream); + for (const auto &A : I->AttrValues) + emitStringRecord(A, COMMENT_ATTRVAL, Stream); + for (const auto &A : I->Args) emitStringRecord(A, COMMENT_ARG, Stream); + for (const auto &P : I->Position) + emitStringRecord(P, COMMENT_POSITION, Stream); + for (const auto &C : I->Children) emitCommentBlock(C.get(), Stream); + Stream.ExitBlock(); +} + +void ClangDocBinaryWriter::emitBlockInfoBlock(BitstreamWriter &Stream) { + Abbrevs.clear(); + emitHeader(Stream); + Stream.EnterBlockInfoBlock(); + + // Comment Block + emitBlockID(COMMENT_BLOCK_ID, "CommentBlock", Stream); + emitRecordID(COMMENT_KIND, "Kind", Stream); + emitRecordID(COMMENT_TEXT, "Text", Stream); + emitRecordID(COMMENT_NAME, "Name", Stream); + emitRecordID(COMMENT_DIRECTION, "Direction", Stream); + emitRecordID(COMMENT_PARAMNAME, "ParamName", Stream); + emitRecordID(COMMENT_CLOSENAME, "CloseName", Stream); + emitRecordID(COMMENT_SELFCLOSING, "SelfClosing", Stream); + emitRecordID(COMMENT_EXPLICIT, "Explicit", Stream); + emitRecordID(COMMENT_ATTRKEY, "AtrrKey", Stream); + emitRecordID(COMMENT_ATTRVAL, "AttrVal", Stream); + emitRecordID(COMMENT_ARG, "Arg", Stream); + emitRecordID(COMMENT_POSITION, "Position", Stream); + emitStringAbbrev(COMMENT_KIND, COMMENT_BLOCK_ID, Stream); + emitStringAbbrev(COMMENT_TEXT, COMMENT_BLOCK_ID, Stream); + emitStringAbbrev(COMMENT_NAME, COMMENT_BLOCK_ID, Stream); + emitStringAbbrev(COMMENT_POSITION, COMMENT_BLOCK_ID, Stream); + emitStringAbbrev(COMMENT_DIRECTION, COMMENT_BLOCK_ID, Stream); + emitStringAbbrev(COMMENT_PARAMNAME, COMMENT_BLOCK_ID, Stream); + emitStringAbbrev(COMMENT_CLOSENAME, COMMENT_BLOCK_ID, Stream); + emitIntAbbrev(COMMENT_SELFCLOSING, COMMENT_BLOCK_ID, Stream); + emitIntAbbrev(COMMENT_EXPLICIT, COMMENT_BLOCK_ID, Stream); + emitStringAbbrev(COMMENT_ATTRKEY, COMMENT_BLOCK_ID, Stream); + emitStringAbbrev(COMMENT_ATTRVAL, COMMENT_BLOCK_ID, Stream); + emitStringAbbrev(COMMENT_ARG, COMMENT_BLOCK_ID, Stream); + + // NamedType Block + emitBlockID(NAMED_TYPE_BLOCK_ID, "NamedTypeBlock", Stream); + emitRecordID(NAMED_TYPE_ID, "ID", Stream); + emitRecordID(NAMED_TYPE_TYPE, "Type", Stream); + emitRecordID(NAMED_TYPE_NAME, "Name", Stream); + emitRecordID(NAMED_TYPE_ACCESS, "Access", Stream); + emitIntAbbrev(NAMED_TYPE_ID, NAMED_TYPE_BLOCK_ID, Stream); + emitStringAbbrev(NAMED_TYPE_TYPE, NAMED_TYPE_BLOCK_ID, Stream); + emitStringAbbrev(NAMED_TYPE_NAME, NAMED_TYPE_BLOCK_ID, Stream); + emitIntAbbrev(NAMED_TYPE_ACCESS, NAMED_TYPE_BLOCK_ID, Stream); + +#define INFORECORD(X) \ + emitRecordID(X##_FULLY_QUALIFIED_NAME, "FullyQualifiedName", Stream); \ + emitRecordID(X##_NAME, "Name", Stream); \ + emitRecordID(X##_NAMESPACE, "Namespace", Stream); + +#define INFOABBREV(X) \ + emitStringAbbrev(X##_FULLY_QUALIFIED_NAME, X##_BLOCK_ID, Stream); \ + emitStringAbbrev(X##_NAME, X##_BLOCK_ID, Stream); \ + emitStringAbbrev(X##_NAMESPACE, X##_BLOCK_ID, Stream); + + // Namespace Block + emitBlockID(NAMESPACE_BLOCK_ID, "NamespaceBlock", Stream); + INFORECORD(NAMESPACE) + INFOABBREV(NAMESPACE) + + // NonDef Block + emitBlockID(NONDEF_BLOCK_ID, "NonDefBlock", Stream); + INFORECORD(NONDEF) + emitRecordID(NONDEF_LOCATION, "Location", Stream); + emitRecordID(NONDEF_TYPE, "Type", Stream); + INFOABBREV(NONDEF) + emitIntAbbrev(NONDEF_TYPE, NONDEF_BLOCK_ID, Stream); + emitLocationAbbrev(NONDEF_LOCATION, NONDEF_BLOCK_ID, Stream); + + // Enum Block + emitBlockID(ENUM_BLOCK_ID, "EnumBlock", Stream); + INFORECORD(ENUM) + emitRecordID(ENUM_DEFLOCATION, "DefLocation", Stream); + emitRecordID(ENUM_LOCATION, "Location", Stream); + emitRecordID(ENUM_SCOPED, "Scoped", Stream); + INFOABBREV(ENUM) + emitLocationAbbrev(ENUM_DEFLOCATION, ENUM_BLOCK_ID, Stream); + emitLocationAbbrev(ENUM_LOCATION, ENUM_BLOCK_ID, Stream); + emitIntAbbrev(ENUM_SCOPED, ENUM_BLOCK_ID, Stream); + + // Record Block + emitBlockID(RECORD_BLOCK_ID, "RecordBlock", Stream); + INFORECORD(RECORD) + emitRecordID(RECORD_DEFLOCATION, "DefLocation", Stream); + emitRecordID(RECORD_LOCATION, "Location", Stream); + emitRecordID(RECORD_TAG_TYPE, "TagType", Stream); + emitRecordID(RECORD_PARENT, "Parent", Stream); + emitRecordID(RECORD_VPARENT, "VParent", Stream); + INFOABBREV(RECORD) + emitLocationAbbrev(RECORD_DEFLOCATION, RECORD_BLOCK_ID, Stream); + emitLocationAbbrev(RECORD_LOCATION, RECORD_BLOCK_ID, Stream); + emitIntAbbrev(RECORD_TAG_TYPE, RECORD_BLOCK_ID, Stream); + emitStringAbbrev(RECORD_PARENT, RECORD_BLOCK_ID, Stream); + emitStringAbbrev(RECORD_VPARENT, RECORD_BLOCK_ID, Stream); + + // Function Block + emitBlockID(FUNCTION_BLOCK_ID, "FunctionBlock", Stream); + INFORECORD(FUNCTION) + emitRecordID(FUNCTION_DEFLOCATION, "DefLocation", Stream); + emitRecordID(FUNCTION_LOCATION, "Location", Stream); + emitRecordID(FUNCTION_MANGLED_NAME, "MangledName", Stream); + emitRecordID(FUNCTION_PARENT, "Parent", Stream); + emitRecordID(FUNCTION_ACCESS, "Access", Stream); + INFOABBREV(FUNCTION) + emitLocationAbbrev(FUNCTION_DEFLOCATION, FUNCTION_BLOCK_ID, Stream); + emitLocationAbbrev(FUNCTION_LOCATION, FUNCTION_BLOCK_ID, Stream); + emitStringAbbrev(FUNCTION_MANGLED_NAME, FUNCTION_BLOCK_ID, Stream); + emitStringAbbrev(FUNCTION_PARENT, FUNCTION_BLOCK_ID, Stream); + emitIntAbbrev(FUNCTION_ACCESS, FUNCTION_BLOCK_ID, Stream); + +#undef INFORECORDS +#undef INFOABBREV + + Stream.ExitBlock(); +} + +// Info emission + +#define EMITINFO(X) \ + emitStringRecord(I.FullyQualifiedName, X##_FULLY_QUALIFIED_NAME, Stream); \ + emitStringRecord(I.SimpleName, X##_NAME, Stream); \ + emitStringRecord(I.Namespace, X##_NAMESPACE, Stream); \ + for (const auto &CI : I.Description) \ + emitCommentBlock(&CI, Stream); + +template <> +void ClangDocBinaryWriter::writeBitstream(const NamespaceInfo &I, + BitstreamWriter &Stream, + bool writeBlockInfo) { + if (writeBlockInfo) emitBlockInfoBlock(Stream); + Stream.EnterSubblock(NAMESPACE_BLOCK_ID, 5); + EMITINFO(NAMESPACE) + Stream.ExitBlock(); +} + +template <> +void ClangDocBinaryWriter::writeBitstream(const NonDefInfo &I, + BitstreamWriter &Stream, + bool writeBlockInfo) { + if (writeBlockInfo) emitBlockInfoBlock(Stream); + Stream.EnterSubblock(NONDEF_BLOCK_ID, 5); + EMITINFO(NONDEF) + emitIntRecord(I.Type, NONDEF_TYPE, Stream); + for (const auto &L : I.Loc) + emitLocationRecord(L.LineNumber, L.Filename, NONDEF_LOCATION, Stream); + Stream.ExitBlock(); +} + +template <> +void ClangDocBinaryWriter::writeBitstream(const EnumInfo &I, + BitstreamWriter &Stream, + bool writeBlockInfo) { + if (writeBlockInfo) emitBlockInfoBlock(Stream); + Stream.EnterSubblock(ENUM_BLOCK_ID, 5); + EMITINFO(ENUM) + emitLocationRecord(I.DefLoc.LineNumber, I.DefLoc.Filename, ENUM_DEFLOCATION, Stream); + for (const auto &L : I.Loc) + emitLocationRecord(L.LineNumber, L.Filename, ENUM_LOCATION, Stream); + emitIntRecord(I.Scoped, ENUM_SCOPED, Stream); + for (const auto &N : I.Members) + emitNamedTypeBlock(N, NamedType::MEMBER, Stream); + Stream.ExitBlock(); +} + +template <> +void ClangDocBinaryWriter::writeBitstream(const RecordInfo &I, + BitstreamWriter &Stream, + bool writeBlockInfo) { + if (writeBlockInfo) emitBlockInfoBlock(Stream); + Stream.EnterSubblock(RECORD_BLOCK_ID, 5); + EMITINFO(RECORD) + emitLocationRecord(I.DefLoc.LineNumber, I.DefLoc.Filename, RECORD_DEFLOCATION, Stream); + for (const auto &L : I.Loc) + emitLocationRecord(L.LineNumber, L.Filename, RECORD_LOCATION, Stream); + emitIntRecord(I.TagType, RECORD_TAG_TYPE, Stream); + for (const auto &N : I.Members) + emitNamedTypeBlock(N, NamedType::MEMBER, Stream); + for (const auto &P : I.Parents) emitStringRecord(P, RECORD_PARENT, Stream); + for (const auto &P : I.VirtualParents) + emitStringRecord(P, RECORD_VPARENT, Stream); + Stream.ExitBlock(); +} + +template <> +void ClangDocBinaryWriter::writeBitstream(const FunctionInfo &I, + BitstreamWriter &Stream, + bool writeBlockInfo) { + if (writeBlockInfo) emitBlockInfoBlock(Stream); + Stream.EnterSubblock(FUNCTION_BLOCK_ID, 5); + EMITINFO(FUNCTION) + emitLocationRecord(I.DefLoc.LineNumber, I.DefLoc.Filename, FUNCTION_DEFLOCATION, Stream); + for (const auto &L : I.Loc) + emitLocationRecord(L.LineNumber, L.Filename, FUNCTION_LOCATION, Stream); + emitStringRecord(I.MangledName, FUNCTION_MANGLED_NAME, Stream); + emitStringRecord(I.Parent, FUNCTION_PARENT, Stream); + emitNamedTypeBlock(I.ReturnType, NamedType::RETTYPE, Stream); + for (const auto &N : I.Params) + emitNamedTypeBlock(N, NamedType::PARAM, Stream); + Stream.ExitBlock(); +} + +#undef EMITINFO + +} // namespace doc +} // namespace clang Index: clang-doc/ClangDocMapper.h =================================================================== --- /dev/null +++ clang-doc/ClangDocMapper.h @@ -0,0 +1,95 @@ +//===-- ClangDocMapper.h - ClangDocMapper -----------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_DOC_CLANG_DOC_MAPPER_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_DOC_CLANG_DOC_MAPPER_H + +#include +#include +#include +#include "ClangDocBinary.h" +#include "ClangDocRepresentation.h" +#include "clang/AST/AST.h" +#include "clang/AST/ASTConsumer.h" +#include "clang/AST/ASTContext.h" +#include "clang/AST/CommentVisitor.h" +#include "clang/AST/RecursiveASTVisitor.h" +#include "clang/Frontend/ASTConsumers.h" +#include "clang/Frontend/FrontendActions.h" +#include "clang/Tooling/Execution.h" +#include "clang/Tooling/Tooling.h" +#include "llvm/ADT/SmallVector.h" +#include "llvm/Support/raw_ostream.h" + +using namespace clang::comments; +using namespace clang::tooling; + +namespace clang { +namespace doc { + +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: + StringRef getCommandName(unsigned CommandID) const; + bool isWhitespaceOnly(StringRef S) const; + + CommentInfo &CurrentCI; +}; + +class ClangDocMapper { + public: + ClangDocMapper(ClangDocBinaryWriter &Writer) : Writer(Writer) {} + + template + StringRef emitInfo(const C *D, const FullComment *FC, StringRef Key, + int LineNumber, StringRef File); + + private: + template + SmallString<2048> serialize(T &I) const; + void populateSymbolInfo(SymbolInfo &I, StringRef Name, StringRef SimpleName, + StringRef Namespace, const FullComment *C, + int LineNumber, StringRef File); + void populateFunctionInfo(FunctionInfo &I, const FunctionDecl *D, + StringRef Name, const FullComment *C, + int LineNumber, StringRef File); + template + StringRef serializeNonDefInfo(const C *D, StringRef Name, NonDefInfo::InfoType Type, + const FullComment *FC, int LineNumber, + StringRef File); + void parseFields(RecordInfo &I, const RecordDecl *D) const; + void parseEnumerators(EnumInfo &I, const EnumDecl *D) const; + void parseBases(RecordInfo &I, const CXXRecordDecl *D) const; + void parseParameters(FunctionInfo &I, const FunctionDecl *D) const; + void parseFullComment(const FullComment *C, CommentInfo &CI); + std::string getParentNamespace(const DeclContext *D) const; + + ClangDocBinaryWriter &Writer; +}; + +} // namespace doc +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_DOC_CLANG_DOC_MAPPER_H Index: clang-doc/ClangDocMapper.cpp =================================================================== --- /dev/null +++ clang-doc/ClangDocMapper.cpp @@ -0,0 +1,292 @@ +//===-- ClangDocMapper.cpp - ClangDoc Mapper ----------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "ClangDocMapper.h" +#include "llvm/Support/Path.h" +#include "llvm/Support/YAMLTraits.h" +#include "llvm/Support/raw_ostream.h" + +using namespace llvm; +using clang::comments::FullComment; + +namespace clang { +namespace doc { + +// ClangDocMapper + +template <> +StringRef ClangDocMapper::emitInfo(const NamespaceDecl *D, + const FullComment *FC, StringRef Name, + int LineNumber, StringRef File) { + NamespaceInfo I; + I.FullyQualifiedName = Name; + I.SimpleName = D->getNameAsString(); + I.Namespace = getParentNamespace(D); + if (FC) { + I.Description.emplace_back(CommentInfo{}); + parseFullComment(FC, I.Description.back()); + } + return serialize(I); +} + +template <> +StringRef ClangDocMapper::emitInfo(const RecordDecl *D, const FullComment *FC, + StringRef Name, int LineNumber, + StringRef File) { + if (!D->isThisDeclarationADefinition()) + return serializeNonDefInfo(D, Name, NonDefInfo::RECORD, FC, LineNumber, File); + RecordInfo I; + populateSymbolInfo(I, Name, D->getNameAsString(), getParentNamespace(D), FC, + LineNumber, File); + I.TagType = D->getTagKind(); + if (const auto *CXXR = dyn_cast(D)) parseBases(I, CXXR); + parseFields(I, D); + return serialize(I); +} + +template <> +StringRef ClangDocMapper::emitInfo(const FunctionDecl *D, const FullComment *FC, + StringRef Name, int LineNumber, + StringRef File) { + if (!D->isThisDeclarationADefinition()) + return serializeNonDefInfo(D, Name, NonDefInfo::FUNCTION, FC, LineNumber, File); + FunctionInfo I; + populateFunctionInfo(I, D, Name, FC, LineNumber, File); + I.Access = clang::AccessSpecifier::AS_none; + return serialize(I); +} + +template <> +StringRef ClangDocMapper::emitInfo(const CXXMethodDecl *D, + const FullComment *FC, StringRef Name, + int LineNumber, StringRef File) { + if (!D->isThisDeclarationADefinition()) + return serializeNonDefInfo(D, Name, NonDefInfo::FUNCTION, FC, LineNumber, File); + FunctionInfo I; + populateFunctionInfo(I, D, Name, FC, LineNumber, File); + I.Parent = D->getParent()->getQualifiedNameAsString(); + I.Access = D->getAccess(); + return serialize(I); +} + +template <> +StringRef ClangDocMapper::emitInfo(const EnumDecl *D, const FullComment *FC, + StringRef Name, int LineNumber, + StringRef File) { + if (!D->isThisDeclarationADefinition()) + return serializeNonDefInfo(D, Name, NonDefInfo::ENUM, FC, LineNumber, File); + EnumInfo I; + populateSymbolInfo(I, Name, D->getNameAsString(), getParentNamespace(D), FC, + LineNumber, File); + I.Scoped = D->isScoped(); + parseEnumerators(I, D); + return serialize(I); +} + +template +SmallString<2048> ClangDocMapper::serialize(T &I) const { + SmallString<2048> Buffer; + llvm::BitstreamWriter Stream(Buffer); + Writer.writeBitstream(I, Stream, /*writeBlockInfo=*/true); + return Buffer; +} + +void ClangDocMapper::parseFullComment(const FullComment *C, CommentInfo &CI) { + ClangDocCommentVisitor Visitor(CI); + Visitor.parseComment(C); +} + +void ClangDocMapper::populateSymbolInfo(SymbolInfo &I, StringRef Name, + StringRef SimpleName, + StringRef Namespace, + const FullComment *C, int LineNumber, + StringRef File) { + I.FullyQualifiedName = Name; + I.SimpleName = SimpleName; + I.Namespace = Namespace; + I.Loc.emplace_back(Location{LineNumber, File}); + I.DefLoc = Location{LineNumber, File}; + if (C) { + I.Description.emplace_back(CommentInfo()); + parseFullComment(C, I.Description.back()); + } +} + +void ClangDocMapper::populateFunctionInfo(FunctionInfo &I, + const FunctionDecl *D, StringRef Name, + const FullComment *C, int LineNumber, + StringRef File) { + populateSymbolInfo(I, D->getQualifiedNameAsString(), D->getNameAsString(), + getParentNamespace(D), C, LineNumber, File); + I.MangledName = Name; + NamedType N; + N.Type = D->getReturnType().getAsString(); + I.ReturnType = N; + parseParameters(I, D); +} + +template +StringRef ClangDocMapper::serializeNonDefInfo(const C *D, StringRef Name, NonDefInfo::InfoType Type, + const FullComment *FC, + int LineNumber, StringRef File) { + NonDefInfo I; + I.Type = Type; + I.FullyQualifiedName = Name; + I.SimpleName = D->getNameAsString(); + I.Namespace = getParentNamespace(D); + I.Loc.emplace_back(Location{LineNumber, File}); + if (FC) { + I.Description.emplace_back(CommentInfo()); + parseFullComment(FC, I.Description.back()); + } + return serialize(I); +} + +void ClangDocMapper::parseFields(RecordInfo &I, const RecordDecl *D) const { + for (const FieldDecl *F : D->fields()) { + NamedType N; + N.Type = F->getTypeSourceInfo()->getType().getAsString(); + N.Name = F->getQualifiedNameAsString(); + // FIXME: Set Access to the appropriate value. + I.Members.emplace_back(N); + } +} + +void ClangDocMapper::parseEnumerators(EnumInfo &I, const EnumDecl *D) const { + for (const EnumConstantDecl *E : D->enumerators()) { + NamedType N; + N.Type = E->getQualifiedNameAsString(); + // FIXME: Set Access to the appropriate value. + I.Members.emplace_back(N); + } +} + +void ClangDocMapper::parseParameters(FunctionInfo &I, + const FunctionDecl *D) const { + for (const ParmVarDecl *P : D->parameters()) { + NamedType N; + N.Type = P->getOriginalType().getAsString(); + N.Name = P->getQualifiedNameAsString(); + // FIXME: Set Access to the appropriate value. + I.Params.emplace_back(N); + } +} + +void ClangDocMapper::parseBases(RecordInfo &I, const CXXRecordDecl *D) const { + for (const CXXBaseSpecifier &B : D->bases()) { + if (!B.isVirtual()) I.Parents.push_back(B.getType().getAsString()); + } + for (const CXXBaseSpecifier &B : D->vbases()) + I.VirtualParents.push_back(B.getType().getAsString()); +} + +std::string ClangDocMapper::getParentNamespace(const DeclContext *D) const { + if (const auto *N = dyn_cast(D->getParent())) { + return N->getQualifiedNameAsString(); + } + return ""; +} + +// ClangDocCommentVisitor + +void ClangDocCommentVisitor::parseComment(const comments::Comment *C) { + CurrentCI.Kind = C->getCommentKindName(); + ConstCommentVisitor::visit(C); + for (comments::Comment *Child : + make_range(C->child_begin(), C->child_end())) { + CurrentCI.Children.emplace_back(std::make_shared()); + 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() && C->isParamIndexValid()) + CurrentCI.ParamName = C->getParamNameAsWritten(); +} + +void ClangDocCommentVisitor::visitTParamCommandComment( + const TParamCommandComment *C) { + if (C->hasParamName() && C->isPositionValid()) + CurrentCI.ParamName = C->getParamNameAsWritten(); + + if (C->isPositionValid()) { + for (unsigned i = 0, e = C->getDepth(); i < e; ++i) + CurrentCI.Position.push_back(std::to_string(C->getIndex(i))); + } +} + +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(); +} + +StringRef ClangDocCommentVisitor::getCommandName(unsigned CommandID) const { + const CommandInfo *Info = CommandTraits::getBuiltinCommandInfo(CommandID); + if (Info) return Info->Name; + // TODO: Add parsing for \file command. + return ""; +} + +bool ClangDocCommentVisitor::isWhitespaceOnly(StringRef S) const { + return std::all_of(S.begin(), S.end(), isspace); +} + +} // namespace doc +} // namespace clang Index: clang-doc/ClangDocRepresentation.h =================================================================== --- /dev/null +++ clang-doc/ClangDocRepresentation.h @@ -0,0 +1,112 @@ +///===-- ClangDocRepresentation.h - ClangDocRepresenation -------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_DOC_CLANG_DOC_REPRESENTATION_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_DOC_CLANG_DOC_REPRESENTATION_H + +#include +#include "clang/AST/Type.h" +#include "clang/Basic/Specifiers.h" +#include "llvm/ADT/DenseMap.h" +#include "llvm/ADT/DenseSet.h" +#include "llvm/ADT/SmallVector.h" + +using namespace llvm; + +namespace clang { +namespace doc { + +// A representation of a parsed comment. +struct CommentInfo { + std::string Kind; + std::string Text; + std::string Name; + std::string Direction; + std::string ParamName; + std::string CloseName; + bool SelfClosing = false; + bool Explicit = false; + llvm::SmallVector AttrKeys; + llvm::SmallVector AttrValues; + llvm::SmallVector Args; + llvm::SmallVector Position; + std::vector> Children; +}; + + +// TODO: Pull the CommentInfo for a parameter or member out of the record or +// function's CommentInfo. +// Info for named types (parameters, members). +struct NamedType { + enum FieldName { PARAM=1, MEMBER, RETTYPE }; + FieldName Field; + std::string Type; + std::string Name; + AccessSpecifier Access = clang::AccessSpecifier::AS_none; + llvm::SmallVector Description; +}; + +struct Location { + int LineNumber; + std::string Filename; +}; + +/// A base struct for Infos. +struct Info { + std::string FullyQualifiedName; + std::string SimpleName; + std::string Namespace; + llvm::SmallVector Description; +}; + +struct NamespaceInfo : public Info {}; + +struct SymbolInfo : public Info { + Location DefLoc; + llvm::SmallVector Loc; +}; + +struct NonDefInfo : public SymbolInfo { + enum InfoType { NAMESPACE, FUNCTION, RECORD, ENUM }; + InfoType Type; +}; + +// TODO: Expand to allow for documenting templating and default args. +// Info for functions. +struct FunctionInfo : public SymbolInfo { + std::string MangledName; + std::string Parent; + NamedType ReturnType; + llvm::SmallVector Params; + AccessSpecifier Access; +}; + +// TODO: Expand to allow for documenting templating, inheritance access, +// friend classes +// Info for types. +struct RecordInfo : public SymbolInfo { + TagTypeKind TagType; + llvm::SmallVector Members; + llvm::SmallVector Parents; + llvm::SmallVector VirtualParents; +}; + +// TODO: Expand to allow for documenting templating. +// Info for types. +struct EnumInfo : public SymbolInfo { + bool Scoped; + llvm::SmallVector Members; +}; + +// TODO: Add functionality to include separate markdown pages. + +} // namespace doc +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_DOC_CLANG_DOC_REPRESENTATION_H Index: clang-doc/tool/CMakeLists.txt =================================================================== --- /dev/null +++ clang-doc/tool/CMakeLists.txt @@ -0,0 +1,16 @@ +include_directories(${CMAKE_CURRENT_SOURCE_DIR}/..) + +add_clang_executable(clang-doc + ClangDocMain.cpp + ) + +target_link_libraries(clang-doc + PRIVATE + clangAST + clangASTMatchers + clangBasic + clangFrontend + clangDoc + clangTooling + clangToolingCore + ) Index: clang-doc/tool/ClangDocMain.cpp =================================================================== --- /dev/null +++ clang-doc/tool/ClangDocMain.cpp @@ -0,0 +1,125 @@ +//===-- ClangDocMain.cpp - Clangdoc -----------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include +#include "ClangDoc.h" +#include "ClangDocBinary.h" +#include "clang/AST/AST.h" +#include "clang/AST/Decl.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/ASTMatchers/ASTMatchersInternal.h" +#include "clang/Driver/Options.h" +#include "clang/Frontend/FrontendActions.h" +#include "clang/Tooling/CommonOptionsParser.h" +#include "clang/Tooling/Execution.h" +#include "clang/Tooling/StandaloneExecution.h" +#include "clang/Tooling/Tooling.h" +#include "llvm/ADT/APFloat.h" +#include "llvm/Support/FileSystem.h" +#include "llvm/Support/Path.h" +#include "llvm/Support/Process.h" +#include "llvm/Support/Signals.h" +#include "llvm/Support/raw_ostream.h" + +using namespace clang::ast_matchers; +using namespace clang::tooling; +using namespace clang; +using namespace llvm; + +namespace { + +static cl::extrahelp CommonHelp(CommonOptionsParser::HelpMessage); +static cl::OptionCategory ClangDocCategory("clang-doc options"); + +static cl::opt OutDirectory( + "docs", cl::desc("Directory for outputting docs."), cl::init("docs"), + cl::cat(ClangDocCategory)); + +static cl::opt Format( + "format", cl::desc("Format for outputting docs. (options are md)"), + cl::init("md"), cl::cat(ClangDocCategory)); + +static cl::opt DumpResult("dump", cl::desc("Dump results to stdout."), + cl::init(false), cl::cat(ClangDocCategory)); + +static cl::opt OmitFilenames("omit-filenames", + cl::desc("Omit filenames in output."), + cl::init(false), cl::cat(ClangDocCategory)); + +static cl::opt DoxygenOnly( + "doxygen", cl::desc("Use only doxygen-style comments to generate docs."), + cl::init(false), cl::cat(ClangDocCategory)); + +} // namespace + +int main(int argc, const char **argv) { + sys::PrintStackTraceOnErrorSignal(argv[0]); + std::error_code OK; + + auto Exec = clang::tooling::createExecutorFromCommandLineArgs( + argc, argv, ClangDocCategory); + + if (!Exec) { + errs() << toString(Exec.takeError()) << "\n"; + return 1; + } + + MatchFinder Finder; + ExecutionContext *ECtx = Exec->get()->getExecutionContext(); + doc::ClangDocBinaryWriter Writer(OmitFilenames); + + doc::ClangDocCallback NCallback("namespace", *ECtx, Writer); + Finder.addMatcher(namespaceDecl().bind("namespace"), &NCallback); + doc::ClangDocCallback RCallback("record", *ECtx, Writer); + Finder.addMatcher(recordDecl().bind("record"), &RCallback); + doc::ClangDocCallback ECallback("enum", *ECtx, Writer); + Finder.addMatcher(enumDecl().bind("enum"), &ECallback); + doc::ClangDocCallback MCallback("method", *ECtx, Writer); + Finder.addMatcher(cxxMethodDecl(isUserProvided()).bind("method"), &MCallback); + doc::ClangDocCallback FCallback("function", *ECtx, Writer); + Finder.addMatcher(functionDecl(unless(cxxMethodDecl())).bind("function"), + &FCallback); + + ArgumentsAdjuster ArgAdjuster; + if (!DoxygenOnly) + ArgAdjuster = combineAdjusters( + getInsertArgumentAdjuster("-fparse-all-comments", + tooling::ArgumentInsertPosition::BEGIN), + ArgAdjuster); + + // Mapping phase + errs() << "Mapping decls...\n"; + auto Err = + Exec->get()->execute(newFrontendActionFactory(&Finder), ArgAdjuster); + if (Err) errs() << toString(std::move(Err)) << "\n"; + + if (DumpResult) { + Exec->get()->getToolResults()->forEachResult([&](StringRef Key, + StringRef Value) { + SmallString<128> IRRootPath; + sys::path::native(OutDirectory, IRRootPath); + std::error_code DirectoryStatus = sys::fs::create_directories(IRRootPath); + if (DirectoryStatus != OK) { + errs() << "Unable to create documentation directories.\n"; + return; + } + sys::path::append(IRRootPath, Key + ".bc"); + std::error_code OutErrorInfo; + raw_fd_ostream OS(IRRootPath, OutErrorInfo, sys::fs::F_None); + if (OutErrorInfo != OK) { + errs() << "Error opening documentation file.\n"; + return; + } + OS << Value; + OS.close(); + }); + } + + return 0; +} Index: docs/clang-doc.rst =================================================================== --- /dev/null +++ docs/clang-doc.rst @@ -0,0 +1,62 @@ +=================== +Clang-Doc +=================== + +.. contents:: + +:program:`clang-doc` is a tool for generating C and C++ documenation from +source code and comments. + +The tool is in a very early development stage, so you might encounter bugs and +crashes. Submitting reports with information about how to reproduce the issue +to `the LLVM bugtracker `_ will definitely help the +project. If you have any ideas or suggestions, please to put a feature request +there. + +Use +===== + +:program:`clang-doc` is a `LibTooling +`_-based tool, and so requires a +compile command database for your project (for an example of how to do this +see `How To Setup Tooling For LLVM +`_). + +The tool can be used on a single file or multiple files as defined in +the compile commands database: + +.. code-block:: console + + $ clang-doc file.cpp -p /path/to/compile/commands + +This generates an intermediate representation of the declarations and their +associated information in the specified TUs, serialized to LLVM +bitcode. + +As currently implemented, the tool is only able to parse TUs that can be +stored in-memory. Future additions will extend the current framewwork to use +map-reduce frameworks to allow for use with large codebases. + +:program:`clang-doc` offers the following options: + +.. code-block:: console + + $ clang-doc --help + USAGE: clang-doc [options] [... ] + + OPTIONS: + + Generic Options: + + -help - Display available options (-help-hidden for more) + -help-list - Display list of available options (-help-list-hidden for more) + -version - Display the version of this program + + clang-doc options: + + -doxygen - Use only doxygen-style comments to generate docs. + -dump - Dump results to stdout. + -extra-arg= - Additional argument to append to the compiler command line + -extra-arg-before= - Additional argument to prepend to the compiler command line + -omit-filenames - Omit filenames in output. + -p= - Build path Index: test/CMakeLists.txt =================================================================== --- test/CMakeLists.txt +++ test/CMakeLists.txt @@ -41,6 +41,7 @@ clang-apply-replacements clang-change-namespace clangd + clang-doc clang-include-fixer clang-move clang-query Index: test/clang-doc/mapper-class.cpp =================================================================== --- /dev/null +++ test/clang-doc/mapper-class.cpp @@ -0,0 +1,14 @@ +// RUN: rm -rf %t +// RUN: mkdir %t +// RUN: echo "" > %t/compile_flags.txt +// RUN: cp "%s" "%t/test.cpp" +// RUN: clang-doc --dump --omit-filenames -doxygen -p %t %t/test.cpp -docs=%t/docs +// RUN: llvm-bcanalyzer %t/docs/E.bc --dump | FileCheck %s + +class E {}; +// CHECK: +// CHECK: + // CHECK: blob data = 'E' + // CHECK: blob data = 'E' + // CHECK: +// CHECK: Index: test/clang-doc/mapper-enum.cpp =================================================================== --- /dev/null +++ test/clang-doc/mapper-enum.cpp @@ -0,0 +1,23 @@ +// RUN: rm -rf %t +// RUN: mkdir %t +// RUN: echo "" > %t/compile_flags.txt +// RUN: cp "%s" "%t/test.cpp" +// RUN: clang-doc --dump --omit-filenames -doxygen -p %t %t/test.cpp -docs=%t/docs +// RUN: llvm-bcanalyzer %t/docs/B.bc --dump | FileCheck %s + +enum B { X, Y }; +// CHECK: +// CHECK: + // CHECK: blob data = 'B' + // CHECK: blob data = 'B' + // CHECK: + // CHECK: + // CHECK: blob data = 'X' + // CHECK: + // CHECK: + // CHECK: + // CHECK: + // CHECK: blob data = 'Y' + // CHECK: + // CHECK: +// CHECK: Index: test/clang-doc/mapper-namespace.cpp =================================================================== --- /dev/null +++ test/clang-doc/mapper-namespace.cpp @@ -0,0 +1,13 @@ +// RUN: rm -rf %t +// RUN: mkdir %t +// RUN: echo "" > %t/compile_flags.txt +// RUN: cp "%s" "%t/test.cpp" +// RUN: clang-doc --dump --omit-filenames -doxygen -p %t %t/test.cpp -docs=%t/docs +// RUN: llvm-bcanalyzer %t/docs/A.bc --dump | FileCheck %s + +namespace A {} +// CHECK: +// CHECK: + // CHECK: blob data = 'A' + // CHECK: blob data = 'A' +// CHECK: Index: test/clang-doc/mapper-struct.cpp =================================================================== --- /dev/null +++ test/clang-doc/mapper-struct.cpp @@ -0,0 +1,19 @@ +// RUN: rm -rf %t +// RUN: mkdir %t +// RUN: echo "" > %t/compile_flags.txt +// RUN: cp "%s" "%t/test.cpp" +// RUN: clang-doc --dump --omit-filenames -doxygen -p %t %t/test.cpp -docs=%t/docs +// RUN: llvm-bcanalyzer %t/docs/C.bc --dump | FileCheck %s + +struct C { int i; }; +// CHECK: +// CHECK: + // CHECK: blob data = 'C' + // CHECK: blob data = 'C' + // CHECK: + // CHECK: + // CHECK: blob data = 'int' + // CHECK: blob data = 'C::i' + // CHECK: + // CHECK: +// CHECK: Index: test/clang-doc/mapper-union.cpp =================================================================== --- /dev/null +++ test/clang-doc/mapper-union.cpp @@ -0,0 +1,26 @@ +// RUN: rm -rf %t +// RUN: mkdir %t +// RUN: echo "" > %t/compile_flags.txt +// RUN: cp "%s" "%t/test.cpp" +// RUN: clang-doc --dump --omit-filenames -doxygen -p %t %t/test.cpp -docs=%t/docs +// RUN: llvm-bcanalyzer %t/docs/A.bc --dump | FileCheck %s + +union A { int X; int Y; }; +// CHECK: +// CHECK: + // CHECK: blob data = 'A' + // CHECK: blob data = 'A' + // CHECK: + // CHECK: + // CHECK: + // CHECK: blob data = 'int' + // CHECK: blob data = 'A::X' + // CHECK: + // CHECK: + // CHECK: + // CHECK: + // CHECK: blob data = 'int' + // CHECK: blob data = 'A::Y' + // CHECK: + // CHECK: +// CHECK: