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,102 @@ +// 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: Parsing codebase... +// CHECK: Writing docs... +// CHECK: --- +// CHECK: Files: +// CHECK: - Filename: [[ROOT]]/test.cpp +// CHECK: Description: +// CHECK: Kind: '' +// CHECK: Namespaces: +// CHECK: - Qualified Name: A +// CHECK: Name: A +// CHECK: Namespace: +// CHECK: Type: Namespace +// CHECK: Name: '' +// CHECK: Descriptions: +// CHECK: - Kind: FullComment +// CHECK: Children: +// CHECK: - Kind: ParagraphComment +// CHECK: Children: +// CHECK: - Kind: TextComment +// CHECK: Text: ' Test namespace' +// CHECK: Locations: +// CHECK: - Type: File +// CHECK: Name: '[[ROOT]]/test.cpp:8:1' +// CHECK: Types: +// CHECK: - Qualified Name: C +// CHECK: Name: C +// CHECK: Namespace: +// CHECK: Type: Namespace +// CHECK: Name: '' +// CHECK: Descriptions: +// CHECK: - Kind: FullComment +// CHECK: Children: +// CHECK: - Kind: ParagraphComment +// CHECK: Children: +// CHECK: - Kind: TextComment +// CHECK: Text: ' A C++ class' +// CHECK: TagType: Class +// CHECK: Locations: +// CHECK: - Type: File +// CHECK: Name: '[[ROOT]]/test.cpp:16:1' +// CHECK: DefinitionFile: +// CHECK: Type: File +// CHECK: Name: '[[ROOT]]/test.cpp:16:1' +// CHECK: Members: +// CHECK: - Type: +// CHECK: Type: Type +// CHECK: Name: int +// CHECK: Name: 'C::i' +// CHECK: Access: None +// CHECK: Functions: +// CHECK: - Qualified Name: 'A::f' +// CHECK: Name: f +// CHECK: Namespace: +// CHECK: Type: Namespace +// CHECK: Name: A +// CHECK: Descriptions: +// CHECK: - Kind: FullComment +// CHECK: Children: +// CHECK: - Kind: ParagraphComment +// CHECK: Children: +// CHECK: - Kind: TextComment +// CHECK: Text: ' A function' +// CHECK: Locations: +// CHECK: - Type: File +// CHECK: Name: '[[ROOT]]/test.cpp:11:1' +// CHECK: DefinitionFile: +// CHECK: Type: File +// CHECK: Name: '' +// CHECK: Params: +// CHECK: - Type: +// CHECK: Type: Type +// CHECK: Name: int +// CHECK: Name: x +// CHECK: Access: None +// CHECK: ReturnType: +// CHECK: Type: Type +// CHECK: Name: void +// CHECK: Parent: +// CHECK: Type: Type +// CHECK: Name: '' +// CHECK: Access: None +// CHECK: ... 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,96 @@ +//===-- 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; + std::string getLocation(const Decl *D) const; + comments::FullComment* getComment(const Decl *D); + StringRef 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,113 @@ +//===-- 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 (D->isThisDeclarationADefinition()) + Reporter.createTypeInfo(D, getComment(D), getLocation(D), + D->getLocStart().printToString(Manager)); + return true; +} + +bool ClangDocVisitor::VisitNamespaceDecl(const NamespaceDecl *D) { + if (!isUnparsed(D->getLocation())) + return true; + Reporter.createNamespaceInfo(D, getComment(D), getLocation(D)); + return true; +} + +bool ClangDocVisitor::VisitFunctionDecl(const FunctionDecl *D) { + if (!isUnparsed(D->getLocation())) + return true; + + std::string DefFile; + if (D->isDefined()) + DefFile = getLocation(D->getDefinition()); + + Reporter.createFunctionInfo(D, getComment(D), getLocation(D), DefFile, + 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; +} + +std::string ClangDocVisitor::getLocation(const Decl *D) const { + return D->getLocStart().printToString(Manager); +} + +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; +} + +StringRef 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,171 @@ +//===-- 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 + +using namespace clang::comments; + +namespace clang { +namespace doc { + +enum class OutFormat { YAML, LLVM }; +enum class DeclType { FILE, TYPE, NAMESPACE, FUNCTION }; + +/// 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::StringMap Attrs; + llvm::SmallVector Args; + llvm::SmallVector Position; + std::vector Children; +}; + +/// A link to a different Info, providing details to get the appropriate Info. +struct Link { + DeclType InfoType; + std::string FullyQualifiedName; +}; + +/// A source file in the project. +struct File { + std::string Filename; + CommentInfo Description; + llvm::SmallVector UnattachedComments; +}; + +// Info for named types (parameters, members). +struct NamedType { + Link Type; + std::string Name; + AccessSpecifier Access; +}; + +/// A base struct for Infos. +struct Info { + std::string FullyQualifiedName; + std::string SimpleName; + Link Namespace; + std::vector Descriptions; + llvm::SmallVector Locations; +}; + +// TODO: Expand to allow for documenting templating. +// Info for types. +struct TypeInfo : public Info { + Link DefinitionFile; + TagTypeKind TagType; + llvm::SmallVector Members; + llvm::SmallVector Parents; + llvm::SmallVector VirtualParents; +}; + +// TODO: Expand to allow for documenting templating. +// Info for functions. +struct FunctionInfo : public Info { + Link DefinitionFile; + Link ReturnType; + Link Parent; + llvm::SmallVector Params; + AccessSpecifier Access; +}; + +/// A struct encapsulating all documentation information for the project. +struct Documentation { + llvm::StringMap Files; + llvm::StringMap Namespaces; + llvm::StringMap Types; + llvm::StringMap Functions; + // 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, + StringRef Loc); + void createTypeInfo(const TagDecl *D, const FullComment *C, StringRef Loc, + StringRef DefFile); + void createFunctionInfo(const FunctionDecl *D, const FullComment *C, + StringRef Loc, StringRef DefFile, + 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, StringRef Location) const; + + void parseComment(CommentInfo *CI, const comments::Comment *C); + void parseFields(TypeInfo &I, const RecordDecl *D) const; + void parseEnumerators(TypeInfo &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; + Link makeLink(DeclType, StringRef Name) 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,297 @@ +//===-- 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/YAMLTraits.h" +#include "llvm/Support/raw_ostream.h" + +using namespace llvm; +using clang::comments::FullComment; + +namespace clang { +namespace doc { + +ClangDocReporter::ClangDocReporter(const std::vector &Sources) { + for (const std::string &Path : Sources) + addFile(Path); +} + +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, + StringRef Loc) { + std::string Name = D->getQualifiedNameAsString(); + Info I; + llvm::StringMapIterator Pair = Docs.Namespaces.find(Name); + if (Pair != Docs.Namespaces.end()) + I = Pair->second; + else { + I.FullyQualifiedName = Name; + I.SimpleName = D->getNameAsString(); + I.Namespace = makeLink(DeclType::NAMESPACE, getParentNamespace(D)); + } + addLocation(I, Loc); + addComment(I, C); + Docs.Namespaces[Name] = I; +} + +void ClangDocReporter::createTypeInfo(const TagDecl *D, const FullComment *C, + StringRef Loc, StringRef DefFile) { + std::string Name = D->getQualifiedNameAsString(); + TypeInfo I; + llvm::StringMapIterator Pair = Docs.Types.find(Name); + if (Pair != Docs.Types.end()) + I = Pair->second; + else { + I.FullyQualifiedName = Name; + I.SimpleName = D->getNameAsString(); + I.TagType = D->getTagKind(); + I.Namespace = makeLink(DeclType::NAMESPACE, getParentNamespace(D)); + if (!DefFile.empty()) + I.DefinitionFile = makeLink(DeclType::FILE, DefFile); + } + addLocation(I, Loc); + addComment(I, C); + if (const auto *E = dyn_cast(D)) + parseEnumerators(I, E); + else if (const auto *R = dyn_cast(D)) { + parseFields(I, R); + if (const auto *CXXR = dyn_cast(D)) + parseBases(I, CXXR); + } + Docs.Types[Name] = I; +} + +void ClangDocReporter::createFunctionInfo(const FunctionDecl *D, + const FullComment *C, StringRef Loc, + StringRef DefFile, + StringRef MangledName) { + FunctionInfo I; + llvm::StringMapIterator Pair = Docs.Functions.find(MangledName); + if (Pair != Docs.Functions.end()) + I = Pair->second; + else { + I.FullyQualifiedName = D->getQualifiedNameAsString(); + I.SimpleName = D->getNameAsString(); + I.DefinitionFile = makeLink(DeclType::FILE, DefFile); + I.ReturnType = makeLink(DeclType::TYPE, D->getReturnType().getAsString()); + I.Namespace = makeLink(DeclType::NAMESPACE, getParentNamespace(D)); + std::string Parent; + if (const auto *M = dyn_cast(D)) { + Parent = M->getParent()->getQualifiedNameAsString(); + I.Access = D->getAccess(); + } else { + Parent = ""; + I.Access = clang::AccessSpecifier::AS_none; + } + I.Parent = makeLink(DeclType::TYPE, Parent); + parseParameters(I, D); + } + addLocation(I, Loc); + addComment(I, C); + Docs.Functions[MangledName] = I; +} + +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); + CurrentCI->Attrs.insert(std::make_pair(Attr.Name, Attr.Value)); + } +} + +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 &Info, const FullComment *C) { + if (!C) + return; + CommentInfo CI; + parseFullComment(C, CI); + Info.Descriptions.push_back(CI); +} + +void ClangDocReporter::addLocation(Info &I, StringRef Location) const { + I.Locations.push_back(makeLink(DeclType::FILE, Location)); +} + +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 = makeLink(DeclType::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(TypeInfo &I, const EnumDecl *D) const { + for (const EnumConstantDecl *E : D->enumerators()) { + NamedType N; + N.Type = makeLink(DeclType::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 = makeLink(DeclType::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()) { + std::string Parent = B.getType().getAsString(); + B.isVirtual() ? I.VirtualParents.push_back(makeLink(DeclType::TYPE, Parent)) + : I.Parents.push_back(makeLink(DeclType::TYPE, Parent)); + } + for (const CXXBaseSpecifier &B : D->vbases()) + I.VirtualParents.push_back(makeLink(DeclType::TYPE, + 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 ""; +} + +Link ClangDocReporter::makeLink(DeclType Type, StringRef Name) const { + Link L{Type, Name}; + return L; +} + +} // namespace doc +} // namespace clang Index: tools/clang-doc/ClangDocYAML.h =================================================================== --- /dev/null +++ tools/clang-doc/ClangDocYAML.h @@ -0,0 +1,193 @@ +//===-- 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. +// +//===----------------------------------------------------------------------===// + +#include "llvm/Support/YAMLTraits.h" + +LLVM_YAML_IS_SEQUENCE_VECTOR(clang::doc::CommentInfo) +LLVM_YAML_IS_SEQUENCE_VECTOR(clang::doc::Link) +LLVM_YAML_IS_SEQUENCE_VECTOR(clang::doc::NamedType) +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::doc::DeclType &value) { + io.enumCase(value, "File", clang::doc::DeclType::FILE); + io.enumCase(value, "Type", clang::doc::DeclType::TYPE); + io.enumCase(value, "Namespace", clang::doc::DeclType::NAMESPACE); + io.enumCase(value, "Function", clang::doc::DeclType::FUNCTION); + } +}; + +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); + } +}; + +// TODO: uncomment description attrs +template <> struct MappingTraits> { + static void mapping(IO &IO, clang::doc::Pair &Pair) { + 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); + } +}; + +template <> struct MappingTraits> { + static void mapping(IO &IO, clang::doc::Pair &Pair) { + 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("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::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) { + 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("Parent", Pair.Value.Parent); + 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::Link &Link) { + StringRef Type; + IO.mapOptional("Type", Link.InfoType); + IO.mapOptional("Name", Link.FullyQualifiedName); + } +}; + +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> + functions(IO, Docs.Functions); + + IO.mapOptional("Files", files->VectorMap); + IO.mapOptional("Namespaces", namespaces->VectorMap); + IO.mapOptional("Types", types->VectorMap); + IO.mapOptional("Functions", functions->VectorMap); + } +}; + +template <> struct MappingTraits { + + static void mapping(IO &IO, clang::doc::CommentInfo &Info) { + MappingNormalization, StringMap> + keys(IO, Info.Attrs); + + 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", keys->VectorMap); + IO.mapOptional("Position", Info.Position); + IO.mapOptional("Children", Info.Children); + } +}; + +} // end namespace yaml +} // end namespace llvm 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; +}