blob: 8b5c6f3661a298a33a21dd31abce05f4138c387c [file] [log] [blame]
//===--- CommentConversion.cpp - Conversion of comments to other formats --===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2017 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://swift.org/LICENSE.txt for license information
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//===----------------------------------------------------------------------===//
#include "swift/IDE/CommentConversion.h"
#include "swift/AST/ASTContext.h"
#include "swift/AST/Comment.h"
#include "swift/AST/Decl.h"
#include "swift/AST/USRGeneration.h"
#include "swift/AST/RawComment.h"
#include "swift/Basic/SourceManager.h"
#include "swift/Markup/Markup.h"
#include "swift/Markup/XMLUtils.h"
#include "swift/Parse/Token.h"
#include "swift/Subsystems.h"
#include "llvm/Support/MemoryBuffer.h"
#include "llvm/Support/raw_ostream.h"
#include "clang/AST/ASTContext.h"
#include "clang/AST/Decl.h"
#include "clang/Index/CommentToXML.h"
using namespace swift::markup;
using namespace swift;
//===----------------------------------------------------------------------===//
// Conversion to XML.
//===----------------------------------------------------------------------===//
namespace {
struct CommentToXMLConverter {
raw_ostream &OS;
CommentToXMLConverter(raw_ostream &OS) : OS(OS) {}
void printRawHTML(StringRef Tag) {
OS << "<rawHTML>";
appendWithCDATAEscaping(OS, Tag);
OS << "</rawHTML>";
}
void printASTNode(const MarkupASTNode *N) {
switch (N->getKind()) {
#define MARKUP_AST_NODE(Id, Parent) \
case ASTNodeKind::Id: \
print##Id(cast<Id>(N)); \
break;
#define ABSTRACT_MARKUP_AST_NODE(Id, Parent)
#define MARKUP_AST_NODE_RANGE(Id, FirstId, LastId)
#include "swift/Markup/ASTNodes.def"
}
}
#define MARKUP_SIMPLE_FIELD(Id, Keyword, XMLKind) \
void print##Id(const Id *Field) { \
OS << "<" #XMLKind << ">"; \
for (auto Child : Field->getChildren()) \
printASTNode(Child); \
\
OS << "</" #XMLKind << ">"; \
}
#include "swift/Markup/SimpleFields.def"
void printDocument(const Document *D) {
llvm_unreachable("Can't print a swift::markup::Document as XML directly");
}
void printBlockQuote(const BlockQuote *BQ) {
for (const auto *N : BQ->getChildren())
printASTNode(N);
}
void printList(const List *L) {
OS << (L->isOrdered() ? "<List-Number>" : "<List-Bullet>");
for (const auto *N : L->getChildren())
printASTNode(N);
OS << (L->isOrdered() ? "</List-Number>" : "</List-Bullet>");
}
void printItem(const Item *I) {
OS << "<Item>";
for (const auto *N : I->getChildren())
printASTNode(N);
OS << "</Item>";
}
void printCode(const Code *C) {
OS << "<codeVoice>";
appendWithXMLEscaping(OS, C->getLiteralContent());
OS << "</codeVoice>";
}
void printCodeBlock(const CodeBlock *CB) {
OS << "<CodeListing language=\"";
appendWithXMLEscaping(OS, CB->getLanguage());
OS << "\">";
SmallVector<StringRef, 16> CodeLines;
CB->getLiteralContent().split(CodeLines, "\n");
for (auto Line : CodeLines) {
OS << "<zCodeLineNumbered>";
appendWithCDATAEscaping(OS, Line);
OS << "</zCodeLineNumbered>";
}
OS << "</CodeListing>";
}
void printParagraph(const Paragraph *P) {
OS << "<Para>";
for (const auto *N : P->getChildren())
printASTNode(N);
OS << "</Para>";
}
void printHeader(const Header *H) {
llvm::SmallString<4> Tag;
llvm::raw_svector_ostream TagStream(Tag);
TagStream << "<h" << H->getLevel() << ">";
printRawHTML(TagStream.str());
for (auto Child : H->getChildren())
printASTNode(Child);
llvm::SmallString<5> EndTag;
llvm::raw_svector_ostream EndTagStream(EndTag);
EndTagStream << "</h" << H->getLevel() << ">";
printRawHTML(EndTagStream.str());
}
void printHRule(const HRule *HR) {
printRawHTML("<hr/>");
}
void printText(const Text *T) {
appendWithXMLEscaping(OS, T->getLiteralContent());
}
void printHTML(const HTML *H) {
printRawHTML(H->getLiteralContent());
}
void printInlineHTML(const InlineHTML *IH) {
printRawHTML(IH->getLiteralContent());
}
void printEmphasis(const Emphasis *E) {
OS << "<emphasis>";
for (const auto *IC : E->getChildren())
printASTNode(IC);
OS << "</emphasis>";
}
void printStrong(const Strong *S) {
OS << "<bold>";
for (const auto *N : S->getChildren())
printASTNode(N);
OS << "</bold>";
}
void printLink(const Link *L) {
SmallString<32> Tag;
llvm::raw_svector_ostream S(Tag);
S << "<Link href=\"";
appendWithXMLEscaping(S, L->getDestination());
S << "\">";
OS << S.str();
for (const auto N : L->getChildren())
printASTNode(N);
OS << "</Link>";
}
void printSoftBreak(const SoftBreak *SB) {
OS << " ";
}
void printLineBreak(const LineBreak *LB) {
printRawHTML("<br/>");
}
void printPrivateExtension(const PrivateExtension *PE) {
llvm_unreachable("Can't directly print a Swift Markup PrivateExtension");
}
void printImage(const Image *I) {
SmallString<64> Tag;
llvm::raw_svector_ostream S(Tag);
S << "<img src=\"" << I->getDestination() << "\"";
if (I->hasTitle())
S << " title=\"" << I->getTitle() << "\"";
if (I->getChildren().size()) {
S << " alt=\"";
for (const auto N : I->getChildren())
printInlinesUnder(N, S);
S << "\"";
}
S << "/>";
printRawHTML(S.str());
}
void printParamField(const ParamField *PF) {
OS << "<Parameter>";
OS << "<Name>";
OS << PF->getName();
OS << "</Name>";
OS << "<Direction isExplicit=\"0\">in</Direction>";
if (PF->isClosureParameter()) {
OS << "<ClosureParameter>";
visitCommentParts(PF->getParts().getValue());
OS << "</ClosureParameter>";
} else {
OS << "<Discussion>";
for (auto Child : PF->getChildren()) {
printASTNode(Child);
}
OS << "</Discussion>";
}
OS << "</Parameter>";
}
void printResultDiscussion(const ReturnsField *RF) {
OS << "<ResultDiscussion>";
for (auto Child : RF->getChildren())
printASTNode(Child);
OS << "</ResultDiscussion>";
}
void printThrowsDiscussion(const ThrowsField *RF) {
OS << "<ThrowsDiscussion>";
for (auto Child : RF->getChildren())
printASTNode(Child);
OS << "</ThrowsDiscussion>";
}
void printTagFields(ArrayRef<StringRef> Tags) {
OS << "<Tags>";
for (const auto Tag : Tags) {
if (Tag.empty()) {
continue;
}
OS << "<Tag>";
appendWithXMLEscaping(OS, Tag);
OS << "</Tag>";
}
OS << "</Tags>";
}
void visitDocComment(const DocComment *DC);
void visitCommentParts(const swift::markup::CommentParts &Parts);
};
} // unnamed namespace
void CommentToXMLConverter::visitCommentParts(const swift::markup::CommentParts &Parts) {
if (Parts.Brief.hasValue()) {
OS << "<Abstract>";
printASTNode(Parts.Brief.getValue());
OS << "</Abstract>";
}
if (!Parts.ParamFields.empty()) {
OS << "<Parameters>";
for (const auto *PF : Parts.ParamFields)
printParamField(PF);
OS << "</Parameters>";
}
if (Parts.ReturnsField.hasValue())
printResultDiscussion(Parts.ReturnsField.getValue());
if (Parts.ThrowsField.hasValue())
printThrowsDiscussion(Parts.ThrowsField.getValue());
if (!Parts.Tags.empty()) {
printTagFields(llvm::makeArrayRef(Parts.Tags.begin(), Parts.Tags.end()));
}
if (!Parts.BodyNodes.empty()) {
OS << "<Discussion>";
for (const auto *N : Parts.BodyNodes)
printASTNode(N);
OS << "</Discussion>";
}
}
void CommentToXMLConverter::visitDocComment(const DocComment *DC) {
const Decl *D = DC->getDecl();
StringRef RootEndTag;
if (isa<AbstractFunctionDecl>(D)) {
OS << "<Function";
RootEndTag = "</Function>";
} else if (isa<StructDecl>(D) || isa<ClassDecl>(D) || isa<ProtocolDecl>(D)) {
OS << "<Class";
RootEndTag = "</Class>";
} else {
OS << "<Other";
RootEndTag = "</Other>";
}
{
// Print line and column number.
auto Loc = D->getLoc();
if (Loc.isValid()) {
const auto &SM = D->getASTContext().SourceMgr;
StringRef FileName = SM.getDisplayNameForLoc(Loc);
auto LineAndColumn = SM.getLineAndColumn(Loc);
OS << " file=\"";
appendWithXMLEscaping(OS, FileName);
OS << "\"";
OS << " line=\"" << LineAndColumn.first << "\" column=\""
<< LineAndColumn.second << "\"";
}
}
// Finish the root tag.
OS << ">";
auto *VD = dyn_cast<ValueDecl>(D);
OS << "<Name>";
if (VD && VD->hasName()) {
llvm::SmallString<64> SS;
llvm::raw_svector_ostream NameOS(SS);
NameOS << VD->getFullName();
appendWithXMLEscaping(OS, NameOS.str());
}
OS << "</Name>";
if (VD) {
llvm::SmallString<64> SS;
bool Failed;
{
llvm::raw_svector_ostream OS(SS);
Failed = ide::printDeclUSR(VD, OS);
}
if (!Failed && !SS.empty()) {
OS << "<USR>" << SS << "</USR>";
}
}
{
PrintOptions PO = PrintOptions::printInterface();
PO.PrintAccess = false;
PO.AccessFilter = AccessLevel::Private;
PO.PrintDocumentationComments = false;
PO.TypeDefinitions = false;
PO.VarInitializers = false;
PO.ShouldQualifyNestedDeclarations =
PrintOptions::QualifyNestedDeclarations::TypesOnly;
OS << "<Declaration>";
llvm::SmallString<32> DeclSS;
{
llvm::raw_svector_ostream DeclOS(DeclSS);
D->print(DeclOS, PO);
}
appendWithXMLEscaping(OS, DeclSS);
OS << "</Declaration>";
}
OS << "<CommentParts>";
visitCommentParts(DC->getParts());
OS << "</CommentParts>";
OS << RootEndTag;
}
static bool getClangDocumentationCommentAsXML(const clang::Decl *D,
raw_ostream &OS) {
const auto &ClangContext = D->getASTContext();
const clang::comments::FullComment *FC =
ClangContext.getCommentForDecl(D, /*PP=*/nullptr);
if (!FC)
return false;
// FIXME: hang the converter object somewhere so that it is persistent
// between requests to this AST.
clang::index::CommentToXMLConverter Converter;
llvm::SmallString<1024> XML;
Converter.convertCommentToXML(FC, XML, ClangContext);
OS << XML;
return true;
}
static void replaceObjcDeclarationsWithSwiftOnes(const Decl *D,
StringRef Doc,
raw_ostream &OS) {
StringRef Open = "<Declaration>";
StringRef Close = "</Declaration>";
PrintOptions Options = PrintOptions::printQuickHelpDeclaration();
std::string S;
llvm::raw_string_ostream SS(S);
D->print(SS, Options);
auto OI = Doc.find(Open);
auto CI = Doc.find(Close);
if (StringRef::npos != OI && StringRef::npos != CI && CI > OI) {
OS << Doc.substr(0, OI) << Open;
appendWithXMLEscaping(OS, SS.str());
OS << Close << Doc.substr(CI + Close.size());
} else {
OS << Doc;
}
}
static LineList getLineListFromComment(SourceManager &SourceMgr,
swift::markup::MarkupContext &MC,
const StringRef Text) {
LangOptions LangOpts;
auto Tokens = swift::tokenize(LangOpts, SourceMgr,
SourceMgr.addMemBufferCopy(Text));
std::vector<SingleRawComment> Comments;
Comments.reserve(Tokens.size());
for (auto &Tok : Tokens) {
if (Tok.is(tok::comment)) {
Comments.push_back(SingleRawComment(Tok.getText(), 0));
}
}
if (Comments.empty())
return {};
RawComment Comment(Comments);
return MC.getLineList(Comment);
}
std::string ide::extractPlainTextFromComment(const StringRef Text) {
SourceManager SourceMgr;
swift::markup::MarkupContext MC;
return getLineListFromComment(SourceMgr, MC, Text).str();
}
bool ide::getDocumentationCommentAsXML(const Decl *D, raw_ostream &OS) {
auto MaybeClangNode = D->getClangNode();
if (MaybeClangNode) {
if (auto *CD = MaybeClangNode.getAsDecl()) {
std::string S;
llvm::raw_string_ostream SS(S);
if (getClangDocumentationCommentAsXML(CD, SS)) {
replaceObjcDeclarationsWithSwiftOnes(D, SS.str(), OS);
return true;
}
}
return false;
}
swift::markup::MarkupContext MC;
auto DC = getCascadingDocComment(MC, D);
if (!DC)
return false;
CommentToXMLConverter Converter(OS);
Converter.visitDocComment(DC);
OS.flush();
return true;
}
bool ide::getLocalizationKey(const Decl *D, raw_ostream &OS) {
swift::markup::MarkupContext MC;
auto DC = getCascadingDocComment(MC, D);
if (!DC)
return false;
if (const auto LKF = DC->getLocalizationKeyField()) {
printInlinesUnder(LKF.getValue(), OS);
return true;
}
return false;
}
bool ide::convertMarkupToXML(StringRef Text, raw_ostream &OS) {
std::string Comment;
{
llvm::raw_string_ostream OS(Comment);
OS << "/**\n" << Text << "\n" << "*/";
}
SourceManager SourceMgr;
MarkupContext MC;
LineList LL = getLineListFromComment(SourceMgr, MC, Comment);
if (auto *Doc = swift::markup::parseDocument(MC, LL)) {
CommentToXMLConverter Converter(OS);
Converter.visitCommentParts(extractCommentParts(MC, Doc));
OS.flush();
return false;
}
return true;
}
//===----------------------------------------------------------------------===//
// Conversion to Doxygen.
//===----------------------------------------------------------------------===//
class DoxygenConverter : public MarkupASTVisitor<DoxygenConverter> {
llvm::raw_ostream &OS;
unsigned Indent;
unsigned IsFreshLine : 1;
unsigned IsEmptyComment : 1;
void printIndent() {
for (unsigned i = 0; i < Indent; ++i) {
OS << ' ';
}
}
void indent(unsigned Amount = 2) {
Indent += Amount;
}
void dedent(unsigned Amount = 2) {
Indent -= Amount;
}
void print(StringRef Str) {
for (auto c : Str) {
if (c == '\n') {
if (IsFreshLine && !IsEmptyComment)
OS << "///";
IsFreshLine = true;
} else {
if (IsFreshLine && !IsEmptyComment)
OS << "///";
if (IsFreshLine) {
printIndent();
IsFreshLine = false;
}
}
OS << c;
IsEmptyComment = false;
}
}
void printNestedParamField(const ParamField *PF) {
auto Parts = PF->getParts().getValue();
if (Parts.Brief.hasValue()) {
visit(Parts.Brief.getValue());
}
if (!Parts.ParamFields.empty()) {
printNewline();
print("\\a ");
print(PF->getName());
print(" parameters:");
printNewline();
print("<ul>");
printNewline();
for (auto Param : Parts.ParamFields) {
print("<li>");
printNewline();
print(Param->getName());
print(": ");
printNestedParamField(Param);
print("</li>");
printNewline();
}
print("</ul>");
printNewline();
printNewline();
}
if (Parts.ReturnsField.hasValue()) {
printNewline();
print("\\a ");
print(PF->getName());
print(" returns: ");
for (auto Child : Parts.ReturnsField.getValue()->getChildren()) {
visit(Child);
}
}
if (Parts.ThrowsField.hasValue()) {
printNewline();
print("\\a ");
print(PF->getName());
print(" error: ");
for (auto Child : Parts.ThrowsField.getValue()->getChildren()) {
visit(Child);
}
}
for (auto BodyNode : Parts.BodyNodes) {
visit(BodyNode);
}
}
public:
DoxygenConverter(llvm::raw_ostream &OS)
: OS(OS), Indent(1), IsFreshLine(true), IsEmptyComment(true) {
printOpeningComment();
}
void printNewline() {
print("\n");
}
void printOpeningComment() {
OS << "///";
}
void printUncommentedNewline() {
OS << '\n';
}
void visitDocument(const Document *D) {
for (const auto *Child : D->getChildren())
visit(Child);
}
void visitBlockQuote(const BlockQuote *BQ) {
print("<blockquote>");
printNewline();
for (const auto *Child : BQ->getChildren())
visit(Child);
printNewline();
print("</blockquote>");
printNewline();
}
void visitList(const List *BL) {
print(BL->isOrdered() ? "<ol>" : "<ul>");
indent();
printNewline();
for (const auto *Child : BL->getChildren())
visit(Child);
dedent();
print(BL->isOrdered() ? "</ol>" : "</ul>");
printNewline();
}
void visitItem(const Item *I) {
print("<li>");
indent();
printNewline();
for (const auto *N : I->getChildren())
visit(N);
dedent();
print("</li>");
printNewline();
}
void visitCodeBlock(const CodeBlock *CB) {
print("\\code");
printNewline();
print(CB->getLiteralContent());
printNewline();
print("\\endcode");
}
void visitCode(const Code *C) {
print("<code>");
print(C->getLiteralContent());
print("</code>");
}
void visitHTML(const HTML *H) {
print(H->getLiteralContent());
}
void visitInlineHTML(const InlineHTML *IH) {
print(IH->getLiteralContent());
}
void visitSoftBreak(const SoftBreak *SB) {
printNewline();
}
void visitLineBreak(const LineBreak *LB) {
print("<br/>");
printNewline();
}
void visitLink(const Link *L) {
SmallString<32> Tag;
llvm::raw_svector_ostream S(Tag);
S << "<a href=\"" << L->getDestination() << "\">";
print(S.str());
for (const auto *Child : L->getChildren())
visit(Child);
print("</a>");
}
void visitImage(const Image *I) {
SmallString<64> Tag;
llvm::raw_svector_ostream S(Tag);
S << "<img src=\"" << I->getDestination() << "\"";
if (I->hasTitle())
S << " title=\"" << I->getTitle() << "\"";
if (I->getChildren().size()) {
S << " alt=\"";
for (const auto *Child : I->getChildren())
printInlinesUnder(Child, S);
S << "\"";
}
S << "/>";
print(S.str());
}
void visitParagraph(const Paragraph *P) {
for (const auto *Child : P->getChildren())
visit(Child);
printNewline();
}
void visitEmphasis(const Emphasis *E) {
print("<em>");
for (const auto *Child : E->getChildren())
visit(Child);
print("</em>");
}
void visitStrong(const Strong *E) {
print("<em>");
for (const auto *Child : E->getChildren())
visit(Child);
print("</em>");
}
void visitHRule(const HRule *HR) {
print("<hr/>");
printNewline();
}
void visitHeader(const Header *H) {
llvm::SmallString<4> Tag;
llvm::raw_svector_ostream TagStream(Tag);
TagStream << "<h" << H->getLevel() << ">";
print(TagStream.str());
for (const auto *Child : H->getChildren())
visit(Child);
llvm::SmallString<5> EndTag;
llvm::raw_svector_ostream EndTagStream(EndTag);
EndTagStream << "</h" << H->getLevel() << ">";
print(EndTagStream.str());
printNewline();
}
void visitText(const Text *T) {
print(T->getLiteralContent());
}
void visitPrivateExtension(const PrivateExtension *PE) {
llvm_unreachable("Can't directly print Doxygen for a Swift Markup PrivateExtension");
}
void visitParamField(const ParamField *PF) {
print("\\param ");
print(PF->getName());
print(" ");
if (PF->isClosureParameter()) {
printNestedParamField(PF);
} else {
for (const auto *Child : PF->getChildren()) {
visit(Child);
}
}
printNewline();
}
void visitReturnField(const ReturnsField *RF) {
print("\\returns ");
for (const auto *Child : RF->getChildren())
visit(Child);
printNewline();
}
void visitThrowField(const ThrowsField *TF) {
print("\\param error ");
for (const auto *Child : TF->getChildren())
visit(Child);
printNewline();
}
#define MARKUP_SIMPLE_FIELD(Id, Keyword, XMLKind) \
void visit##Id(const Id *Field) { \
print(#Keyword); \
print(":"); \
printNewline(); \
for (const auto *Child : Field->getChildren()) \
visit(Child); \
}
#include "swift/Markup/SimpleFields.def"
~DoxygenConverter() override {
if (IsEmptyComment || !IsFreshLine)
printUncommentedNewline();
}
};
void ide::getDocumentationCommentAsDoxygen(const DocComment *DC,
raw_ostream &OS) {
DoxygenConverter Converter(OS);
auto Brief = DC->getBrief();
if (Brief.hasValue()) {
Converter.visit(Brief.getValue());
}
for (const auto *N : DC->getBodyNodes()) {
if (const auto *P = dyn_cast<Paragraph>(N)) {
Converter.visit(P);
continue;
}
Converter.visit(N);
}
for (const auto PF : DC->getParamFields()) {
Converter.visit(PF);
}
auto TF = DC->getThrowsField();
if (TF.hasValue()) {
Converter.printNewline();
Converter.visit(TF.getValue());
}
auto RF = DC->getReturnsField();
if (RF.hasValue()) {
Converter.printNewline();
Converter.visit(RF.getValue());
}
}