Index: test/CMakeLists.txt =================================================================== --- test/CMakeLists.txt +++ test/CMakeLists.txt @@ -54,6 +54,7 @@ clang-rename clang-refactor clang-diff + clang-doc ) if(CLANG_ENABLE_STATIC_ANALYZER) Index: test/Tooling/clang-doc-basic.cpp =================================================================== --- /dev/null +++ test/Tooling/clang-doc-basic.cpp @@ -0,0 +1,83 @@ +// RUN: rm -rf %t +// RUN: mkdir %t +// RUN: echo '[{"directory":"%t","command":"clang++ -c %t/test.cpp","file":"%t/test.cpp"}]' | sed -e 's/\\/\//g' > %t/compile_commands.json +// RUN: cp "%s" "%t/test.cpp" +// RUN: clang-doc -p %t %t/test.cpp | FileCheck %s -DROOT=%t --implicit-check-not Skipping + +/// Test namespace +namespace A { + +/// A function +void f(int x) {} + +} // end namespace A + +/// A C++ class +class C { +private: + int i; +}; + +// CHECK-DAG: Parsing codebase... +// CHECK-DAG: Writing docs... +// CHECK-DAG: --- +// CHECK-DAG: Files: +// CHECK-DAG: - Filename: [[ROOT]]/test.cpp +// CHECK-DAG: Description: +// CHECK-DAG: Kind: '' +// CHECK-DAG: Namespaces: +// CHECK-DAG: - Qualified Name: '' +// CHECK-DAG: Name: '' +// CHECK-DAG: Namespace: '' +// CHECK-DAG: - Qualified Name: A +// CHECK-DAG: Name: A +// CHECK-DAG: Namespace: '' +// CHECK-DAG: Descriptions: +// CHECK-DAG: - Kind: FullComment +// CHECK-DAG: Children: +// CHECK-DAG: - Kind: ParagraphComment +// CHECK-DAG: Children: +// CHECK-DAG: - Kind: TextComment +// CHECK-DAG: Text: ' Test namespace' +// CHECK-DAG: Locations: +// CHECK-DAG: - LineNumber: 8 +// CHECK-DAG: Filename: [[ROOT]]/test.cpp +// CHECK-DAG: Functions: +// CHECK-DAG: - Mangled Name: _ZN1A1fEi +// CHECK-DAG: Qualified Name: 'A::f' +// CHECK-DAG: Name: f +// CHECK-DAG: Namespace: A +// CHECK-DAG: Descriptions: +// CHECK-DAG: - Kind: FullComment +// CHECK-DAG: Children: +// CHECK-DAG: - Kind: ParagraphComment +// CHECK-DAG: Children: +// CHECK-DAG: - Kind: TextComment +// CHECK-DAG: Text: ' A function' +// CHECK-DAG: Locations: +// CHECK-DAG: - LineNumber: 11 +// CHECK-DAG: Filename: [[ROOT]]/test.cpp +// CHECK-DAG: DefinitionFile: [[ROOT]]/test.cpp +// CHECK-DAG: ReturnType: void +// CHECK-DAG: Access: None +// CHECK-DAG: Types: +// CHECK-DAG: - Qualified Name: C +// CHECK-DAG: Name: C +// CHECK-DAG: Namespace: '' +// CHECK-DAG: Descriptions: +// CHECK-DAG: - Kind: FullComment +// CHECK-DAG: Children: +// CHECK-DAG: - Kind: ParagraphComment +// CHECK-DAG: Children: +// CHECK-DAG: - Kind: TextComment +// CHECK-DAG: Text: ' A C++ class' +// CHECK-DAG: TagType: Class +// CHECK-DAG: Locations: +// CHECK-DAG: - LineNumber: 16 +// CHECK-DAG: Filename: [[ROOT]]/test.cpp +// CHECK-DAG: DefinitionFile: [[ROOT]]/test.cpp +// CHECK-DAG: Members: +// CHECK-DAG: - Type: int +// CHECK-DAG: Name: 'C::i' +// CHECK-DAG: Access: None +// CHECK-DAG: ... Index: test/lit.cfg.py =================================================================== --- test/lit.cfg.py +++ test/lit.cfg.py @@ -57,9 +57,9 @@ tool_dirs = [config.clang_tools_dir, config.llvm_tools_dir] tools = [ - 'c-index-test', 'clang-check', 'clang-diff', 'clang-format', 'opt', - ToolSubst('%clang_func_map', command=FindTool( - 'clang-func-mapping'), unresolved='ignore'), + 'c-index-test', 'clang-check', 'clang-diff', 'clang-doc', 'clang-format', + 'opt', ToolSubst('%clang_func_map', command=FindTool( + 'clang-func-mapping'), unresolved='ignore'), ] if config.clang_examples: Index: tools/CMakeLists.txt =================================================================== --- tools/CMakeLists.txt +++ tools/CMakeLists.txt @@ -3,6 +3,7 @@ add_clang_subdirectory(diagtool) add_clang_subdirectory(driver) add_clang_subdirectory(clang-diff) +add_clang_subdirectory(clang-doc) add_clang_subdirectory(clang-format) add_clang_subdirectory(clang-format-vs) add_clang_subdirectory(clang-fuzzer) Index: tools/clang-doc/CMakeLists.txt =================================================================== --- /dev/null +++ tools/clang-doc/CMakeLists.txt @@ -0,0 +1,21 @@ +set(LLVM_LINK_COMPONENTS + support + ) + +add_clang_library(clangDoc + ClangDoc.cpp + ClangDocReporter.cpp + + LINK_LIBS + clangAnalysis + clangAST + clangASTMatchers + clangBasic + clangFormat + clangFrontend + clangLex + clangTooling + clangToolingCore + ) + +add_subdirectory(tool) Index: tools/clang-doc/ClangDoc.h =================================================================== --- /dev/null +++ tools/clang-doc/ClangDoc.h @@ -0,0 +1,97 @@ +//===-- ClangDoc.cpp - ClangDoc ---------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_DOC_CLANGDOC_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_DOC_CLANGDOC_H + +#include "ClangDocReporter.h" +#include "clang/AST/AST.h" +#include "clang/AST/ASTConsumer.h" +#include "clang/AST/ASTContext.h" +#include "clang/AST/RecursiveASTVisitor.h" +#include "clang/Frontend/ASTConsumers.h" +#include "clang/Frontend/FrontendActions.h" +#include "clang/Tooling/Tooling.h" +#include +#include + +namespace clang { +namespace doc { + +// A Context which contains extra options which are used in ClangMoveTool. +struct ClangDocContext { + // Which format in which to emit representation. + OutFormat EmitFormat; +}; + +class ClangDocVisitor : public RecursiveASTVisitor { +public: + explicit ClangDocVisitor(ASTContext *Ctx, ClangDocReporter &Reporter) + : Context(Ctx), Manager(Ctx->getSourceManager()), + MC(Ctx->createMangleContext()), Reporter(Reporter) {} + + bool VisitTagDecl(const TagDecl *D); + bool VisitNamespaceDecl(const NamespaceDecl *D); + bool VisitFunctionDecl(const FunctionDecl *D); + void parseUnattachedComments(); + +private: + bool isUnparsed(SourceLocation Loc) const; + int getLine(const Decl *D) const; + std::string getFile(const Decl *D) const; + comments::FullComment* getComment(const Decl *D); + std::string mangleName(const FunctionDecl *D); + + ASTContext *Context; + SourceManager &Manager; + MangleContext *MC; + ClangDocReporter &Reporter; +}; + +class ClangDocConsumer : public clang::ASTConsumer { +public: + explicit ClangDocConsumer(ASTContext *Ctx, ClangDocReporter &Reporter) + : Visitor(Ctx, Reporter), Reporter(Reporter) {} + + virtual void HandleTranslationUnit(clang::ASTContext &Context); + +private: + ClangDocVisitor Visitor; + ClangDocReporter &Reporter; +}; + +class ClangDocAction : public clang::ASTFrontendAction { +public: + ClangDocAction(ClangDocReporter &Reporter) : Reporter(Reporter) {} + + virtual std::unique_ptr + CreateASTConsumer(clang::CompilerInstance &C, llvm::StringRef InFile); + +private: + ClangDocReporter &Reporter; +}; + +class ClangDocActionFactory : public tooling::FrontendActionFactory { +public: + ClangDocActionFactory(ClangDocContext &Ctx, ClangDocReporter &Reporter) + : Context(Ctx), Reporter(Reporter) {} + + clang::FrontendAction *create() override { + return new ClangDocAction(Reporter); + } + +private: + ClangDocContext &Context; + ClangDocReporter &Reporter; +}; + +} // namespace doc +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_DOC_CLANGDOC_H Index: tools/clang-doc/ClangDoc.cpp =================================================================== --- /dev/null +++ tools/clang-doc/ClangDoc.cpp @@ -0,0 +1,127 @@ +//===-- ClangDoc.cpp - ClangDoc ---------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "ClangDoc.h" +#include "clang/AST/Comment.h" +#include "clang/AST/Mangle.h" +#include "clang/Frontend/CompilerInstance.h" + +using namespace clang; +using namespace clang::tooling; +using namespace llvm; + +namespace clang { +namespace doc { + +bool ClangDocVisitor::VisitTagDecl(const TagDecl *D) { + if (!isUnparsed(D->getLocation())) + return true; + + if (const auto *E = dyn_cast(D)) { + Reporter.createEnumInfo(E, getComment(E), getLine(E), getFile(E)); + return true; + } + if (const auto *R = dyn_cast(D)) { + Reporter.createTypeInfo(R, getComment(R), getLine(R), getFile(R)); + return true; + } + + // Error? + return true; +} + +bool ClangDocVisitor::VisitNamespaceDecl(const NamespaceDecl *D) { + if (!isUnparsed(D->getLocation())) + return true; + Reporter.createNamespaceInfo(D, getComment(D), getLine(D), getFile(D)); + return true; +} + +bool ClangDocVisitor::VisitFunctionDecl(const FunctionDecl *D) { + if (!isUnparsed(D->getLocation())) + return true; + if (const auto *C = dyn_cast(D)) { + Reporter.createMethodInfo(C, getComment(C), getLine(C), getFile(C), + mangleName(C)); + return true; + } + + Reporter.createFunctionInfo(D, getComment(D), getLine(D), getFile(D), + mangleName(D)); + return true; +} + +comments::FullComment *ClangDocVisitor::getComment(const Decl *D) { + 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 ClangDocVisitor::getLine(const Decl *D) const { + PresumedLoc PLoc = Manager.getPresumedLoc(D->getLocStart()); + return PLoc.getLine(); +} + +std::string ClangDocVisitor::getFile(const Decl *D) const { + PresumedLoc PLoc = Manager.getPresumedLoc(D->getLocStart()); + return PLoc.getFilename(); +} + +void ClangDocVisitor::parseUnattachedComments() { + for (RawComment *Comment : Context->getRawCommentList().getComments()) { + if (!isUnparsed(Comment->getLocStart()) || Comment->isAttached()) + continue; + CommentInfo CI; + Reporter.parseFullComment(Comment->parse(*Context, nullptr, nullptr), CI); + Reporter.addUnattachedComment(Manager.getFilename(Comment->getLocStart()), + CI); + } +} + +bool ClangDocVisitor::isUnparsed(SourceLocation Loc) const { + if (!Loc.isValid()) + return false; + const std::string &Filename = Manager.getFilename(Loc); + + if (!Reporter.hasFile(Filename)) + return false; + if (Manager.isInSystemHeader(Loc) || Manager.isInExternCSystemHeader(Loc)) + return false; + return true; +} + +std::string ClangDocVisitor::mangleName(const FunctionDecl *D) { + std::string S; + llvm::raw_string_ostream MangledName(S); + if (const auto *Ctor = dyn_cast(D)) + MC->mangleCXXCtor(Ctor, CXXCtorType::Ctor_Complete, MangledName); + else if (const auto *Dtor = dyn_cast(D)) + MC->mangleCXXDtor(Dtor, CXXDtorType::Dtor_Complete, MangledName); + else + MC->mangleName(D, MangledName); + return MangledName.str(); +} + +void ClangDocConsumer::HandleTranslationUnit(ASTContext &Context) { + Visitor.TraverseDecl(Context.getTranslationUnitDecl()); + Visitor.parseUnattachedComments(); +} + +std::unique_ptr +ClangDocAction::CreateASTConsumer(CompilerInstance &C, StringRef InFile) { + return make_unique(&C.getASTContext(), Reporter); +} + +} // namespace doc +} // namespace clang Index: tools/clang-doc/ClangDocReporter.h =================================================================== --- /dev/null +++ tools/clang-doc/ClangDocReporter.h @@ -0,0 +1,190 @@ +//===-- Doc.cpp - 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_CLANG_DOC_REPORTER_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_DOC_CLANG_DOC_REPORTER_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/Tooling.h" +#include "llvm/ADT/SmallVector.h" +#include "llvm/Support/raw_ostream.h" +#include +#include +#include +#include + +using namespace clang::comments; + +namespace clang { +namespace doc { + +enum class OutFormat { YAML, LLVM }; + +// Info for named types (parameters, members). +struct NamedType { + std::string Type; + std::string Name; + AccessSpecifier Access; +}; + +struct Location { + int LineNumber; + std::string Filename; +}; + +/// 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 Attrs; + llvm::SmallVector Args; + llvm::SmallVector Position; + std::vector Children; +}; + +/// A source file in the project. +struct File { + std::string Filename; + CommentInfo Description; + llvm::SmallVector UnattachedComments; +}; + +/// A base struct for Infos. +struct Info { + bool isDefined = false; + std::string FullyQualifiedName; + std::string SimpleName; + std::string Namespace; + std::vector Descriptions; + llvm::SmallVector Locations; +}; + +// TODO: Expand to allow for documenting templating. +// Info for functions. +struct FunctionInfo : public Info { + std::string MangledName; + std::string DefinitionFile; + std::string ReturnType; + llvm::SmallVector Params; + AccessSpecifier Access; +}; + +struct NamespaceInfo : public Info { + llvm::StringMap Functions; +}; + +// TODO: Expand to allow for documenting templating, inheritance access, +// friend classes +// Info for types. +struct TypeInfo : public Info { + std::string DefinitionFile; + TagTypeKind TagType; + llvm::SmallVector Members; + llvm::SmallVector Parents; + llvm::SmallVector VirtualParents; + llvm::StringMap Functions; +}; + +// TODO: Expand to allow for documenting templating. +// Info for types. +struct EnumInfo : public Info { + std::string DefinitionFile; + bool Scoped; + llvm::SmallVector Members; +}; + +/// A struct encapsulating all documentation information for the project. +struct Documentation { + llvm::StringMap Files; + llvm::StringMap Namespaces; + llvm::StringMap Types; + llvm::StringMap Enums; + // TODO: Add functionality to include separate markdown pages. +}; + +/// Struct with a key and a value to allow for serialization. +template struct Pair { + std::string Key; + T Value; +}; + +class ClangDocReporter : public ConstCommentVisitor { +public: + ClangDocReporter() {} + ClangDocReporter(const std::vector &Sources); + + void addUnattachedComment(StringRef Filename, const CommentInfo &CI); + void addFile(StringRef Filename); + + void createNamespaceInfo(const NamespaceDecl *D, const FullComment *C, + int LineNumber, StringRef File); + void createTypeInfo(const RecordDecl *D, const FullComment *C, int LineNumber, StringRef File); + void createEnumInfo(const EnumDecl *D, const FullComment *C, int LineNumber, StringRef File); + void createFunctionInfo(const FunctionDecl *D, const FullComment *C, + int LineNumber, StringRef File, StringRef MangledName); + void createMethodInfo(const CXXMethodDecl *D, const FullComment *C, + int LineNumber, StringRef File, StringRef MangledName); + + void parseFullComment(const FullComment *C, CommentInfo &CI); + 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); + + bool hasFile(StringRef Filename) const; + void serialize(clang::doc::OutFormat Format, llvm::raw_ostream &OS) const; + +private: + void addComment(Info &I, const FullComment *C); + void addLocation(Info &I, int LineNumber, StringRef File) const; + + void populateBasicInfo(Info &I, StringRef Name, StringRef SimpleName, StringRef Namespace); + void populateTypeInfo(TypeInfo &I, const RecordDecl *D, StringRef Name, StringRef File); + void populateEnumInfo(EnumInfo &I, const EnumDecl *D, StringRef Name, StringRef File); + void populateFunctionInfo(FunctionInfo &I, const FunctionDecl *D, StringRef Name, StringRef File, AccessSpecifier AS); + + void parseComment(CommentInfo *CI, const comments::Comment *C); + void parseFields(TypeInfo &I, const RecordDecl *D) const; + void parseEnumerators(EnumInfo &I, const EnumDecl *D) const; + void parseBases(TypeInfo &I, const CXXRecordDecl *D) const; + void parseParameters(FunctionInfo &I, const FunctionDecl *D) const; + + void serializeYAML(llvm::raw_ostream &OS) const; + void serializeLLVM(llvm::raw_ostream &OS) const; + + const char *getCommandName(unsigned CommandID) const; + bool isWhitespaceOnly(StringRef S) const; + std::string getParentNamespace(const DeclContext *D) const; + + CommentInfo *CurrentCI; + Documentation Docs; +}; + +} // namespace doc +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_DOC_CLANG_DOC_REPORTER_H Index: tools/clang-doc/ClangDocReporter.cpp =================================================================== --- /dev/null +++ tools/clang-doc/ClangDocReporter.cpp @@ -0,0 +1,350 @@ +//===-- ClangDocReporter.cpp - ClangDoc Reporter ----------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "ClangDocReporter.h" +#include "ClangDocYAML.h" +#include "llvm/Support/Path.h" +#include "llvm/Support/raw_ostream.h" +#include "llvm/Support/YAMLTraits.h" + +using namespace llvm; +using clang::comments::FullComment; + +namespace clang { +namespace doc { + +ClangDocReporter::ClangDocReporter(const std::vector &Sources) { + for (const std::string &Path : Sources) { + assert(sys::path::is_absolute(Path) && "clang-doc expects absolute paths."); + addFile(Path); + } + // Create base namespace + NamespaceInfo I; + I.isDefined = true; + Docs.Namespaces[""] = I; +} + +void ClangDocReporter::addUnattachedComment(StringRef Filename, + const CommentInfo &CI) { + Docs.Files[Filename].UnattachedComments.push_back(CI); +} + +void ClangDocReporter::addFile(StringRef Filename) { + File F; + F.Filename = Filename; + Docs.Files.insert(std::make_pair(Filename, F)); +} + +void ClangDocReporter::createNamespaceInfo(const NamespaceDecl *D, + const FullComment *C, + int LineNumber, StringRef File) { + std::string Name = D->getQualifiedNameAsString(); + llvm::StringMapIterator Pair = Docs.Namespaces.find(Name); + if (Pair == Docs.Namespaces.end()) { + NamespaceInfo I; + Docs.Namespaces[Name] = I; + populateBasicInfo(Docs.Namespaces[Name], Name, D->getNameAsString(), getParentNamespace(D)); + } + + addLocation(Docs.Namespaces[Name], LineNumber, File); + addComment(Docs.Namespaces[Name], C); +} + +void ClangDocReporter::createTypeInfo(const RecordDecl *D, const FullComment *C, + int LineNumber, StringRef File) { + + std::string Name = D->getQualifiedNameAsString(); + llvm::StringMapIterator Pair = Docs.Types.find(Name); + if (Pair == Docs.Types.end()) { + TypeInfo I; + Docs.Types[Name] = I; + } + + if (D->isThisDeclarationADefinition()) + populateTypeInfo(Docs.Types[Name], D, Name, File); + + addLocation(Docs.Types[Name], LineNumber, File); + addComment(Docs.Types[Name], C); +} + +void ClangDocReporter::createEnumInfo(const EnumDecl *D, const FullComment *C, + int LineNumber, StringRef File) { + std::string Name = D->getQualifiedNameAsString(); + llvm::StringMapIterator Pair = Docs.Enums.find(Name); + if (Pair == Docs.Enums.end()) { + EnumInfo I; + Docs.Enums[Name] = I; + } + + if (D->isThisDeclarationADefinition()) + populateEnumInfo(Docs.Enums[Name], D, Name, File); + + addLocation(Docs.Enums[Name], LineNumber, File); + addComment(Docs.Enums[Name], C); +} + +void ClangDocReporter::createFunctionInfo(const FunctionDecl *D, + const FullComment *C, int LineNumber, StringRef File, + StringRef MangledName) { + std::string Namespace = getParentNamespace(D); + llvm::StringMapIterator NS = Docs.Namespaces.find(Namespace); + if (NS == Docs.Namespaces.end()) { + NamespaceInfo NI;; + NI.FullyQualifiedName = Namespace; + Docs.Namespaces[Namespace] = NI; + } + + llvm::StringMapIterator Pair = Docs.Namespaces[Namespace].Functions.find(MangledName); + if (Pair != Docs.Namespaces[Namespace].Functions.end()) { + FunctionInfo I; + Docs.Namespaces[Namespace].Functions[MangledName] = I; + } + + if (D->isThisDeclarationADefinition()) + populateFunctionInfo(Docs.Namespaces[Namespace].Functions[MangledName], D, MangledName, File, clang::AccessSpecifier::AS_none); + + addLocation(Docs.Namespaces[Namespace].Functions[MangledName], LineNumber, File); + addComment(Docs.Namespaces[Namespace].Functions[MangledName], C); +} + +void ClangDocReporter::createMethodInfo(const CXXMethodDecl *D, + const FullComment *C, int LineNumber, StringRef File, + StringRef MangledName) { + std::string ParentName = D->getParent()->getQualifiedNameAsString(); + llvm::StringMapIterator T = Docs.Types.find(ParentName); + if (T == Docs.Types.end()) { + TypeInfo TI; + TI.FullyQualifiedName = getParentNamespace(D); + Docs.Types[ParentName] = TI; + } + + llvm::StringMapIterator Pair = Docs.Types[ParentName].Functions.find(MangledName); + if (Pair != Docs.Types[ParentName].Functions.end()) { + FunctionInfo I; + Docs.Types[ParentName].Functions[MangledName] = I; + } + + if (D->isThisDeclarationADefinition()) + populateFunctionInfo(Docs.Types[ParentName].Functions[MangledName], D, MangledName, File, D->getAccess()); + + addLocation(Docs.Types[ParentName].Functions[MangledName], LineNumber, File); + addComment(Docs.Types[ParentName].Functions[MangledName], C); +} + +void ClangDocReporter::parseFullComment(const FullComment *C, CommentInfo &CI) { + parseComment(&CI, C); +} + +void ClangDocReporter::visitTextComment(const TextComment *C) { + if (!isWhitespaceOnly(C->getText())) + CurrentCI->Text = C->getText(); +} + +void ClangDocReporter::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 ClangDocReporter::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); + NamedType T{Attr.Name, Attr.Value, clang::AccessSpecifier::AS_none}; + CurrentCI->Attrs.push_back(T); + } +} + +void ClangDocReporter::visitHTMLEndTagComment(const HTMLEndTagComment *C) { + CurrentCI->Name = C->getTagName(); + CurrentCI->SelfClosing = true; +} + +void ClangDocReporter::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 ClangDocReporter::visitParamCommandComment(const ParamCommandComment *C) { + CurrentCI->Direction = + ParamCommandComment::getDirectionAsString(C->getDirection()); + CurrentCI->Explicit = C->isDirectionExplicit(); + if (C->hasParamName() && C->isParamIndexValid()) + CurrentCI->ParamName = C->getParamNameAsWritten(); +} + +void ClangDocReporter::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(C->getIndex(i)); + } +} + +void ClangDocReporter::visitVerbatimBlockComment( + const VerbatimBlockComment *C) { + CurrentCI->Name = getCommandName(C->getCommandID()); + CurrentCI->CloseName = C->getCloseName(); +} + +void ClangDocReporter::visitVerbatimBlockLineComment( + const VerbatimBlockLineComment *C) { + if (!isWhitespaceOnly(C->getText())) + CurrentCI->Text = C->getText(); +} + +void ClangDocReporter::visitVerbatimLineComment(const VerbatimLineComment *C) { + if (!isWhitespaceOnly(C->getText())) + CurrentCI->Text = C->getText(); +} + +bool ClangDocReporter::hasFile(StringRef Filename) const { + return Docs.Files.find(Filename) != Docs.Files.end(); +} + +void ClangDocReporter::serialize(OutFormat Format, raw_ostream &OS) const { + Format == clang::doc::OutFormat::LLVM ? serializeLLVM(OS) : serializeYAML(OS); +} + +void ClangDocReporter::addComment(Info &I, const FullComment *C) { + if (!C) + return; + CommentInfo CI; + parseFullComment(C, CI); + I.Descriptions.push_back(CI); +} + +void ClangDocReporter::addLocation(Info &I, int LineNumber, StringRef File) const { + Location L{LineNumber, File}; + I.Locations.push_back(L); +} + +void ClangDocReporter::populateBasicInfo(Info &I, StringRef Name, StringRef SimpleName, StringRef Namespace) { + I.FullyQualifiedName = Name; + I.SimpleName = SimpleName; + I.Namespace = Namespace; + I.isDefined = true; +} + +void ClangDocReporter::populateTypeInfo(TypeInfo &I, const RecordDecl * D, StringRef Name, StringRef File) { + populateBasicInfo(I, Name, D->getNameAsString(), getParentNamespace(D)); + I.TagType = D->getTagKind(); + I.DefinitionFile = File; + if (const auto *CXXR = dyn_cast(D)) + parseBases(I, CXXR); + parseFields(I, D); +} + +void ClangDocReporter::populateFunctionInfo(FunctionInfo &I, const FunctionDecl *D, StringRef Name, StringRef File, AccessSpecifier AS) { + populateBasicInfo(I, D->getQualifiedNameAsString(), D->getNameAsString(), getParentNamespace(D)); + I.MangledName = Name; + I.DefinitionFile = File; + I.ReturnType = D->getReturnType().getAsString(); + I.Access = AS; + parseParameters(I, D); +} + +void ClangDocReporter::populateEnumInfo(EnumInfo &I, const EnumDecl *D, StringRef Name, StringRef File) { + populateBasicInfo(I, Name, D->getNameAsString(), getParentNamespace(D)); + I.DefinitionFile = File; + I.Scoped = D->isScoped(); + parseEnumerators(Docs.Enums[Name], D); +} + +void ClangDocReporter::parseComment(CommentInfo *CI, + const comments::Comment *C) { + CurrentCI = CI; + CI->Kind = C->getCommentKindName(); + ConstCommentVisitor::visit(C); + for (comments::Comment *Child : + make_range(C->child_begin(), C->child_end())) { + CommentInfo ChildCI; + parseComment(&ChildCI, Child); + CI->Children.push_back(ChildCI); + } +} + +void ClangDocReporter::parseFields(TypeInfo &I, const RecordDecl *D) const { + for (const FieldDecl *F : D->fields()) { + NamedType N; + N.Type = F->getTypeSourceInfo()->getType().getAsString(); + N.Name = F->getQualifiedNameAsString(); + if (const auto *M = dyn_cast(D)) + N.Access = M->getAccess(); + else + N.Access = clang::AccessSpecifier::AS_none; + I.Members.push_back(N); + } +} + +void ClangDocReporter::parseEnumerators(EnumInfo &I, const EnumDecl *D) const { + for (const EnumConstantDecl *E : D->enumerators()) { + NamedType N; + N.Type = E->getQualifiedNameAsString(); + N.Access = clang::AccessSpecifier::AS_none; + I.Members.push_back(N); + } +} + +void ClangDocReporter::parseParameters(FunctionInfo &I, + const FunctionDecl *D) const { + for (const ParmVarDecl *P : D->parameters()) { + NamedType Parm; + Parm.Type = P->getOriginalType().getAsString(); + Parm.Access = clang::AccessSpecifier::AS_none; + Parm.Name = P->getQualifiedNameAsString(); + I.Params.push_back(Parm); + } +} + +void ClangDocReporter::parseBases(TypeInfo &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()); +} + +void ClangDocReporter::serializeYAML(raw_ostream &OS) const { + yaml::Output Output(OS); + Documentation NonConstValue = Docs; + Output << NonConstValue; +} + +void ClangDocReporter::serializeLLVM(raw_ostream &OS) const { + // TODO: Implement. + OS << "Not yet implemented.\n"; +} + +const char *ClangDocReporter::getCommandName(unsigned CommandID) const { + const CommandInfo *Info = CommandTraits::getBuiltinCommandInfo(CommandID); + if (Info) + return Info->Name; + // TODO: Add parsing for \file command. + return ""; +} + +bool ClangDocReporter::isWhitespaceOnly(StringRef S) const { + return S.find_first_not_of(" \t\n\v\f\r") == std::string::npos || S.empty(); +} + +std::string ClangDocReporter::getParentNamespace(const DeclContext *D) const { + if (const auto *N = dyn_cast(D->getParent())) + return N->getQualifiedNameAsString(); + return ""; +} + +} // namespace doc +} // namespace clang Index: tools/clang-doc/ClangDocYAML.h =================================================================== --- /dev/null +++ tools/clang-doc/ClangDocYAML.h @@ -0,0 +1,217 @@ +//===-- ClangDocYAML.h - ClangDoc YAML -------------------------*- 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_YAML_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_DOC_CLANG_DOC_YAML_H + +#include "llvm/Support/YAMLTraits.h" + +LLVM_YAML_IS_SEQUENCE_VECTOR(clang::doc::CommentInfo) +LLVM_YAML_IS_SEQUENCE_VECTOR(clang::doc::NamedType) +LLVM_YAML_IS_SEQUENCE_VECTOR(clang::doc::Location) +LLVM_YAML_IS_SEQUENCE_VECTOR(clang::doc::Pair) +LLVM_YAML_IS_SEQUENCE_VECTOR(clang::doc::Pair) +LLVM_YAML_IS_SEQUENCE_VECTOR(clang::doc::Pair) +LLVM_YAML_IS_SEQUENCE_VECTOR(clang::doc::Pair) +LLVM_YAML_IS_SEQUENCE_VECTOR(clang::doc::Pair) + +namespace llvm { +namespace yaml { + +template struct NormalizedMap { + NormalizedMap(IO &) {} + NormalizedMap(IO &, const StringMap &Map) { + for (const auto &Entry : Map) { + clang::doc::Pair Pair{Entry.getKeyData(), Entry.getValue()}; + VectorMap.push_back(Pair); + } + } + + StringMap denormalize(IO &) { + StringMap Map; + for (const auto &Pair : VectorMap) + Map[Pair.Key] = Pair.Value; + return Map; + } + + std::vector> VectorMap; +}; + +template <> struct ScalarEnumerationTraits { + static void enumeration(IO &io, clang::AccessSpecifier &value) { + io.enumCase(value, "Public", clang::AccessSpecifier::AS_public); + io.enumCase(value, "Protected", clang::AccessSpecifier::AS_protected); + io.enumCase(value, "Private", clang::AccessSpecifier::AS_private); + io.enumCase(value, "None", clang::AccessSpecifier::AS_none); + } +}; + +template <> struct ScalarEnumerationTraits { + static void enumeration(IO &io, clang::TagTypeKind &value) { + io.enumCase(value, "Struct", clang::TagTypeKind::TTK_Struct); + io.enumCase(value, "Interface", clang::TagTypeKind::TTK_Interface); + io.enumCase(value, "Union", clang::TagTypeKind::TTK_Union); + io.enumCase(value, "Class", clang::TagTypeKind::TTK_Class); + io.enumCase(value, "Enum", clang::TagTypeKind::TTK_Enum); + } +}; + +template <> struct MappingTraits> { + static void mapping(IO &IO, clang::doc::Pair &Pair) { + IO.mapOptional("Value", Pair.Value); + } +}; + +template <> struct MappingTraits { + static void mapping(IO &IO, clang::doc::Location &Location) { + IO.mapOptional("LineNumber", Location.LineNumber); + IO.mapOptional("Filename", Location.Filename); + } +}; + +// TODO: uncomment description attrs +template <> struct MappingTraits> { + static void mapping(IO &IO, clang::doc::Pair &Pair) { + if (!Pair.Value.isDefined) + return; + MappingNormalization, + StringMap> + functions(IO, Pair.Value.Functions); + + IO.mapOptional("Qualified Name", Pair.Value.FullyQualifiedName); + IO.mapOptional("Name", Pair.Value.SimpleName); + IO.mapOptional("Namespace", Pair.Value.Namespace); + IO.mapOptional("Descriptions", Pair.Value.Descriptions); + IO.mapOptional("Locations", Pair.Value.Locations); + IO.mapOptional("Functions", functions->VectorMap); + } +}; + +template <> struct MappingTraits> { + static void mapping(IO &IO, clang::doc::Pair &Pair) { + if (!Pair.Value.isDefined) + return; + + MappingNormalization, + StringMap> + functions(IO, Pair.Value.Functions); + + IO.mapOptional("Qualified Name", Pair.Value.FullyQualifiedName); + IO.mapOptional("Name", Pair.Value.SimpleName); + IO.mapOptional("Namespace", Pair.Value.Namespace); + IO.mapOptional("Descriptions", Pair.Value.Descriptions); + IO.mapOptional("TagType", Pair.Value.TagType); + IO.mapOptional("Locations", Pair.Value.Locations); + IO.mapOptional("Functions", functions->VectorMap); + IO.mapOptional("DefinitionFile", Pair.Value.DefinitionFile); + IO.mapOptional("Members", Pair.Value.Members); + IO.mapOptional("Parents", Pair.Value.Parents); + IO.mapOptional("VirtualParents", Pair.Value.VirtualParents); + } +}; + +template <> struct MappingTraits> { + static void mapping(IO &IO, clang::doc::Pair &Pair) { + if (!Pair.Value.isDefined) + return; + IO.mapOptional("Qualified Name", Pair.Value.FullyQualifiedName); + IO.mapOptional("Name", Pair.Value.SimpleName); + IO.mapOptional("Namespace", Pair.Value.Namespace); + IO.mapOptional("Descriptions", Pair.Value.Descriptions); + IO.mapOptional("Locations", Pair.Value.Locations); + IO.mapOptional("DefinitionFile", Pair.Value.DefinitionFile); + IO.mapOptional("Scoped", Pair.Value.Scoped); + IO.mapOptional("Members", Pair.Value.Members); + } +}; + +template <> struct MappingTraits { + static void mapping(IO &IO, clang::doc::NamedType &NamedType) { + IO.mapOptional("Type", NamedType.Type); + IO.mapOptional("Name", NamedType.Name); + IO.mapOptional("Access", NamedType.Access); + } +}; + +template <> struct MappingTraits> { + static void mapping(IO &IO, + clang::doc::Pair &Pair) { + if (!Pair.Value.isDefined) + return; + IO.mapOptional("Mangled Name", Pair.Value.MangledName); + IO.mapOptional("Qualified Name", Pair.Value.FullyQualifiedName); + IO.mapOptional("Name", Pair.Value.SimpleName); + IO.mapOptional("Namespace", Pair.Value.Namespace); + IO.mapOptional("Descriptions", Pair.Value.Descriptions); + IO.mapOptional("Locations", Pair.Value.Locations); + IO.mapOptional("DefinitionFile", Pair.Value.DefinitionFile); + IO.mapOptional("Params", Pair.Value.Params); + IO.mapOptional("ReturnType", Pair.Value.ReturnType); + IO.mapOptional("Access", Pair.Value.Access); + } +}; + +template <> struct MappingTraits> { + static void mapping(IO &IO, clang::doc::Pair &Pair) { + IO.mapOptional("Filename", Pair.Value.Filename); + IO.mapOptional("Description", Pair.Value.Description); + } +}; + +template <> struct MappingTraits { + static void mapping(IO &IO, clang::doc::Documentation &Docs) { + MappingNormalization, + StringMap> + files(IO, Docs.Files); + MappingNormalization, + StringMap> + namespaces(IO, Docs.Namespaces); + MappingNormalization, + StringMap> + types(IO, Docs.Types); + MappingNormalization, + StringMap> + enums(IO, Docs.Enums); + + IO.mapOptional("Files", files->VectorMap); + IO.mapOptional("Namespaces", namespaces->VectorMap); + IO.mapOptional("Types", types->VectorMap); + IO.mapOptional("Enums", enums->VectorMap); + } +}; + +template <> struct MappingTraits { + + static void mapping(IO &IO, clang::doc::CommentInfo &Info) { + IO.mapOptional("Kind", Info.Kind); + if (!Info.Text.empty()) + IO.mapOptional("Text", Info.Text); + if (!Info.Name.empty()) + IO.mapOptional("Name", Info.Name); + if (!Info.Direction.empty()) + IO.mapOptional("Direction", Info.Direction); + if (!Info.ParamName.empty()) + IO.mapOptional("ParamName", Info.ParamName); + if (!Info.CloseName.empty()) + IO.mapOptional("CloseName", Info.CloseName); + if (Info.SelfClosing) + IO.mapOptional("SelfClosing", Info.SelfClosing); + if (Info.Explicit) + IO.mapOptional("Explicit", Info.Explicit); + IO.mapOptional("Args", Info.Args); + IO.mapOptional("Attrs", Info.Attrs); + IO.mapOptional("Position", Info.Position); + IO.mapOptional("Children", Info.Children); + } +}; + +} // end namespace yaml +} // end namespace llvm + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_DOC_CLANG_DOC_YAML_H Index: tools/clang-doc/tool/CMakeLists.txt =================================================================== --- /dev/null +++ tools/clang-doc/tool/CMakeLists.txt @@ -0,0 +1,18 @@ +include_directories(${CMAKE_CURRENT_SOURCE_DIR}/..) + +add_clang_executable(clang-doc + ClangDocMain.cpp + ) + +target_link_libraries(clang-doc + PRIVATE + clangAST + clangASTMatchers + clangBasic + clangFormat + clangFrontend + clangDoc + clangRewrite + clangTooling + clangToolingCore + ) Index: tools/clang-doc/tool/ClangDocMain.cpp =================================================================== --- /dev/null +++ tools/clang-doc/tool/ClangDocMain.cpp @@ -0,0 +1,68 @@ +//===-- 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 "ClangDoc.h" +#include "clang/Driver/Options.h" +#include "clang/Frontend/FrontendActions.h" +#include "clang/Tooling/CommonOptionsParser.h" +#include "clang/Tooling/Tooling.h" +#include "llvm/Support/Process.h" +#include "llvm/Support/Signals.h" +#include + +using namespace clang; +using namespace llvm; + +namespace { + +cl::OptionCategory ClangDocCategory("clang-doc options"); + +cl::opt + EmitLLVM("emit-llvm", + cl::desc("Output in LLVM bitstream format (default is YAML)."), + cl::init(false), cl::cat(ClangDocCategory)); + +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]); + tooling::CommonOptionsParser OptionsParser(argc, argv, ClangDocCategory); + + clang::doc::OutFormat EmitFormat = + EmitLLVM ? doc::OutFormat::LLVM : doc::OutFormat::YAML; + + // TODO: Update the source path list to only consider changed files for + // incremental doc updates. + doc::ClangDocReporter Reporter(OptionsParser.getSourcePathList()); + doc::ClangDocContext Context{EmitFormat}; + + tooling::ClangTool Tool(OptionsParser.getCompilations(), + OptionsParser.getSourcePathList()); + + if (!DoxygenOnly) + Tool.appendArgumentsAdjuster(tooling::getInsertArgumentAdjuster( + "-fparse-all-comments", tooling::ArgumentInsertPosition::BEGIN)); + + doc::ClangDocActionFactory Factory(Context, Reporter); + + outs() << "Parsing codebase...\n"; + int Status = Tool.run(&Factory); + if (Status) + return Status; + + outs() << "Writing docs...\n"; + Reporter.serialize(EmitFormat, outs()); + + return 0; +}