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/BitcodeWriter.h =================================================================== --- /dev/null +++ clang-doc/BitcodeWriter.h @@ -0,0 +1,170 @@ +//===-- BitcodeWriter.h - ClangDoc Bitcode Writer --------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// This file implements a writer for serializing the clang-doc internal +// representation to LLVM bitcode. The writer takes in a stream and emits the +// generated bitcode to that stream. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_DOC_CLANG_DOC_BINARY_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_DOC_CLANG_DOC_BINARY_H + +#include +#include +#include "Representation.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 { + +// Current version number of clang-doc bitcode. +// Should be bumped when changing BlockIds, RecordIds, or BitCodeConstants +static const unsigned VERSION_NUMBER = 0; + +struct BitCodeConstants { + static constexpr int SubblockIDSize = 5; + static constexpr int LineNumFixedSize = 16; + static constexpr int SignatureBitSize = 8; +}; + +enum BlockId { + BI_VERSION_BLOCK_ID = bitc::FIRST_APPLICATION_BLOCKID, + BI_NAMESPACE_BLOCK_ID, + BI_ENUM_BLOCK_ID, + BI_TYPE_BLOCK_ID, + BI_FIELD_TYPE_BLOCK_ID, + BI_MEMBER_TYPE_BLOCK_ID, + BI_RECORD_BLOCK_ID, + BI_FUNCTION_BLOCK_ID, + BI_COMMENT_BLOCK_ID, + BI_FIRST = BI_VERSION_BLOCK_ID, + BI_LAST = BI_COMMENT_BLOCK_ID +}; + +#define INFORECORDS(X) X##_NAME, X##_NAMESPACE + +enum RecordId { + VERSION = 1, + COMMENT_KIND, + COMMENT_TEXT, + COMMENT_NAME, + COMMENT_POSITION, + COMMENT_DIRECTION, + COMMENT_PARAMNAME, + COMMENT_CLOSENAME, + COMMENT_SELFCLOSING, + COMMENT_EXPLICIT, + COMMENT_ATTRKEY, + COMMENT_ATTRVAL, + COMMENT_ARG, + TYPE_TYPE, + FIELD_TYPE_TYPE, + FIELD_TYPE_NAME, + MEMBER_TYPE_TYPE, + MEMBER_TYPE_NAME, + MEMBER_TYPE_ACCESS, + INFORECORDS(NAMESPACE), + INFORECORDS(ENUM), + ENUM_ISDEFINITION, + ENUM_LOCATION, + ENUM_SCOPED, + INFORECORDS(RECORD), + RECORD_ISDEFINITION, + RECORD_LOCATION, + RECORD_TAG_TYPE, + RECORD_PARENT, + RECORD_VPARENT, + INFORECORDS(FUNCTION), + FUNCTION_ISDEFINITION, + FUNCTION_LOCATION, + FUNCTION_MANGLED_NAME, + FUNCTION_PARENT, + FUNCTION_ACCESS, + RI_FIRST = VERSION, + RI_LAST = FUNCTION_ACCESS +}; + +#undef INFORECORDS + +class ClangDocBitcodeWriter { + public: + ClangDocBitcodeWriter(BitstreamWriter &Stream, bool OmitFilenames = false) + : Stream(Stream), OmitFilenames(OmitFilenames) {} + + using RecordData = SmallVector; + + void writeBitstream(const NamespaceInfo &I, bool writeBlockInfo = false); + void writeBitstream(const RecordInfo &I, bool writeBlockInfo = false); + void writeBitstream(const FunctionInfo &I, bool writeBlockInfo = false); + void writeBitstream(const EnumInfo &I, bool writeBlockInfo = false); + + private: + class AbbreviationMap { + llvm::DenseMap Abbrevs; + + public: + AbbreviationMap() {} + void add(RecordId RID, unsigned abbrevID); + unsigned get(RecordId RID) const; + void clear(); + }; + + class StreamSubBlockGuard { + BitstreamWriter &Stream; + + public: + StreamSubBlockGuard(BitstreamWriter &Stream_, BlockId ID) + : Stream(Stream_) { + Stream.EnterSubblock(ID, BitCodeConstants::SubblockIDSize); + } + + StreamSubBlockGuard() = default; + StreamSubBlockGuard(const StreamSubBlockGuard &) = delete; + StreamSubBlockGuard &operator=(const StreamSubBlockGuard &) = delete; + + ~StreamSubBlockGuard() { Stream.ExitBlock(); } + }; + + void emitBlockInfoBlock(); + void emitHeader(); + void emitVersion(); + void emitRecordID(RecordId ID); + void emitBlockID(BlockId ID); + + void emitStringRecord(StringRef Str, RecordId ID); + void emitLocationRecord(int LineNumber, StringRef File, RecordId ID); + void emitIntRecord(int Value, RecordId ID); + void emitTypeBlock(const TypeInfo &N); + void emitMemberTypeBlock(const MemberTypeInfo &N); + void emitFieldTypeBlock(const FieldTypeInfo &N); + void emitCommentBlock(const std::unique_ptr &I); + + template + void emitAbbrev(RecordId ID, BlockId Block, Lambda &&L); + + void emitStringAbbrev(RecordId ID, BlockId Block); + void emitLocationAbbrev(RecordId ID, BlockId Block); + void emitIntAbbrev(RecordId ID, BlockId Block); + + RecordData Record; + BitstreamWriter &Stream; + bool OmitFilenames; + AbbreviationMap Abbrevs; +}; + +} // namespace doc +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_DOC_CLANG_DOC_BINARY_H Index: clang-doc/BitcodeWriter.cpp =================================================================== --- /dev/null +++ clang-doc/BitcodeWriter.cpp @@ -0,0 +1,415 @@ +//===-- BitcodeWriter.cpp - ClangDoc Bitcode Writer ------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "BitcodeWriter.h" +#include "llvm/ADT/IndexedMap.h" + +using namespace llvm; + +namespace clang { +namespace doc { + +// Since id enums are not zero-indexed, we need to transform the given id into +// its associated index. +struct BlockIdToIndexFunctor { + using argument_type = unsigned; + unsigned operator()(unsigned ID) const { return ID - BI_FIRST; } +}; + +struct RecordIdToIndexFunctor { + using argument_type = unsigned; + unsigned operator()(unsigned ID) const { return ID - RI_FIRST; } +}; + +static const llvm::IndexedMap BlockIdNameMap = + []() { + llvm::IndexedMap BlockIdNameMap; + static constexpr unsigned ExpectedSize = BI_LAST - BI_FIRST + 1; + BlockIdNameMap.resize(ExpectedSize); + + // There is no init-list constructor for the IndexedMap, so have to + // improvise + static constexpr std::initializer_list< + std::pair> + inits = {{BI_VERSION_BLOCK_ID, "VersionBlock"}, + {BI_NAMESPACE_BLOCK_ID, "NamespaceBlock"}, + {BI_ENUM_BLOCK_ID, "EnumBlock"}, + {BI_TYPE_BLOCK_ID, "TypeBlock"}, + {BI_FIELD_TYPE_BLOCK_ID, "FieldTypeBlock"}, + {BI_MEMBER_TYPE_BLOCK_ID, "MemberTypeBlock"}, + {BI_RECORD_BLOCK_ID, "RecordBlock"}, + {BI_FUNCTION_BLOCK_ID, "FunctionBlock"}, + {BI_COMMENT_BLOCK_ID, "CommentBlock"}}; + static_assert(inits.size() == ExpectedSize, + "unexpected count of initializers"); + for (const auto &init : inits) BlockIdNameMap[init.first] = init.second; + assert(BlockIdNameMap.size() == ExpectedSize); + return BlockIdNameMap; + }(); + +static const llvm::IndexedMap + RecordIdNameMap = []() { + llvm::IndexedMap RecordIdNameMap; + static constexpr unsigned ExpectedSize = RI_LAST - RI_FIRST + 1; + RecordIdNameMap.resize(ExpectedSize); + + // There is no init-list constructor for the IndexedMap, so have to + // improvise + static constexpr std::initializer_list< + std::pair> + inits = {{VERSION, "Version"}, + {COMMENT_KIND, "Kind"}, + {COMMENT_TEXT, "Text"}, + {COMMENT_NAME, "Name"}, + {COMMENT_POSITION, "Position"}, + {COMMENT_DIRECTION, "Direction"}, + {COMMENT_PARAMNAME, "ParamName"}, + {COMMENT_CLOSENAME, "CloseName"}, + {COMMENT_SELFCLOSING, "SelfClosing"}, + {COMMENT_EXPLICIT, "Explicit"}, + {COMMENT_ATTRKEY, "AttrKey"}, + {COMMENT_ATTRVAL, "AttrVal"}, + {COMMENT_ARG, "Arg"}, + {TYPE_TYPE, "Type"}, + {FIELD_TYPE_TYPE, "Type"}, + {FIELD_TYPE_NAME, "Name"}, + {MEMBER_TYPE_TYPE, "Type"}, + {MEMBER_TYPE_NAME, "Name"}, + {MEMBER_TYPE_ACCESS, "Access"}, + {NAMESPACE_NAME, "Name"}, + {NAMESPACE_NAMESPACE, "Namespace"}, + {ENUM_NAME, "Name"}, + {ENUM_NAMESPACE, "Namespace"}, + {ENUM_ISDEFINITION, "IsDefinition"}, + {ENUM_LOCATION, "Location"}, + {ENUM_SCOPED, "Scoped"}, + {RECORD_NAME, "Name"}, + {RECORD_NAMESPACE, "Namespace"}, + {RECORD_ISDEFINITION, "IsDefinition"}, + {RECORD_LOCATION, "Location"}, + {RECORD_TAG_TYPE, "TagType"}, + {RECORD_PARENT, "Parent"}, + {RECORD_VPARENT, "VParent"}, + {FUNCTION_NAME, "Name"}, + {FUNCTION_NAMESPACE, "Namespace"}, + {FUNCTION_ISDEFINITION, "IsDefinition"}, + {FUNCTION_LOCATION, "Location"}, + {FUNCTION_ISDEFINITION, "IsDefinition"}, + {FUNCTION_PARENT, "Parent"}, + {FUNCTION_ACCESS, "Access"}}; + static_assert(inits.size() == ExpectedSize, + "unexpected count of initializers"); + for (const auto &init : inits) RecordIdNameMap[init.first] = init.second; + assert(RecordIdNameMap.size() == ExpectedSize); + return RecordIdNameMap; + }(); + +void ClangDocBitcodeWriter::AbbreviationMap::add(RecordId RID, + unsigned AbbrevID) { + assert(Abbrevs.find(RID) == Abbrevs.end() && "Abbreviation already added."); + Abbrevs[RID] = AbbrevID; +} + +unsigned ClangDocBitcodeWriter::AbbreviationMap::get(RecordId RID) const { + assert(Abbrevs.find(RID) != Abbrevs.end() && "Unknown abbreviation."); + return Abbrevs.lookup(RID); +} + +void ClangDocBitcodeWriter::AbbreviationMap::clear() { Abbrevs.clear(); } + +void ClangDocBitcodeWriter::emitHeader() { + // Emit the file header. + for (char c : StringRef("DOCS")) + Stream.Emit((unsigned)c, BitCodeConstants::SignatureBitSize); +} + +void ClangDocBitcodeWriter::emitVersion() { + // Emit the version number + StreamSubBlockGuard Block(Stream, BI_VERSION_BLOCK_ID); + Stream.EmitRecord(VERSION, ArrayRef{VERSION_NUMBER}); +} + +/// \brief Emits a block ID in the BLOCKINFO block. +void ClangDocBitcodeWriter::emitBlockID(BlockId ID) { + Record.clear(); + Record.push_back(ID); + Stream.EmitRecord(llvm::bitc::BLOCKINFO_CODE_SETBID, Record); + + Record.clear(); + for (const char C : BlockIdNameMap[ID]) Record.push_back(C); + Stream.EmitRecord(llvm::bitc::BLOCKINFO_CODE_BLOCKNAME, Record); +} + +/// \brief Emits a record ID in the BLOCKINFO block. +void ClangDocBitcodeWriter::emitRecordID(RecordId ID) { + Record.clear(); + Record.push_back(ID); + for (const char C : RecordIdNameMap[ID]) Record.push_back(C); + Stream.EmitRecord(llvm::bitc::BLOCKINFO_CODE_SETRECORDNAME, Record); +} + +// Common Abbreviations + +template +void ClangDocBitcodeWriter::emitAbbrev(RecordId ID, BlockId Block, Lambda &&L) { + auto Abbrev = std::make_shared(); + Abbrev->Add(BitCodeAbbrevOp(ID)); + L(Abbrev); + Abbrevs.add(ID, Stream.EmitBlockInfoAbbrev(Block, std::move(Abbrev))); +} + +void ClangDocBitcodeWriter::emitStringAbbrev(RecordId ID, BlockId Block) { + auto EmitString = [](std::shared_ptr &Abbrev) { + Abbrev->Add( + BitCodeAbbrevOp(BitCodeAbbrevOp::Fixed, + BitCodeConstants::LineNumFixedSize)); // String size + Abbrev->Add(BitCodeAbbrevOp(BitCodeAbbrevOp::Blob)); // String + }; + emitAbbrev(ID, Block, EmitString); +} + +void ClangDocBitcodeWriter::emitLocationAbbrev(RecordId ID, BlockId Block) { + auto EmitString = [](std::shared_ptr &Abbrev) { + Abbrev->Add( + BitCodeAbbrevOp(BitCodeAbbrevOp::Fixed, + BitCodeConstants::LineNumFixedSize)); // Line number + Abbrev->Add( + BitCodeAbbrevOp(BitCodeAbbrevOp::Fixed, + BitCodeConstants::LineNumFixedSize)); // Filename size + Abbrev->Add(BitCodeAbbrevOp(BitCodeAbbrevOp::Blob)); // Filename + }; + emitAbbrev(ID, Block, EmitString); +} + +void ClangDocBitcodeWriter::emitIntAbbrev(RecordId ID, BlockId Block) { + auto EmitString = [](std::shared_ptr &Abbrev) { + Abbrev->Add( + BitCodeAbbrevOp(BitCodeAbbrevOp::Fixed, + BitCodeConstants::LineNumFixedSize)); // Integer + }; + emitAbbrev(ID, Block, EmitString); +} + +// Common Records + +void ClangDocBitcodeWriter::emitStringRecord(StringRef Str, RecordId ID) { + if (Str.empty()) return; + Record.clear(); + Record.push_back(ID); + Record.push_back(Str.size()); + Stream.EmitRecordWithBlob(Abbrevs.get(ID), Record, Str); +} + +void ClangDocBitcodeWriter::emitLocationRecord(int LineNumber, StringRef File, + RecordId ID) { + if (OmitFilenames) return; + Record.clear(); + Record.push_back(ID); + Record.push_back(LineNumber); + Record.push_back(File.size()); + Stream.EmitRecordWithBlob(Abbrevs.get(ID), Record, File); +} + +void ClangDocBitcodeWriter::emitIntRecord(int Value, RecordId ID) { + if (!Value) return; + Record.clear(); + Record.push_back(ID); + Record.push_back(Value); + Stream.EmitRecordWithAbbrev(Abbrevs.get(ID), Record); +} + +// Common Blocks + +void ClangDocBitcodeWriter::emitTypeBlock(const TypeInfo &T) { + StreamSubBlockGuard Block(Stream, BI_FIELD_TYPE_BLOCK_ID); + emitStringRecord(T.TypeUSR, FIELD_TYPE_TYPE); + for (const auto &CI : T.Description) emitCommentBlock(CI); +} + +void ClangDocBitcodeWriter::emitFieldTypeBlock(const FieldTypeInfo &T) { + StreamSubBlockGuard Block(Stream, BI_FIELD_TYPE_BLOCK_ID); + emitStringRecord(T.TypeUSR, FIELD_TYPE_TYPE); + emitStringRecord(T.Name, FIELD_TYPE_NAME); + for (const auto &CI : T.Description) emitCommentBlock(CI); +} + +void ClangDocBitcodeWriter::emitMemberTypeBlock(const MemberTypeInfo &T) { + StreamSubBlockGuard Block(Stream, BI_MEMBER_TYPE_BLOCK_ID); + emitStringRecord(T.TypeUSR, MEMBER_TYPE_TYPE); + emitStringRecord(T.Name, MEMBER_TYPE_NAME); + emitIntRecord(T.Access, MEMBER_TYPE_ACCESS); + for (const auto &CI : T.Description) emitCommentBlock(CI); +} + +void ClangDocBitcodeWriter::emitCommentBlock( + const std::unique_ptr &I) { + StreamSubBlockGuard Block(Stream, BI_COMMENT_BLOCK_ID); + for (const auto &L : std::initializer_list>{ + {I->Text, COMMENT_TEXT}, + {I->Name, COMMENT_NAME}, + {I->Direction, COMMENT_DIRECTION}, + {I->ParamName, COMMENT_PARAMNAME}, + {I->CloseName, COMMENT_CLOSENAME}}) + emitStringRecord(L.first, L.second); + emitIntRecord(I->SelfClosing, COMMENT_SELFCLOSING); + emitIntRecord(I->Explicit, COMMENT_EXPLICIT); + for (const auto &A : I->AttrKeys) emitStringRecord(A, COMMENT_ATTRKEY); + for (const auto &A : I->AttrValues) emitStringRecord(A, COMMENT_ATTRVAL); + for (const auto &A : I->Args) emitStringRecord(A, COMMENT_ARG); + for (const auto &P : I->Position) emitStringRecord(P, COMMENT_POSITION); + for (const auto &C : I->Children) emitCommentBlock(C); +} + +void ClangDocBitcodeWriter::emitBlockInfoBlock() { + Abbrevs.clear(); + emitHeader(); + Stream.EnterBlockInfoBlock(); + + // Version block + emitBlockID(BI_VERSION_BLOCK_ID); + emitRecordID(VERSION); + + // Comment Block + emitBlockID(BI_COMMENT_BLOCK_ID); + for (RecordId RID : {COMMENT_KIND, COMMENT_TEXT, COMMENT_NAME, + COMMENT_DIRECTION, COMMENT_PARAMNAME, COMMENT_CLOSENAME, + COMMENT_SELFCLOSING, COMMENT_EXPLICIT, COMMENT_ATTRKEY, + COMMENT_ATTRVAL, COMMENT_ARG, COMMENT_POSITION}) + emitRecordID(RID); + for (RecordId RID : + {COMMENT_KIND, COMMENT_TEXT, COMMENT_NAME, COMMENT_DIRECTION, + COMMENT_PARAMNAME, COMMENT_CLOSENAME, COMMENT_ATTRKEY, COMMENT_ATTRVAL, + COMMENT_ARG, COMMENT_POSITION}) + emitStringAbbrev(RID, BI_COMMENT_BLOCK_ID); + emitIntAbbrev(COMMENT_SELFCLOSING, BI_COMMENT_BLOCK_ID); + emitIntAbbrev(COMMENT_EXPLICIT, BI_COMMENT_BLOCK_ID); + + // TypeInfo Block + emitBlockID(BI_TYPE_BLOCK_ID); + emitRecordID(TYPE_TYPE); + emitStringAbbrev(TYPE_TYPE, BI_TYPE_BLOCK_ID); + + // FieldTypeInfo Block + emitBlockID(BI_FIELD_TYPE_BLOCK_ID); + emitRecordID(FIELD_TYPE_TYPE); + emitRecordID(FIELD_TYPE_NAME); + emitStringAbbrev(FIELD_TYPE_TYPE, BI_FIELD_TYPE_BLOCK_ID); + emitStringAbbrev(FIELD_TYPE_NAME, BI_FIELD_TYPE_BLOCK_ID); + + // MemberTypeInfo Block + emitBlockID(BI_MEMBER_TYPE_BLOCK_ID); + for (RecordId RID : {MEMBER_TYPE_TYPE, MEMBER_TYPE_NAME, MEMBER_TYPE_ACCESS}) + emitRecordID(RID); + emitStringAbbrev(MEMBER_TYPE_TYPE, BI_MEMBER_TYPE_BLOCK_ID); + emitStringAbbrev(MEMBER_TYPE_NAME, BI_MEMBER_TYPE_BLOCK_ID); + emitIntAbbrev(MEMBER_TYPE_ACCESS, BI_MEMBER_TYPE_BLOCK_ID); + + // Namespace Block + emitBlockID(BI_NAMESPACE_BLOCK_ID); + emitRecordID(NAMESPACE_NAME); + emitRecordID(NAMESPACE_NAMESPACE); + emitStringAbbrev(NAMESPACE_NAME, BI_NAMESPACE_BLOCK_ID); + emitStringAbbrev(NAMESPACE_NAMESPACE, BI_NAMESPACE_BLOCK_ID); + + // Enum Block + emitBlockID(BI_ENUM_BLOCK_ID); + for (RecordId RID : {ENUM_NAME, ENUM_NAMESPACE, ENUM_ISDEFINITION, + ENUM_LOCATION, ENUM_SCOPED}) + emitRecordID(RID); + emitStringAbbrev(ENUM_NAME, BI_ENUM_BLOCK_ID); + emitStringAbbrev(ENUM_NAMESPACE, BI_ENUM_BLOCK_ID); + emitLocationAbbrev(ENUM_LOCATION, BI_ENUM_BLOCK_ID); + emitIntAbbrev(ENUM_ISDEFINITION, BI_ENUM_BLOCK_ID); + emitIntAbbrev(ENUM_SCOPED, BI_ENUM_BLOCK_ID); + + // Record Block + emitBlockID(BI_RECORD_BLOCK_ID); + for (RecordId RID : + {RECORD_NAME, RECORD_NAMESPACE, RECORD_ISDEFINITION, RECORD_LOCATION, + RECORD_TAG_TYPE, RECORD_PARENT, RECORD_VPARENT}) + emitRecordID(RID); + for (RecordId RID : + {RECORD_NAME, RECORD_NAMESPACE, RECORD_PARENT, RECORD_VPARENT}) + emitStringAbbrev(RID, BI_RECORD_BLOCK_ID); + emitLocationAbbrev(RECORD_LOCATION, BI_RECORD_BLOCK_ID); + emitIntAbbrev(RECORD_ISDEFINITION, BI_RECORD_BLOCK_ID); + emitIntAbbrev(RECORD_TAG_TYPE, BI_RECORD_BLOCK_ID); + + // Function Block + emitBlockID(BI_FUNCTION_BLOCK_ID); + for (RecordId RID : {FUNCTION_NAME, FUNCTION_NAMESPACE, FUNCTION_ISDEFINITION, + FUNCTION_LOCATION, FUNCTION_PARENT, FUNCTION_ACCESS}) + emitRecordID(RID); + for (RecordId RID : {FUNCTION_NAME, FUNCTION_NAMESPACE, FUNCTION_PARENT}) + emitStringAbbrev(RID, BI_FUNCTION_BLOCK_ID); + emitLocationAbbrev(FUNCTION_LOCATION, BI_FUNCTION_BLOCK_ID); + emitIntAbbrev(FUNCTION_ISDEFINITION, BI_FUNCTION_BLOCK_ID); + emitIntAbbrev(FUNCTION_ACCESS, BI_FUNCTION_BLOCK_ID); + + Stream.ExitBlock(); + emitVersion(); +} + +// Info emission + +#define EMITINFO(X) \ + emitStringRecord(I.Name, X##_NAME); \ + for (const auto &N : I.Namespace) emitStringRecord(N, X##_NAMESPACE); \ + for (const auto &CI : I.Description) emitCommentBlock(CI); + +void ClangDocBitcodeWriter::writeBitstream(const NamespaceInfo &I, + bool writeBlockInfo) { + if (writeBlockInfo) emitBlockInfoBlock(); + StreamSubBlockGuard Block(Stream, BI_NAMESPACE_BLOCK_ID); + EMITINFO(NAMESPACE) +} + +void ClangDocBitcodeWriter::writeBitstream(const EnumInfo &I, + bool writeBlockInfo) { + if (writeBlockInfo) emitBlockInfoBlock(); + StreamSubBlockGuard Block(Stream, BI_ENUM_BLOCK_ID); + EMITINFO(ENUM) + emitIntRecord(I.IsDefinition, ENUM_ISDEFINITION); + for (const auto &L : I.Loc) + emitLocationRecord(L.LineNumber, L.Filename, ENUM_LOCATION); + emitIntRecord(I.Scoped, ENUM_SCOPED); + for (const auto &N : I.Members) emitTypeBlock(N); +} + +void ClangDocBitcodeWriter::writeBitstream(const RecordInfo &I, + bool writeBlockInfo) { + if (writeBlockInfo) emitBlockInfoBlock(); + StreamSubBlockGuard Block(Stream, BI_RECORD_BLOCK_ID); + EMITINFO(RECORD) + emitIntRecord(I.IsDefinition, RECORD_ISDEFINITION); + for (const auto &L : I.Loc) + emitLocationRecord(L.LineNumber, L.Filename, RECORD_LOCATION); + emitIntRecord(I.TagType, RECORD_TAG_TYPE); + for (const auto &N : I.Members) emitMemberTypeBlock(N); + for (const auto &P : I.ParentUSRs) emitStringRecord(P, RECORD_PARENT); + for (const auto &P : I.VirtualParentUSRs) emitStringRecord(P, RECORD_VPARENT); +} + +void ClangDocBitcodeWriter::writeBitstream(const FunctionInfo &I, + bool writeBlockInfo) { + if (writeBlockInfo) emitBlockInfoBlock(); + StreamSubBlockGuard Block(Stream, BI_FUNCTION_BLOCK_ID); + EMITINFO(FUNCTION) + emitIntRecord(I.IsDefinition, FUNCTION_ISDEFINITION); + for (const auto &L : I.Loc) + emitLocationRecord(L.LineNumber, L.Filename, FUNCTION_LOCATION); + emitStringRecord(I.ParentUSR, FUNCTION_PARENT); + emitTypeBlock(I.ReturnType); + for (const auto &N : I.Params) emitFieldTypeBlock(N); +} + +#undef EMITINFO + +} // namespace doc +} // namespace clang Index: clang-doc/CMakeLists.txt =================================================================== --- /dev/null +++ clang-doc/CMakeLists.txt @@ -0,0 +1,21 @@ +set(LLVM_LINK_COMPONENTS + support + ) + +add_clang_library(clangDoc + BitcodeWriter.cpp + Mapper.cpp + + LINK_LIBS + clangAnalysis + clangAST + clangASTMatchers + clangBasic + clangFrontend + clangIndex + clangLex + clangTooling + clangToolingCore + ) + +add_subdirectory(tool) Index: clang-doc/ClangDoc.h =================================================================== --- /dev/null +++ clang-doc/ClangDoc.h @@ -0,0 +1,81 @@ +//===-- 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. +// +//===----------------------------------------------------------------------===// +// +// This file implements the main entry point for the clang-doc tool. It runs +// the clang-doc mapper on a given set of source code files using a +// FrontendActionFactory. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_DOC_CLANGDOC_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_DOC_CLANGDOC_H + +#include +#include +#include "BitcodeWriter.h" +#include "Mapper.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/Frontend/ASTConsumers.h" +#include "clang/Frontend/CompilerInstance.h" +#include "clang/Frontend/FrontendActions.h" +#include "clang/Tooling/Tooling.h" +#include "llvm/Support/raw_ostream.h" + +namespace clang { +namespace doc { + +class MapperActionFactory : public tooling::FrontendActionFactory { + public: + MapperActionFactory(tooling::ExecutionContext *ECtx, bool OmitFilenames) + : ECtx(ECtx), OmitFilenames(OmitFilenames) {} + + clang::FrontendAction *create() override { + class ClangDocConsumer : public clang::ASTConsumer { + public: + ClangDocConsumer(ASTContext *Ctx, ExecutionContext *ECtx, + bool OmitFilenames) + : Mapper(Ctx, ECtx, OmitFilenames){}; + void HandleTranslationUnit(clang::ASTContext &Context) override { + Mapper.TraverseDecl(Context.getTranslationUnitDecl()); + } + + private: + ClangDocMapper Mapper; + }; + + class ClangDocAction : public clang::ASTFrontendAction { + public: + ClangDocAction(ExecutionContext *ECtx, bool OmitFilenames) + : ECtx(ECtx), OmitFilenames(OmitFilenames) {} + + std::unique_ptr CreateASTConsumer( + clang::CompilerInstance &Compiler, llvm::StringRef InFile) override { + return llvm::make_unique(&Compiler.getASTContext(), + ECtx, OmitFilenames); + } + + private: + ExecutionContext *ECtx; + bool OmitFilenames; + }; + return new ClangDocAction(ECtx, OmitFilenames); + } + + tooling::ExecutionContext *ECtx; + bool OmitFilenames; +}; + +} // namespace doc +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_DOC_CLANGDOC_H Index: clang-doc/Mapper.h =================================================================== --- /dev/null +++ clang-doc/Mapper.h @@ -0,0 +1,139 @@ +//===-- Mapper.h - ClangDoc Mapper ------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// This file implements the Mapper piece of the clang-doc tool. It implements +// a RecursiveASTVisitor to look at each declaration and populate the info +// into the internal representation. Each seen declaration is serialized to +// to bitcode and written out to the ExecutionContext as a KV pair where the +// key is the declaration's USR and the value is the serialized bitcode. +// +//===----------------------------------------------------------------------===// + +#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 "Representation.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 ClangDocMapper : public clang::RecursiveASTVisitor { + public: + explicit ClangDocMapper(ASTContext *Ctx, ExecutionContext *ECtx, + bool OmitFilenames) + : ECtx(ECtx), Serializer(OmitFilenames) {} + + bool VisitNamespaceDecl(const NamespaceDecl *D); + bool VisitRecordDecl(const RecordDecl *D); + bool VisitEnumDecl(const EnumDecl *D); + bool VisitCXXMethodDecl(const CXXMethodDecl *D); + bool VisitFunctionDecl(const FunctionDecl *D); + + private: + 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; + }; + + class ClangDocSerializer { + public: + ClangDocSerializer(bool OmitFilenames) : OmitFilenames(OmitFilenames) {} + + std::string emitInfo(const NamespaceDecl *D, const FullComment *FC, + int LineNumber, StringRef File); + std::string emitInfo(const RecordDecl *D, const FullComment *FC, + int LineNumber, StringRef File); + std::string emitInfo(const EnumDecl *D, const FullComment *FC, + int LineNumber, StringRef File); + std::string emitInfo(const FunctionDecl *D, const FullComment *FC, + int LineNumber, StringRef File); + std::string emitInfo(const CXXMethodDecl *D, const FullComment *FC, + int LineNumber, StringRef File); + + private: + template + std::string serialize(T &I); + + void populateInfo(Info &I, const NamedDecl *D, const FullComment *C); + void populateSymbolInfo(SymbolInfo &I, const NamedDecl *D, + const FullComment *C, int LineNumber, + StringRef File); + void populateFunctionInfo(FunctionInfo &I, const FunctionDecl *D, + const FullComment *C, 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); + + template + void populateParentNamespaces(llvm::SmallVector &Namespaces, + const T *D); + std::string getUSRForType(const Type *T) const; + std::string getUSRForDecl(const Decl *D) const; + RecordDecl *getDeclForType(const QualType &T) const; + + bool OmitFilenames; + }; + + template + bool mapDecl(const T *D); + + int getLine(const NamedDecl *D, const ASTContext &Context) const; + StringRef getFile(const NamedDecl *D, const ASTContext &Context) const; + comments::FullComment *getComment(const NamedDecl *D, + const ASTContext &Context) const; + + ExecutionContext *ECtx; + ClangDocSerializer Serializer; +}; + +} // namespace doc +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_DOC_CLANG_DOC_MAPPER_H Index: clang-doc/Mapper.cpp =================================================================== --- /dev/null +++ clang-doc/Mapper.cpp @@ -0,0 +1,373 @@ +//===-- Mapper.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 "Mapper.h" +#include "BitcodeWriter.h" +#include "clang/AST/Comment.h" +#include "clang/AST/Mangle.h" +#include "clang/Index/USRGeneration.h" + +using namespace llvm; +using clang::comments::FullComment; + +namespace clang { +namespace doc { + +// ClangDocMapper::ClangDocSerializer + +std::string ClangDocMapper::ClangDocSerializer::emitInfo(const NamespaceDecl *D, + const FullComment *FC, + int LineNumber, + StringRef File) { + NamespaceInfo I; + populateInfo(I, D, FC); + populateParentNamespaces(I.Namespace, D); + return serialize(I); +} + +std::string ClangDocMapper::ClangDocSerializer::emitInfo(const RecordDecl *D, + const FullComment *FC, + int LineNumber, + StringRef File) { + RecordInfo I; + if (D->isThisDeclarationADefinition()) I.IsDefinition = true; + populateSymbolInfo(I, D, FC, LineNumber, File); + populateParentNamespaces(I.Namespace, D); + I.TagType = D->getTagKind(); + if (const auto *CXXR = dyn_cast(D)) parseBases(I, CXXR); + parseFields(I, D); + return serialize(I); +} + +std::string ClangDocMapper::ClangDocSerializer::emitInfo(const FunctionDecl *D, + const FullComment *FC, + int LineNumber, + StringRef File) { + FunctionInfo I; + if (D->isThisDeclarationADefinition()) I.IsDefinition = true; + populateFunctionInfo(I, D, FC, LineNumber, File); + populateParentNamespaces(I.Namespace, D); + I.Access = clang::AccessSpecifier::AS_none; + return serialize(I); +} + +std::string ClangDocMapper::ClangDocSerializer::emitInfo(const CXXMethodDecl *D, + const FullComment *FC, + int LineNumber, + StringRef File) { + FunctionInfo I; + if (D->isThisDeclarationADefinition()) I.IsDefinition = true; + populateFunctionInfo(I, D, FC, LineNumber, File); + populateParentNamespaces(I.Namespace, D); + I.ParentUSR = getUSRForDecl(D->getParent()); + I.Access = D->getAccess(); + return serialize(I); +} + +std::string ClangDocMapper::ClangDocSerializer::emitInfo(const EnumDecl *D, + const FullComment *FC, + int LineNumber, + StringRef File) { + EnumInfo I; + if (D->isThisDeclarationADefinition()) I.IsDefinition = true; + populateSymbolInfo(I, D, FC, LineNumber, File); + populateParentNamespaces(I.Namespace, D); + I.Scoped = D->isScoped(); + parseEnumerators(I, D); + return serialize(I); +} + +template +std::string ClangDocMapper::ClangDocSerializer::serialize(T &I) { + SmallString<2048> Buffer; + llvm::BitstreamWriter Stream(Buffer); + ClangDocBitcodeWriter Writer(Stream, OmitFilenames); + Writer.writeBitstream(I, /*writeBlockInfo=*/true); + return Buffer.str().str(); +} + +void ClangDocMapper::ClangDocSerializer::parseFullComment(const FullComment *C, + CommentInfo &CI) { + ClangDocCommentVisitor Visitor(CI); + Visitor.parseComment(C); +} + +void ClangDocMapper::ClangDocSerializer::populateInfo(Info &I, + const NamedDecl *D, + const FullComment *C) { + I.Name = D->getNameAsString(); + if (C) { + I.Description.emplace_back(llvm::make_unique()); + parseFullComment(C, *I.Description.back()); + } +} + +void ClangDocMapper::ClangDocSerializer::populateSymbolInfo( + SymbolInfo &I, const NamedDecl *D, const FullComment *C, int LineNumber, + StringRef File) { + populateInfo(I, D, C); + I.Loc.emplace_back(Location{LineNumber, File}); +} + +void ClangDocMapper::ClangDocSerializer::populateFunctionInfo( + FunctionInfo &I, const FunctionDecl *D, const FullComment *FC, + int LineNumber, StringRef File) { + populateSymbolInfo(I, D, FC, LineNumber, File); + if (const auto *T = getDeclForType(D->getReturnType())) + I.ReturnType.TypeUSR = getUSRForDecl(T); + else + I.ReturnType.TypeUSR = D->getReturnType().getAsString(); + // TODO: Populate return type comment description from info description. + parseParameters(I, D); +} + +void ClangDocMapper::ClangDocSerializer::parseFields( + RecordInfo &I, const RecordDecl *D) const { + for (const FieldDecl *F : D->fields()) { + // FIXME: Set Access to the appropriate value. + std::string Type; + if (const auto *D = getDeclForType(F->getTypeSourceInfo()->getType())) + Type = getUSRForDecl(D); + else + Type = F->getTypeSourceInfo()->getType().getAsString(); + I.Members.emplace_back(MemberTypeInfo{Type, F->getQualifiedNameAsString()}); + } + // TODO: Populate type comment description from info description. +} + +void ClangDocMapper::ClangDocSerializer::parseEnumerators( + EnumInfo &I, const EnumDecl *D) const { + for (const EnumConstantDecl *E : D->enumerators()) + I.Members.emplace_back(TypeInfo{E->getQualifiedNameAsString()}); + // TODO: Populate member comment description from info description. +} + +void ClangDocMapper::ClangDocSerializer::parseParameters( + FunctionInfo &I, const FunctionDecl *D) const { + for (const ParmVarDecl *P : D->parameters()) { + std::string Type; + if (const auto *D = getDeclForType(P->getOriginalType())) + Type = getUSRForDecl(D); + else + Type = P->getOriginalType().getAsString(); + I.Params.emplace_back(FieldTypeInfo{Type, P->getQualifiedNameAsString()}); + // TODO: Populate field comment description from info description. + } +} + +void ClangDocMapper::ClangDocSerializer::parseBases( + RecordInfo &I, const CXXRecordDecl *D) const { + for (const CXXBaseSpecifier &B : D->bases()) { + if (B.isVirtual()) continue; + if (const auto *P = getDeclForType(B.getType())) + I.ParentUSRs.emplace_back(getUSRForDecl(P)); + else + I.ParentUSRs.emplace_back(B.getType().getAsString()); + } + for (const CXXBaseSpecifier &B : D->vbases()) { + if (const auto *P = getDeclForType(B.getType())) + I.VirtualParentUSRs.emplace_back(getUSRForDecl(P)); + else + I.ParentUSRs.emplace_back(B.getType().getAsString()); + } +} + +RecordDecl *ClangDocMapper::ClangDocSerializer::getDeclForType( + const QualType &T) const { + auto *Ty = T->getAs(); + if (!Ty) return nullptr; + return Ty->getDecl()->getDefinition(); +} + +template +void ClangDocMapper::ClangDocSerializer::populateParentNamespaces( + llvm::SmallVector &Namespaces, const T *D) { + const NamespaceDecl *N; + // Get the initial namespace parent from passed-in decl. + if (const auto *C = dyn_cast(D)) { + N = dyn_cast(C->getParent()); + if (!N) return; + Namespaces.push_back(getUSRForDecl(N)); + } + + // Get all parents until the top level namespace. + while (const auto *C = dyn_cast(N)) { + N = dyn_cast(C->getParent()); + if (!N) break; + Namespaces.push_back(getUSRForDecl(N)); + } +} + +std::string ClangDocMapper::ClangDocSerializer::getUSRForDecl( + const Decl *D) const { + llvm::SmallString<128> USR; + if (index::generateUSRForDecl(D, USR)) return ""; + return USR.str().str(); +} + +// ClangDocCommentVisitor + +void ClangDocMapper::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(llvm::make_unique()); + ClangDocCommentVisitor Visitor(*CurrentCI.Children.back()); + Visitor.parseComment(Child); + } +} + +void ClangDocMapper::ClangDocCommentVisitor::visitTextComment( + const TextComment *C) { + if (!isWhitespaceOnly(C->getText())) CurrentCI.Text = C->getText(); +} + +void ClangDocMapper::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 ClangDocMapper::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 ClangDocMapper::ClangDocCommentVisitor::visitHTMLEndTagComment( + const HTMLEndTagComment *C) { + CurrentCI.Name = C->getTagName(); + CurrentCI.SelfClosing = true; +} + +void ClangDocMapper::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 ClangDocMapper::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 ClangDocMapper::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 ClangDocMapper::ClangDocCommentVisitor::visitVerbatimBlockComment( + const VerbatimBlockComment *C) { + CurrentCI.Name = getCommandName(C->getCommandID()); + CurrentCI.CloseName = C->getCloseName(); +} + +void ClangDocMapper::ClangDocCommentVisitor::visitVerbatimBlockLineComment( + const VerbatimBlockLineComment *C) { + if (!isWhitespaceOnly(C->getText())) CurrentCI.Text = C->getText(); +} + +void ClangDocMapper::ClangDocCommentVisitor::visitVerbatimLineComment( + const VerbatimLineComment *C) { + if (!isWhitespaceOnly(C->getText())) CurrentCI.Text = C->getText(); +} + +std::string ClangDocMapper::ClangDocCommentVisitor::getCommandName( + unsigned CommandID) const { + const CommandInfo *Info = CommandTraits::getBuiltinCommandInfo(CommandID); + if (Info) return Info->Name; + // TODO: Add parsing for \file command. + return ""; +} + +bool ClangDocMapper::ClangDocCommentVisitor::isWhitespaceOnly( + StringRef S) const { + return std::all_of(S.begin(), S.end(), isspace); +} + +// ClangDocMapper + +template +bool ClangDocMapper::mapDecl(const T *D) { + if (!D->getASTContext().getSourceManager().isWrittenInMainFile( + D->getLocation())) + return false; + + llvm::SmallString<128> USR; + if (index::generateUSRForDecl(D, USR)) return false; + + ECtx->reportResult(USR, + Serializer.emitInfo(D, getComment(D, D->getASTContext()), + getLine(D, D->getASTContext()), + getFile(D, D->getASTContext()))); + return true; +} + +bool ClangDocMapper::VisitNamespaceDecl(const NamespaceDecl *D) { + return mapDecl(D); +} + +bool ClangDocMapper::VisitRecordDecl(const RecordDecl *D) { return mapDecl(D); } + +bool ClangDocMapper::VisitEnumDecl(const EnumDecl *D) { return mapDecl(D); } + +bool ClangDocMapper::VisitCXXMethodDecl(const CXXMethodDecl *D) { + return mapDecl(D); +} + +bool ClangDocMapper::VisitFunctionDecl(const FunctionDecl *D) { + // Don't visit CXXMethodDecls twice + if (dyn_cast(D)) return true; + return mapDecl(D); +} + +comments::FullComment *ClangDocMapper::getComment( + const NamedDecl *D, const ASTContext &Context) 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 ClangDocMapper::getLine(const NamedDecl *D, + const ASTContext &Context) const { + return Context.getSourceManager().getPresumedLoc(D->getLocStart()).getLine(); +} + +StringRef ClangDocMapper::getFile(const NamedDecl *D, + const ASTContext &Context) const { + return Context.getSourceManager() + .getPresumedLoc(D->getLocStart()) + .getFilename(); +} + +} // namespace doc +} // namespace clang Index: clang-doc/Representation.h =================================================================== --- /dev/null +++ clang-doc/Representation.h @@ -0,0 +1,126 @@ +///===-- Representation.h - ClangDoc Represenation --------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// This file defines the internal representations of different declaration +// types for the clang-doc tool. +// +//===----------------------------------------------------------------------===// + +#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" +#include "llvm/ADT/StringExtras.h" +#include "llvm/Support/SHA1.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 type out of the info's CommentInfo. +// A base struct for TypeInfos +struct TypeInfo { + TypeInfo() {} + TypeInfo(StringRef TypeUSR) : TypeUSR(TypeUSR) {} + + std::string TypeUSR; + std::vector> Description; +}; + +// Info for field types. +struct FieldTypeInfo : public TypeInfo { + FieldTypeInfo() {} + FieldTypeInfo(StringRef TypeUSR) : TypeInfo(TypeUSR) {} + FieldTypeInfo(StringRef TypeUSR, StringRef Name) + : TypeInfo(TypeUSR), Name(Name) {} + std::string Name; +}; + +// Info for member types. +struct MemberTypeInfo : public FieldTypeInfo { + MemberTypeInfo() {} + MemberTypeInfo(StringRef TypeUSR, StringRef Name) + : FieldTypeInfo(TypeUSR, Name) {} + AccessSpecifier Access = clang::AccessSpecifier::AS_none; +}; + +struct Location { + int LineNumber; + std::string Filename; +}; + +/// A base struct for Infos. +struct Info { + std::string Name; + llvm::SmallVector Namespace; + std::vector> Description; +}; + +struct NamespaceInfo : public Info {}; + +struct SymbolInfo : public Info { + bool IsDefinition = false; + llvm::SmallVector Loc; +}; + +// TODO: Expand to allow for documenting templating and default args. +// Info for functions. +struct FunctionInfo : public SymbolInfo { + std::string ParentUSR; + TypeInfo 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 ParentUSRs; + llvm::SmallVector VirtualParentUSRs; +}; + +// 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,112 @@ +//===-- 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. +// +//===----------------------------------------------------------------------===// +// +// This tool for generating C and C++ documenation from source code +// and comments. +// +//===----------------------------------------------------------------------===// + +#include +#include "ClangDoc.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( + "output", cl::desc("Directory for outputting generated files."), + cl::init("docs"), cl::cat(ClangDocCategory)); + +static cl::opt DumpResult( + "dump", cl::desc("Dump intermediate results to bitcode file."), + 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; + } + + ArgumentsAdjuster ArgAdjuster; + if (!DoxygenOnly) + ArgAdjuster = combineAdjusters( + getInsertArgumentAdjuster("-fparse-all-comments", + tooling::ArgumentInsertPosition::BEGIN), + ArgAdjuster); + + // Mapping phase + outs() << "Mapping decls...\n"; + auto Err = Exec->get()->execute( + llvm::make_unique( + Exec->get()->getExecutionContext(), OmitFilenames), + 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 /path/to/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 framework 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 intermediate results to bitcode file. + -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. + -output= - Directory for outputting generated files. + -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,18 @@ +// 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 -output=%t/docs +// RUN: llvm-bcanalyzer %t/docs/c:@S@E.bc --dump | FileCheck %s + +class E {}; +// CHECK: +// CHECK: + // CHECK: +// CHECK: +// CHECK: + // CHECK: blob data = 'E' + // CHECK: + // CHECK: +// CHECK: + Index: test/clang-doc/mapper-enum.cpp =================================================================== --- /dev/null +++ test/clang-doc/mapper-enum.cpp @@ -0,0 +1,25 @@ +// 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 -output=%t/docs +// RUN: llvm-bcanalyzer %t/docs/c:@E@B.bc --dump | FileCheck %s + +enum B { X, Y }; +// CHECK: +// CHECK: + // CHECK: +// CHECK: +// CHECK: + // CHECK: blob data = 'B' + // CHECK: + // CHECK: + // CHECK: blob data = 'X' + // CHECK: + // CHECK: + // CHECK: blob data = 'Y' + // CHECK: +// CHECK: + + + Index: test/clang-doc/mapper-function.cpp =================================================================== --- /dev/null +++ test/clang-doc/mapper-function.cpp @@ -0,0 +1,24 @@ +// 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 -output=%t/docs +// RUN: llvm-bcanalyzer %t/docs/c:@F@F#I#.bc --dump | FileCheck %s + +int F(int param) { return param; } +// CHECK: +// CHECK: + // CHECK: +// CHECK: +// CHECK: + // CHECK: blob data = 'F' + // CHECK: + // CHECK: + // CHECK: blob data = 'int' + // CHECK: + // CHECK: + // CHECK: blob data = 'int' + // CHECK: blob data = 'param' + // CHECK: +// CHECK: + Index: test/clang-doc/mapper-method.cpp =================================================================== --- /dev/null +++ test/clang-doc/mapper-method.cpp @@ -0,0 +1,31 @@ +// 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 -output=%t/docs +// RUN: llvm-bcanalyzer %t/docs/c:@S@G@F@Method#I#.bc --dump | FileCheck %s + +class G { +public: + int Method(int param) { return param; } +}; +// CHECK: +// CHECK: + // CHECK: +// CHECK: +// CHECK: + // CHECK: blob data = 'Method' + // CHECK: + // CHECK: blob data = 'c:@S@G' + // CHECK: + // CHECK: blob data = 'int' + // CHECK: + // CHECK: + // CHECK: blob data = 'int' + // CHECK: blob data = 'param' + // CHECK: +// CHECK: + + + + Index: test/clang-doc/mapper-namespace.cpp =================================================================== --- /dev/null +++ test/clang-doc/mapper-namespace.cpp @@ -0,0 +1,17 @@ +// 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 -output=%t/docs +// RUN: llvm-bcanalyzer %t/docs/c:@N@A.bc --dump | FileCheck %s + +namespace A {} +// CHECK: +// CHECK: + // CHECK: +// CHECK: +// CHECK: + // CHECK: blob data = 'A' +// CHECK: + + Index: test/clang-doc/mapper-struct.cpp =================================================================== --- /dev/null +++ test/clang-doc/mapper-struct.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 -output=%t/docs +// RUN: llvm-bcanalyzer %t/docs/c:@S@C.bc --dump | FileCheck %s + +struct C { int i; }; +// CHECK: +// CHECK: + // CHECK: +// CHECK: +// CHECK: + // 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,29 @@ +// 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 -output=%t/docs +// RUN: llvm-bcanalyzer %t/docs/c:@U@D.bc --dump | FileCheck %s + +union D { int X; int Y; }; +// CHECK: +// CHECK: + // CHECK: +// CHECK: +// CHECK: + // CHECK: blob data = 'D' + // CHECK: + // CHECK: + // CHECK: + // CHECK: blob data = 'int' + // CHECK: blob data = 'D::X' + // CHECK: + // CHECK: + // CHECK: + // CHECK: blob data = 'int' + // CHECK: blob data = 'D::Y' + // CHECK: + // CHECK: +// CHECK: + +