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,20 @@ +set(LLVM_LINK_COMPONENTS + support + ) + +add_clang_library(clangDoc + 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,75 @@ +//===-- 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/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){}; + virtual void HandleTranslationUnit(clang::ASTContext &Context) { + Mapper.TraverseDecl(Context.getTranslationUnitDecl()); + } + + private: + ClangDocMapper Mapper; + }; + + class ClangDocAction : public clang::ASTFrontendAction { + public: + ClangDocAction(ExecutionContext *ECtx, bool OmitFilenames) + : ECtx(ECtx), OmitFilenames(OmitFilenames) {} + + virtual std::unique_ptr CreateASTConsumer( + clang::CompilerInstance &Compiler, llvm::StringRef InFile) { + 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/ClangDocBinary.h =================================================================== --- /dev/null +++ clang-doc/ClangDocBinary.h @@ -0,0 +1,161 @@ +//===-- ClangDocBinary.h - ClangDoc Bitstream 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 the binary writer for the clang-doc tool. The writer +// takes a bitcode stream and a top-level info struct describing a declaration +// and converts it to LLVM bitcode format. +//===----------------------------------------------------------------------===// + +#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 "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 { + +struct BitCodeConstants { + static constexpr int SubblockIDSize = 5; + static constexpr int LineNumFixedSize = 16; + static constexpr int SignatureBitSize = 8; +}; + +enum BlockId { + BI_NAMESPACE_BLOCK_ID = bitc::FIRST_APPLICATION_BLOCKID, + BI_NONDEF_BLOCK_ID, + BI_ENUM_BLOCK_ID, + BI_NAMED_TYPE_BLOCK_ID, + BI_RECORD_BLOCK_ID, + BI_FUNCTION_BLOCK_ID, + BI_COMMENT_BLOCK_ID, + BI_FIRST = BI_NAMESPACE_BLOCK_ID, + BI_LAST = BI_COMMENT_BLOCK_ID +}; + +#define INFORECORDS(X) X##_FULLY_QUALIFIED_NAME, X##_NAME, X##_NAMESPACE, + +enum RecordId { + 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, + INFORECORDS(NAMESPACE) INFORECORDS(NONDEF) NONDEF_TYPE, + NONDEF_LOCATION, + INFORECORDS(ENUM) ENUM_DEFLOCATION, + ENUM_LOCATION, + ENUM_SCOPED, + INFORECORDS(RECORD) RECORD_DEFLOCATION, + RECORD_LOCATION, + RECORD_TAG_TYPE, + RECORD_PARENT, + RECORD_VPARENT, + INFORECORDS(FUNCTION) FUNCTION_DEFLOCATION, + FUNCTION_LOCATION, + FUNCTION_MANGLED_NAME, + FUNCTION_PARENT, + FUNCTION_ACCESS, + RI_FIRST = COMMENT_KIND, + RI_LAST = FUNCTION_ACCESS +}; + +#undef INFORECORDS + +class ClangDocBinaryWriter { + public: + ClangDocBinaryWriter(bool OmitFilenames = false) + : OmitFilenames(OmitFilenames){}; + + using RecordData = SmallVector; + + void writeBitstream(const NamespaceInfo &I, BitstreamWriter &Stream, + bool writeBlockInfo = false); + void writeBitstream(const NonDefInfo &I, BitstreamWriter &Stream, + bool writeBlockInfo = false); + void writeBitstream(const RecordInfo &I, BitstreamWriter &Stream, + bool writeBlockInfo = false); + void writeBitstream(const FunctionInfo &I, BitstreamWriter &Stream, + bool writeBlockInfo = false); + void writeBitstream(const EnumInfo &I, BitstreamWriter &Stream, + bool writeBlockInfo = false); + + private: + class AbbreviationMap { + llvm::DenseMap Abbrevs; + + public: + AbbreviationMap() {} + void add(RecordId RID, unsigned abbrevID); + unsigned get(RecordId RID); + void clear(); + }; + + class StreamSubBlock { + BitstreamWriter &Stream; + + public: + StreamSubBlock(BitstreamWriter &Stream_, BlockId ID) : Stream(Stream_) { + Stream.EnterSubblock(ID, BitCodeConstants::SubblockIDSize); + } + + StreamSubBlock() = default; + StreamSubBlock(const StreamSubBlock &) = delete; + StreamSubBlock &operator=(const StreamSubBlock &) = delete; + + ~StreamSubBlock() { Stream.ExitBlock(); } + }; + + void emitBlockInfoBlock(BitstreamWriter &Stream); + void emitHeader(BitstreamWriter &Stream); + void emitRecordID(RecordId ID, BitstreamWriter &Stream); + void emitBlockID(BlockId ID, BitstreamWriter &Stream); + + void emitStringRecord(StringRef Str, RecordId ID, BitstreamWriter &Stream); + void emitLocationRecord(int LineNumber, StringRef File, RecordId ID, + BitstreamWriter &Stream); + void emitIntRecord(int Value, RecordId ID, BitstreamWriter &Stream); + void emitNamedTypeBlock(const NamedType &N, NamedType::FieldName ID, + BitstreamWriter &Stream); + void emitCommentBlock(const CommentInfo *I, BitstreamWriter &Stream); + + template + void emitAbbrev(RecordId ID, BlockId Block, Lambda &&L, + BitstreamWriter &Stream); + + void emitStringAbbrev(RecordId ID, BlockId Block, BitstreamWriter &Stream); + void emitLocationAbbrev(RecordId ID, BlockId Block, BitstreamWriter &Stream); + void emitIntAbbrev(RecordId ID, BlockId 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,454 @@ +//===-- 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" +#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; + BlockIdNameMap.resize(BI_LAST - BI_FIRST + 1); + + // There is no init-list constructor for the IndexedMap, so have to + // improvise + static const std::initializer_list> + inits = {{BI_NAMESPACE_BLOCK_ID, "NamespaceBlock"}, + {BI_NONDEF_BLOCK_ID, "NonDefBlock"}, + {BI_ENUM_BLOCK_ID, "EnumBlock"}, + {BI_NAMED_TYPE_BLOCK_ID, "NamedTypeBlock"}, + {BI_RECORD_BLOCK_ID, "RecordBlock"}, + {BI_FUNCTION_BLOCK_ID, "FunctionBlock"}, + {BI_COMMENT_BLOCK_ID, "CommentBlock"}}; + for (const auto &init : inits) { + BlockIdNameMap[init.first] = init.second; + } + return BlockIdNameMap; + }(); + +static const llvm::IndexedMap + RecordIdNameMap = []() { + llvm::IndexedMap RecordIdNameMap; + RecordIdNameMap.resize(RI_LAST - RI_FIRST + 1); + + // There is no init-list constructor for the IndexedMap, so have to + // improvise + static const std::initializer_list> + inits = {{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"}, + {NAMED_TYPE_ID, "ID"}, + {NAMED_TYPE_TYPE, "Type"}, + {NAMED_TYPE_NAME, "Name"}, + {NAMED_TYPE_ACCESS, "Access"}, + {NAMESPACE_FULLY_QUALIFIED_NAME, "FullyQualifiedName"}, + {NAMESPACE_NAME, "Name"}, + {NAMESPACE_NAMESPACE, "Namespace"}, + {NONDEF_FULLY_QUALIFIED_NAME, "FullyQualifiedName"}, + {NONDEF_NAME, "Name"}, + {NONDEF_NAMESPACE, "Namespace"}, + {NONDEF_LOCATION, "Location"}, + {NONDEF_TYPE, "Type"}, + {ENUM_FULLY_QUALIFIED_NAME, "FullyQualifiedName"}, + {ENUM_NAME, "Name"}, + {ENUM_NAMESPACE, "Namespace"}, + {ENUM_DEFLOCATION, "DefLocation"}, + {ENUM_LOCATION, "Location"}, + {ENUM_SCOPED, "Scoped"}, + {RECORD_FULLY_QUALIFIED_NAME, "FullyQualifiedName"}, + {RECORD_NAME, "Name"}, + {RECORD_NAMESPACE, "Namespace"}, + {RECORD_DEFLOCATION, "DefLocation"}, + {RECORD_LOCATION, "Location"}, + {RECORD_TAG_TYPE, "TagType"}, + {RECORD_PARENT, "Parent"}, + {RECORD_VPARENT, "VParent"}, + {FUNCTION_FULLY_QUALIFIED_NAME, "FullyQualifiedName"}, + {FUNCTION_NAME, "Name"}, + {FUNCTION_NAMESPACE, "Namespace"}, + {FUNCTION_LOCATION, "Location"}, + {FUNCTION_DEFLOCATION, "DefLocation"}, + {FUNCTION_MANGLED_NAME, "MangledName"}, + {FUNCTION_PARENT, "Parent"}, + {FUNCTION_ACCESS, "Access"}}; + for (const auto &init : inits) { + RecordIdNameMap[init.first] = init.second; + } + return RecordIdNameMap; + }(); + +void ClangDocBinaryWriter::AbbreviationMap::add(RecordId RID, + unsigned AbbrevID) { + assert(Abbrevs.find(RID) == Abbrevs.end() && "Abbreviation already added."); + Abbrevs[RID] = AbbrevID; +} + +unsigned ClangDocBinaryWriter::AbbreviationMap::get(RecordId RID) { + assert(Abbrevs.find(RID) != Abbrevs.end() && "Unknown abbreviation."); + return Abbrevs[RID]; +} + +void ClangDocBinaryWriter::AbbreviationMap::clear() { Abbrevs.clear(); } + +void ClangDocBinaryWriter::emitHeader(BitstreamWriter &Stream) { + // Emit the file header. + Stream.Emit((unsigned)'D', BitCodeConstants::SignatureBitSize); + Stream.Emit((unsigned)'O', BitCodeConstants::SignatureBitSize); + Stream.Emit((unsigned)'C', BitCodeConstants::SignatureBitSize); + Stream.Emit((unsigned)'S', BitCodeConstants::SignatureBitSize); +} + +/// \brief Emits a block ID in the BLOCKINFO block. +void ClangDocBinaryWriter::emitBlockID(BlockId ID, BitstreamWriter &Stream) { + 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 ClangDocBinaryWriter::emitRecordID(RecordId ID, BitstreamWriter &Stream) { + 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 ClangDocBinaryWriter::emitAbbrev(RecordId ID, BlockId Block, Lambda &&L, + BitstreamWriter &Stream) { + auto Abbrev = std::make_shared(); + Abbrev->Add(BitCodeAbbrevOp(ID)); + L(Abbrev); + Abbrevs.add(ID, Stream.EmitBlockInfoAbbrev(Block, std::move(Abbrev))); +} + +void ClangDocBinaryWriter::emitStringAbbrev(RecordId ID, BlockId Block, + BitstreamWriter &Stream) { + 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, Stream); +} + +void ClangDocBinaryWriter::emitLocationAbbrev(RecordId ID, BlockId Block, + BitstreamWriter &Stream) { + 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, Stream); +} + +void ClangDocBinaryWriter::emitIntAbbrev(RecordId ID, BlockId Block, + BitstreamWriter &Stream) { + auto EmitString = [](std::shared_ptr &Abbrev) { + Abbrev->Add( + BitCodeAbbrevOp(BitCodeAbbrevOp::Fixed, + BitCodeConstants::LineNumFixedSize)); // Integer + }; + emitAbbrev(ID, Block, EmitString, Stream); +} + +// Common Records + +void ClangDocBinaryWriter::emitStringRecord(StringRef Str, RecordId ID, + BitstreamWriter &Stream) { + if (Str.empty()) return; + Record.clear(); + Record.push_back(ID); + Record.push_back(Str.size()); + Stream.EmitRecordWithBlob(Abbrevs.get(ID), Record, Str); +} + +void ClangDocBinaryWriter::emitLocationRecord(int LineNumber, StringRef File, + RecordId ID, + BitstreamWriter &Stream) { + 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 ClangDocBinaryWriter::emitIntRecord(int Value, RecordId ID, + BitstreamWriter &Stream) { + if (!Value) return; + Record.clear(); + Record.push_back(ID); + Record.push_back(Value); + Stream.EmitRecordWithAbbrev(Abbrevs.get(ID), Record); +} + +// Common Blocks + +void ClangDocBinaryWriter::emitNamedTypeBlock(const NamedType &N, + NamedType::FieldName ID, + BitstreamWriter &Stream) { + StreamSubBlock Block(Stream, BI_NAMED_TYPE_BLOCK_ID); + 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); +} + +void ClangDocBinaryWriter::emitCommentBlock(const CommentInfo *I, + BitstreamWriter &Stream) { + StreamSubBlock Block(Stream, BI_COMMENT_BLOCK_ID); + 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); +} + +void ClangDocBinaryWriter::emitBlockInfoBlock(BitstreamWriter &Stream) { + Abbrevs.clear(); + emitHeader(Stream); + Stream.EnterBlockInfoBlock(); + + // Comment Block + emitBlockID(BI_COMMENT_BLOCK_ID, Stream); + emitRecordID(COMMENT_KIND, Stream); + emitRecordID(COMMENT_TEXT, Stream); + emitRecordID(COMMENT_NAME, Stream); + emitRecordID(COMMENT_DIRECTION, Stream); + emitRecordID(COMMENT_PARAMNAME, Stream); + emitRecordID(COMMENT_CLOSENAME, Stream); + emitRecordID(COMMENT_SELFCLOSING, Stream); + emitRecordID(COMMENT_EXPLICIT, Stream); + emitRecordID(COMMENT_ATTRKEY, Stream); + emitRecordID(COMMENT_ATTRVAL, Stream); + emitRecordID(COMMENT_ARG, Stream); + emitRecordID(COMMENT_POSITION, Stream); + emitStringAbbrev(COMMENT_KIND, BI_COMMENT_BLOCK_ID, Stream); + emitStringAbbrev(COMMENT_TEXT, BI_COMMENT_BLOCK_ID, Stream); + emitStringAbbrev(COMMENT_NAME, BI_COMMENT_BLOCK_ID, Stream); + emitStringAbbrev(COMMENT_DIRECTION, BI_COMMENT_BLOCK_ID, Stream); + emitStringAbbrev(COMMENT_PARAMNAME, BI_COMMENT_BLOCK_ID, Stream); + emitStringAbbrev(COMMENT_CLOSENAME, BI_COMMENT_BLOCK_ID, Stream); + emitIntAbbrev(COMMENT_SELFCLOSING, BI_COMMENT_BLOCK_ID, Stream); + emitIntAbbrev(COMMENT_EXPLICIT, BI_COMMENT_BLOCK_ID, Stream); + emitStringAbbrev(COMMENT_ATTRKEY, BI_COMMENT_BLOCK_ID, Stream); + emitStringAbbrev(COMMENT_ATTRVAL, BI_COMMENT_BLOCK_ID, Stream); + emitStringAbbrev(COMMENT_ARG, BI_COMMENT_BLOCK_ID, Stream); + emitStringAbbrev(COMMENT_POSITION, BI_COMMENT_BLOCK_ID, Stream); + + // NamedType Block + emitBlockID(BI_NAMED_TYPE_BLOCK_ID, Stream); + emitRecordID(NAMED_TYPE_ID, Stream); + emitRecordID(NAMED_TYPE_TYPE, Stream); + emitRecordID(NAMED_TYPE_NAME, Stream); + emitRecordID(NAMED_TYPE_ACCESS, Stream); + emitIntAbbrev(NAMED_TYPE_ID, BI_NAMED_TYPE_BLOCK_ID, Stream); + emitStringAbbrev(NAMED_TYPE_TYPE, BI_NAMED_TYPE_BLOCK_ID, Stream); + emitStringAbbrev(NAMED_TYPE_NAME, BI_NAMED_TYPE_BLOCK_ID, Stream); + emitIntAbbrev(NAMED_TYPE_ACCESS, BI_NAMED_TYPE_BLOCK_ID, Stream); + +#define INFORECORD(X) \ + emitRecordID(X##_FULLY_QUALIFIED_NAME, Stream); \ + emitRecordID(X##_NAME, Stream); \ + emitRecordID(X##_NAMESPACE, Stream); + +#define INFOABBREV(X) \ + emitStringAbbrev(X##_FULLY_QUALIFIED_NAME, BI_##X##_BLOCK_ID, Stream); \ + emitStringAbbrev(X##_NAME, BI_##X##_BLOCK_ID, Stream); \ + emitStringAbbrev(X##_NAMESPACE, BI_##X##_BLOCK_ID, Stream); + + // Namespace Block + emitBlockID(BI_NAMESPACE_BLOCK_ID, Stream); + INFORECORD(NAMESPACE) + INFOABBREV(NAMESPACE) + + // NonDef Block + emitBlockID(BI_NONDEF_BLOCK_ID, Stream); + INFORECORD(NONDEF) + emitRecordID(NONDEF_TYPE, Stream); + emitRecordID(NONDEF_LOCATION, Stream); + INFOABBREV(NONDEF) + emitIntAbbrev(NONDEF_TYPE, BI_NONDEF_BLOCK_ID, Stream); + emitLocationAbbrev(NONDEF_LOCATION, BI_NONDEF_BLOCK_ID, Stream); + + // Enum Block + emitBlockID(BI_ENUM_BLOCK_ID, Stream); + INFORECORD(ENUM) + emitRecordID(ENUM_DEFLOCATION, Stream); + emitRecordID(ENUM_LOCATION, Stream); + emitRecordID(ENUM_SCOPED, Stream); + INFOABBREV(ENUM) + emitLocationAbbrev(ENUM_DEFLOCATION, BI_ENUM_BLOCK_ID, Stream); + emitLocationAbbrev(ENUM_LOCATION, BI_ENUM_BLOCK_ID, Stream); + emitIntAbbrev(ENUM_SCOPED, BI_ENUM_BLOCK_ID, Stream); + + // Record Block + emitBlockID(BI_RECORD_BLOCK_ID, Stream); + INFORECORD(RECORD) + emitRecordID(RECORD_DEFLOCATION, Stream); + emitRecordID(RECORD_LOCATION, Stream); + emitRecordID(RECORD_TAG_TYPE, Stream); + emitRecordID(RECORD_PARENT, Stream); + emitRecordID(RECORD_VPARENT, Stream); + INFOABBREV(RECORD) + emitLocationAbbrev(RECORD_DEFLOCATION, BI_RECORD_BLOCK_ID, Stream); + emitLocationAbbrev(RECORD_LOCATION, BI_RECORD_BLOCK_ID, Stream); + emitIntAbbrev(RECORD_TAG_TYPE, BI_RECORD_BLOCK_ID, Stream); + emitStringAbbrev(RECORD_PARENT, BI_RECORD_BLOCK_ID, Stream); + emitStringAbbrev(RECORD_VPARENT, BI_RECORD_BLOCK_ID, Stream); + + // Function Block + emitBlockID(BI_FUNCTION_BLOCK_ID, Stream); + INFORECORD(FUNCTION) + emitRecordID(FUNCTION_DEFLOCATION, Stream); + emitRecordID(FUNCTION_LOCATION, Stream); + emitRecordID(FUNCTION_MANGLED_NAME, Stream); + emitRecordID(FUNCTION_PARENT, Stream); + emitRecordID(FUNCTION_ACCESS, Stream); + INFOABBREV(FUNCTION) + emitLocationAbbrev(FUNCTION_DEFLOCATION, BI_FUNCTION_BLOCK_ID, Stream); + emitLocationAbbrev(FUNCTION_LOCATION, BI_FUNCTION_BLOCK_ID, Stream); + emitStringAbbrev(FUNCTION_MANGLED_NAME, BI_FUNCTION_BLOCK_ID, Stream); + emitStringAbbrev(FUNCTION_PARENT, BI_FUNCTION_BLOCK_ID, Stream); + emitIntAbbrev(FUNCTION_ACCESS, BI_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); + +void ClangDocBinaryWriter::writeBitstream(const NamespaceInfo &I, + BitstreamWriter &Stream, + bool writeBlockInfo) { + if (writeBlockInfo) emitBlockInfoBlock(Stream); + StreamSubBlock Block(Stream, BI_NAMESPACE_BLOCK_ID); + EMITINFO(NAMESPACE) +} + +void ClangDocBinaryWriter::writeBitstream(const NonDefInfo &I, + BitstreamWriter &Stream, + bool writeBlockInfo) { + if (writeBlockInfo) emitBlockInfoBlock(Stream); + StreamSubBlock Block(Stream, BI_NONDEF_BLOCK_ID); + EMITINFO(NONDEF) + emitIntRecord(I.Type, NONDEF_TYPE, Stream); + for (const auto &L : I.Loc) + emitLocationRecord(L.LineNumber, L.Filename, NONDEF_LOCATION, Stream); +} + +void ClangDocBinaryWriter::writeBitstream(const EnumInfo &I, + BitstreamWriter &Stream, + bool writeBlockInfo) { + if (writeBlockInfo) emitBlockInfoBlock(Stream); + StreamSubBlock Block(Stream, BI_ENUM_BLOCK_ID); + 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); +} + +void ClangDocBinaryWriter::writeBitstream(const RecordInfo &I, + BitstreamWriter &Stream, + bool writeBlockInfo) { + if (writeBlockInfo) emitBlockInfoBlock(Stream); + StreamSubBlock Block(Stream, BI_RECORD_BLOCK_ID); + 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); +} + +void ClangDocBinaryWriter::writeBitstream(const FunctionInfo &I, + BitstreamWriter &Stream, + bool writeBlockInfo) { + if (writeBlockInfo) emitBlockInfoBlock(Stream); + StreamSubBlock Block(Stream, BI_FUNCTION_BLOCK_ID); + 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); +} + +#undef EMITINFO + +} // namespace doc +} // namespace clang Index: clang-doc/ClangDocMapper.h =================================================================== --- /dev/null +++ clang-doc/ClangDocMapper.h @@ -0,0 +1,129 @@ +//===-- 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 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) : Writer(OmitFilenames) {} + + std::string emitInfo(const NamespaceDecl *D, const FullComment *FC, + StringRef Key, int LineNumber, StringRef File); + std::string emitInfo(const RecordDecl *D, const FullComment *FC, + StringRef Key, int LineNumber, StringRef File); + std::string emitInfo(const EnumDecl *D, const FullComment *FC, + StringRef Key, int LineNumber, StringRef File); + std::string emitInfo(const FunctionDecl *D, const FullComment *FC, + StringRef Key, int LineNumber, StringRef File); + std::string emitInfo(const CXXMethodDecl *D, const FullComment *FC, + StringRef Key, int LineNumber, StringRef File); + + private: + template + std::string serialize(T &I); + 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 + std::string 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; + }; + + 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; + std::string getName(const NamedDecl *D, 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/ClangDocMapper.cpp =================================================================== --- /dev/null +++ clang-doc/ClangDocMapper.cpp @@ -0,0 +1,381 @@ +//===-- 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 "clang/AST/Comment.h" +#include "clang/AST/Mangle.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::ClangDocSerializer + +std::string ClangDocMapper::ClangDocSerializer::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); +} + +std::string ClangDocMapper::ClangDocSerializer::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); +} + +std::string ClangDocMapper::ClangDocSerializer::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); +} + +std::string ClangDocMapper::ClangDocSerializer::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); +} + +std::string ClangDocMapper::ClangDocSerializer::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 +std::string ClangDocMapper::ClangDocSerializer::serialize(T &I) { + SmallString<2048> Buffer; + llvm::BitstreamWriter Stream(Buffer); + Writer.writeBitstream(I, Stream, /*writeBlockInfo=*/true); + return Buffer.str().str(); +} + +void ClangDocMapper::ClangDocSerializer::parseFullComment(const FullComment *C, + CommentInfo &CI) { + ClangDocCommentVisitor Visitor(CI); + Visitor.parseComment(C); +} + +void ClangDocMapper::ClangDocSerializer::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::ClangDocSerializer::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 +std::string ClangDocMapper::ClangDocSerializer::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::ClangDocSerializer::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::ClangDocSerializer::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::ClangDocSerializer::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::ClangDocSerializer::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::ClangDocSerializer::getParentNamespace( + const DeclContext *D) const { + if (const auto *N = dyn_cast(D->getParent())) { + return N->getQualifiedNameAsString(); + } + return ""; +} + +// 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(std::make_shared()); + 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; + std::string Name = getName(D, D->getASTContext()); + ECtx->reportResult( + Name, Serializer.emitInfo(D, getComment(D, D->getASTContext()), Name, + 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(); +} + +std::string ClangDocMapper::getName(const NamedDecl *D, + ASTContext &Context) 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/ClangDocRepresentation.h =================================================================== --- /dev/null +++ clang-doc/ClangDocRepresentation.h @@ -0,0 +1,111 @@ +///===-- 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,107 @@ +//===-- 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 "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,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 -output=%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 -output=%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-function.cpp =================================================================== --- /dev/null +++ test/clang-doc/mapper-function.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/_Z1Fi.bc --dump | FileCheck %s + +int F(int param) { return param; } +// CHECK: +// CHECK: + // CHECK: blob data = 'F' + // CHECK: blob data = 'F' + // CHECK: blob data = '_Z1Fi' + // CHECK: + // CHECK: + // CHECK: blob data = 'int' + // CHECK: + // CHECK: + // CHECK: + // CHECK: + // CHECK: blob data = 'int' + // CHECK: blob data = 'param' + // CHECK: + // CHECK: +// CHECK: Index: test/clang-doc/mapper-method.cpp =================================================================== --- /dev/null +++ test/clang-doc/mapper-method.cpp @@ -0,0 +1,30 @@ +// 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/_ZN1E6MethodEi.bc --dump | FileCheck %s + +class E { +public: + int Method(int param) { return param; } +}; +// CHECK: +// CHECK: + // CHECK: blob data = 'E::Method' + // CHECK: blob data = 'Method' + // CHECK: blob data = 'E' + // CHECK: blob data = '_ZN1E6MethodEi' + // CHECK: blob data = 'E' + // CHECK: + // CHECK: + // CHECK: blob data = 'int' + // CHECK: + // CHECK: + // CHECK: + // CHECK: + // CHECK: blob data = 'int' + // CHECK: blob data = 'param' + // 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 -output=%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 -output=%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-undefined.cpp =================================================================== --- /dev/null +++ test/clang-doc/mapper-undefined.cpp @@ -0,0 +1,15 @@ +// 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/E.bc --dump | FileCheck %s + +class E; +// CHECK: +// CHECK: + // CHECK: blob data = 'E' + // CHECK: blob data = 'E' + // 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 -output=%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: