mirror of
https://github.com/apple/swift.git
synced 2025-12-14 20:36:38 +01:00
This allows authors to not have to copy and paste doc comments from requirements to default implementations to get documentation. For now, this will only be active if a protocol doesn't inherit two equivalent requirements from other protocols with doc comments. Those will need special treatment and it would be better not to just copy and paste them into the same doc comment. More output structure is needed to differentiate documentation for different protocols' requirements. rdar://problem/16512247
454 lines
14 KiB
C++
454 lines
14 KiB
C++
//===--- DocComment.cpp - Extraction of doc comments ----------------------===//
|
|
//
|
|
// This source file is part of the Swift.org open source project
|
|
//
|
|
// Copyright (c) 2014 - 2016 Apple Inc. and the Swift project authors
|
|
// Licensed under Apache License v2.0 with Runtime Library Exception
|
|
//
|
|
// See http://swift.org/LICENSE.txt for license information
|
|
// See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
///
|
|
/// \file
|
|
/// This file implements extraction of documentation comments from a Swift
|
|
/// Markup AST tree.
|
|
///
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
#include "swift/AST/Comment.h"
|
|
#include "swift/AST/Decl.h"
|
|
#include "swift/AST/PrettyStackTrace.h"
|
|
#include "swift/AST/RawComment.h"
|
|
#include "swift/Markup/Markup.h"
|
|
|
|
using namespace swift;
|
|
|
|
void *DocComment::operator new(size_t Bytes, swift::markup::MarkupContext &MC,
|
|
unsigned Alignment) {
|
|
return MC.allocate(Bytes, Alignment);
|
|
}
|
|
|
|
Optional<swift::markup::ParamField *> extractParamOutlineItem(
|
|
swift::markup::MarkupContext &MC,
|
|
swift::markup::MarkupASTNode *Node) {
|
|
|
|
auto Item = dyn_cast<swift::markup::Item>(Node);
|
|
if (!Item)
|
|
return None;
|
|
|
|
auto Children = Item->getChildren();
|
|
if (Children.empty())
|
|
return None;
|
|
|
|
auto FirstChild = Children.front();
|
|
auto FirstParagraph = dyn_cast<swift::markup::Paragraph>(FirstChild);
|
|
if (!FirstParagraph)
|
|
return None;
|
|
|
|
auto FirstParagraphChildren = FirstParagraph->getChildren();
|
|
if (FirstParagraphChildren.empty())
|
|
return None;
|
|
|
|
auto ParagraphText =
|
|
dyn_cast<swift::markup::Text>(FirstParagraphChildren.front());
|
|
if (!ParagraphText)
|
|
return None;
|
|
|
|
StringRef Name;
|
|
StringRef Remainder;
|
|
std::tie(Name, Remainder) = ParagraphText->getLiteralContent().split(':');
|
|
Name = Name.rtrim();
|
|
|
|
if (Name.empty())
|
|
return None;
|
|
|
|
ParagraphText->setLiteralContent(Remainder.ltrim());
|
|
|
|
return swift::markup::ParamField::create(MC, Name, Children);
|
|
}
|
|
|
|
bool extractParameterOutline(
|
|
swift::markup::MarkupContext &MC, swift::markup::List *L,
|
|
SmallVectorImpl<swift::markup::ParamField *> &ParamFields) {
|
|
SmallVector<swift::markup::MarkupASTNode *, 8> NormalItems;
|
|
auto Children = L->getChildren();
|
|
if (Children.empty())
|
|
return false;
|
|
|
|
for (auto Child : Children) {
|
|
auto I = dyn_cast<swift::markup::Item>(Child);
|
|
if (!I) {
|
|
NormalItems.push_back(Child);
|
|
continue;
|
|
}
|
|
|
|
auto ItemChildren = I->getChildren();
|
|
if (ItemChildren.empty()) {
|
|
NormalItems.push_back(Child);
|
|
continue;
|
|
}
|
|
|
|
auto FirstChild = ItemChildren.front();
|
|
auto FirstParagraph = dyn_cast<swift::markup::Paragraph>(FirstChild);
|
|
if (!FirstParagraph) {
|
|
NormalItems.push_back(Child);
|
|
continue;
|
|
}
|
|
|
|
auto FirstParagraphChildren = FirstParagraph->getChildren();
|
|
if (FirstParagraphChildren.empty()) {
|
|
NormalItems.push_back(Child);
|
|
continue;
|
|
}
|
|
|
|
auto HeadingText
|
|
= dyn_cast<swift::markup::Text>(FirstParagraphChildren.front());
|
|
if (!HeadingText) {
|
|
NormalItems.push_back(Child);
|
|
continue;
|
|
}
|
|
|
|
auto HeadingContent = HeadingText->getLiteralContent();
|
|
if (!HeadingContent.rtrim().equals_lower("parameters:")) {
|
|
NormalItems.push_back(Child);
|
|
continue;
|
|
}
|
|
|
|
auto Rest = ArrayRef<swift::markup::MarkupASTNode *>(
|
|
ItemChildren.begin() + 1, ItemChildren.end());
|
|
if (Rest.empty()) {
|
|
NormalItems.push_back(Child);
|
|
continue;
|
|
}
|
|
|
|
for (auto Child : Rest) {
|
|
auto SubList = dyn_cast<swift::markup::List>(Child);
|
|
if (!SubList)
|
|
continue;
|
|
|
|
for (auto SubListChild : SubList->getChildren()) {
|
|
auto Param = extractParamOutlineItem(MC, SubListChild);
|
|
if (Param.hasValue()) {
|
|
ParamFields.push_back(Param.getValue());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (NormalItems.size() != Children.size()) {
|
|
L->setChildren(NormalItems);
|
|
}
|
|
|
|
return NormalItems.size() == 0;
|
|
}
|
|
|
|
bool extractSeparatedParams(
|
|
swift::markup::MarkupContext &MC, swift::markup::List *L,
|
|
SmallVectorImpl<swift::markup::ParamField *> &ParamFields) {
|
|
SmallVector<swift::markup::MarkupASTNode *, 8> NormalItems;
|
|
auto Children = L->getChildren();
|
|
|
|
for (auto Child : Children) {
|
|
auto I = dyn_cast<swift::markup::Item>(Child);
|
|
if (!I) {
|
|
NormalItems.push_back(Child);
|
|
continue;
|
|
}
|
|
|
|
auto ItemChildren = I->getChildren();
|
|
if (ItemChildren.empty()) {
|
|
NormalItems.push_back(Child);
|
|
continue;
|
|
}
|
|
|
|
auto FirstChild = ItemChildren.front();
|
|
auto FirstParagraph = dyn_cast<swift::markup::Paragraph>(FirstChild);
|
|
if (!FirstParagraph) {
|
|
NormalItems.push_back(Child);
|
|
continue;
|
|
}
|
|
|
|
auto FirstParagraphChildren = FirstParagraph->getChildren();
|
|
if (FirstParagraphChildren.empty()) {
|
|
NormalItems.push_back(Child);
|
|
continue;
|
|
}
|
|
|
|
auto ParagraphText
|
|
= dyn_cast<swift::markup::Text>(FirstParagraphChildren.front());
|
|
if (!ParagraphText) {
|
|
NormalItems.push_back(Child);
|
|
continue;
|
|
}
|
|
|
|
StringRef ParameterPrefix("parameter ");
|
|
auto ParagraphContent = ParagraphText->getLiteralContent();
|
|
auto PotentialMatch = ParagraphContent.substr(0, ParameterPrefix.size());
|
|
|
|
if (!PotentialMatch.startswith_lower(ParameterPrefix)) {
|
|
NormalItems.push_back(Child);
|
|
continue;
|
|
}
|
|
|
|
ParagraphContent = ParagraphContent.substr(ParameterPrefix.size());
|
|
ParagraphText->setLiteralContent(ParagraphContent.ltrim());
|
|
|
|
auto ParamField = extractParamOutlineItem(MC, I);
|
|
if (ParamField.hasValue())
|
|
ParamFields.push_back(ParamField.getValue());
|
|
else
|
|
NormalItems.push_back(Child);
|
|
}
|
|
|
|
if (NormalItems.size() != Children.size())
|
|
L->setChildren(NormalItems);
|
|
|
|
return NormalItems.size() == 0;
|
|
}
|
|
|
|
bool extractSimpleField(
|
|
swift::markup::MarkupContext &MC, swift::markup::List *L,
|
|
swift::markup::CommentParts &Parts,
|
|
SmallVectorImpl<const swift::markup::MarkupASTNode *> &BodyNodes) {
|
|
auto Children = L->getChildren();
|
|
SmallVector<swift::markup::MarkupASTNode *, 8> NormalItems;
|
|
for (auto Child : Children) {
|
|
auto I = dyn_cast<swift::markup::Item>(Child);
|
|
if (!I) {
|
|
NormalItems.push_back(Child);
|
|
continue;
|
|
}
|
|
|
|
auto ItemChildren = I->getChildren();
|
|
if (ItemChildren.empty()) {
|
|
NormalItems.push_back(Child);
|
|
continue;
|
|
}
|
|
|
|
auto FirstParagraph
|
|
= dyn_cast<swift::markup::Paragraph>(ItemChildren.front());
|
|
if (!FirstParagraph) {
|
|
NormalItems.push_back(Child);
|
|
continue;
|
|
}
|
|
|
|
auto ParagraphChildren = FirstParagraph->getChildren();
|
|
if (ParagraphChildren.empty()) {
|
|
NormalItems.push_back(Child);
|
|
continue;
|
|
}
|
|
|
|
auto ParagraphText
|
|
= dyn_cast<swift::markup::Text>(ParagraphChildren.front());
|
|
if (!ParagraphText) {
|
|
NormalItems.push_back(Child);
|
|
continue;
|
|
}
|
|
|
|
StringRef Tag;
|
|
StringRef Remainder;
|
|
std::tie(Tag, Remainder) = ParagraphText->getLiteralContent().split(':');
|
|
Tag = Tag.ltrim().rtrim();
|
|
Remainder = Remainder.ltrim();
|
|
|
|
if (!swift::markup::isAFieldTag(Tag)) {
|
|
NormalItems.push_back(Child);
|
|
continue;
|
|
}
|
|
|
|
ParagraphText->setLiteralContent(Remainder);
|
|
auto Field = swift::markup::createSimpleField(MC, Tag, ItemChildren);
|
|
|
|
if (auto RF = dyn_cast<swift::markup::ReturnsField>(Field))
|
|
Parts.ReturnsField = RF;
|
|
else if (auto TF = dyn_cast<swift::markup::ThrowsField>(Field))
|
|
Parts.ThrowsField = TF;
|
|
else
|
|
BodyNodes.push_back(Field);
|
|
}
|
|
|
|
if (NormalItems.size() != Children.size())
|
|
L->setChildren(NormalItems);
|
|
|
|
return NormalItems.size() == 0;
|
|
}
|
|
|
|
static swift::markup::CommentParts
|
|
extractCommentParts(swift::markup::MarkupContext &MC,
|
|
swift::markup::MarkupASTNode *Node) {
|
|
|
|
swift::markup::CommentParts Parts;
|
|
auto Children = Node->getChildren();
|
|
if (Children.empty())
|
|
return Parts;
|
|
|
|
auto FirstParagraph
|
|
= dyn_cast<swift::markup::Paragraph>(Node->getChildren().front());
|
|
if (FirstParagraph)
|
|
Parts.Brief = FirstParagraph;
|
|
|
|
SmallVector<const swift::markup::MarkupASTNode *, 4> BodyNodes;
|
|
SmallVector<swift::markup::ParamField *, 8> ParamFields;
|
|
|
|
// Look for special top-level lists
|
|
size_t StartOffset = FirstParagraph == nullptr ? 0 : 1;
|
|
for (auto C = Children.begin() + StartOffset; C != Children.end(); ++C) {
|
|
if (auto L = dyn_cast<swift::markup::List>(*C)) {
|
|
// Could be one of the following:
|
|
// 1. A parameter outline:
|
|
// - Parameters:
|
|
// - x: ...
|
|
// - y: ...
|
|
// 2. An exploded parameter list:
|
|
// - parameter x: ...
|
|
// - parameter y: ...
|
|
// 3. Some other simple field, including "returns" (see SimpleFields.def)
|
|
auto ListNowEmpty = extractParameterOutline(MC, L, ParamFields);
|
|
ListNowEmpty |= extractSeparatedParams(MC, L, ParamFields);
|
|
ListNowEmpty |= extractSimpleField(MC, L, Parts, BodyNodes);
|
|
if (ListNowEmpty)
|
|
continue; // This drops the empty list from the doc comment body.
|
|
}
|
|
BodyNodes.push_back(*C);
|
|
}
|
|
|
|
// Copy BodyNodes and ParamFields into the MarkupContext.
|
|
Parts.BodyNodes = MC.allocateCopy(llvm::makeArrayRef(BodyNodes));
|
|
Parts.ParamFields = MC.allocateCopy(llvm::makeArrayRef(ParamFields));
|
|
|
|
for (auto Param : Parts.ParamFields) {
|
|
auto ParamParts = extractCommentParts(MC, Param);
|
|
Param->setParts(ParamParts);
|
|
}
|
|
|
|
return Parts;
|
|
}
|
|
|
|
Optional<DocComment *>
|
|
swift::getSingleDocComment(swift::markup::MarkupContext &MC, const Decl *D) {
|
|
PrettyStackTraceDecl StackTrace("parsing comment for", D);
|
|
|
|
auto RC = D->getRawComment();
|
|
if (RC.isEmpty())
|
|
return None;
|
|
|
|
swift::markup::LineList LL = MC.getLineList(RC);
|
|
auto *Doc = swift::markup::parseDocument(MC, LL);
|
|
auto Parts = extractCommentParts(MC, Doc);
|
|
return new (MC) DocComment(D, Doc, Parts);
|
|
}
|
|
|
|
static Optional<DocComment *>
|
|
getAnyBaseClassDocComment(swift::markup::MarkupContext &MC,
|
|
const ClassDecl *CD,
|
|
const Decl *D) {
|
|
RawComment RC;
|
|
|
|
if (const auto *VD = dyn_cast<ValueDecl>(D)) {
|
|
const auto *BaseDecl = VD->getOverriddenDecl();
|
|
while (BaseDecl) {
|
|
RC = BaseDecl->getRawComment();
|
|
if (!RC.isEmpty()) {
|
|
swift::markup::LineList LL = MC.getLineList(RC);
|
|
auto *Doc = swift::markup::parseDocument(MC, LL);
|
|
auto Parts = extractCommentParts(MC, Doc);
|
|
|
|
SmallString<48> RawCascadeText;
|
|
llvm::raw_svector_ostream OS(RawCascadeText);
|
|
OS << "This documentation comment was inherited from ";
|
|
|
|
|
|
auto *Text = swift::markup::Text::create(MC, MC.allocateCopy(OS.str()));
|
|
|
|
auto BaseClass =
|
|
BaseDecl->getDeclContext()->getAsClassOrClassExtensionContext();
|
|
|
|
auto *BaseClassMonospace =
|
|
swift::markup::Code::create(MC,
|
|
MC.allocateCopy(BaseClass->getNameStr()));
|
|
|
|
auto *Period = swift::markup::Text::create(MC, ".");
|
|
|
|
auto *Para = swift::markup::Paragraph::create(MC, {
|
|
Text, BaseClassMonospace, Period
|
|
});
|
|
auto CascadeNote = swift::markup::NoteField::create(MC, {Para});
|
|
|
|
SmallVector<const swift::markup::MarkupASTNode *, 8> BodyNodes {
|
|
Parts.BodyNodes.begin(),
|
|
Parts.BodyNodes.end()
|
|
};
|
|
BodyNodes.push_back(CascadeNote);
|
|
Parts.BodyNodes = MC.allocateCopy(llvm::makeArrayRef(BodyNodes));
|
|
|
|
return new (MC) DocComment(D, Doc, Parts);
|
|
}
|
|
|
|
BaseDecl = BaseDecl->getOverriddenDecl();
|
|
}
|
|
}
|
|
|
|
return None;
|
|
}
|
|
|
|
static Optional<DocComment *>
|
|
getProtocolRequirementDocComment(swift::markup::MarkupContext &MC,
|
|
const ProtocolDecl *ProtoExt,
|
|
const Decl *D) {
|
|
|
|
auto getSingleRequirementWithNonemptyDoc = [](const ProtocolDecl *P,
|
|
const ValueDecl *VD)
|
|
-> const ValueDecl * {
|
|
SmallVector<ValueDecl *, 2> Members;
|
|
P->lookupQualified(P->getType(), VD->getFullName(),
|
|
NLOptions::NL_ProtocolMembers,
|
|
/*resolver=*/nullptr, Members);
|
|
SmallVector<const ValueDecl *, 1> ProtocolRequirements;
|
|
for (auto Member : Members)
|
|
if (!Member->isDefinition())
|
|
ProtocolRequirements.push_back(Member);
|
|
|
|
if (ProtocolRequirements.size() == 1) {
|
|
auto Requirement = ProtocolRequirements.front();
|
|
if (!Requirement->getRawComment().isEmpty())
|
|
return Requirement;
|
|
}
|
|
|
|
return nullptr;
|
|
};
|
|
|
|
if (const auto *VD = dyn_cast<ValueDecl>(D)) {
|
|
SmallVector<const ValueDecl *, 4> RequirementsWithDocs;
|
|
if (auto Requirement = getSingleRequirementWithNonemptyDoc(ProtoExt, VD))
|
|
RequirementsWithDocs.push_back(Requirement);
|
|
|
|
for (auto Proto : ProtoExt->getInheritedProtocols(/*resolver=*/nullptr))
|
|
if (auto Requirement = getSingleRequirementWithNonemptyDoc(Proto, VD))
|
|
RequirementsWithDocs.push_back(Requirement);
|
|
|
|
if (RequirementsWithDocs.size() == 1)
|
|
return getSingleDocComment(MC, RequirementsWithDocs.front());
|
|
}
|
|
return None;
|
|
}
|
|
|
|
Optional<DocComment *>
|
|
swift::getCascadingDocComment(swift::markup::MarkupContext &MC, const Decl *D) {
|
|
auto Doc = getSingleDocComment(MC, D);
|
|
if (Doc.hasValue())
|
|
return Doc;
|
|
|
|
// If this refers to a class member, check to see if any
|
|
// base classes have a doc comment and cascade it to here.
|
|
if (const auto *CD = D->getDeclContext()->getAsClassOrClassExtensionContext())
|
|
if (auto BaseClassDoc = getAnyBaseClassDocComment(MC, CD, D))
|
|
return BaseClassDoc;
|
|
|
|
if (const auto *PE = D->getDeclContext()->getAsProtocolExtensionContext())
|
|
if (auto ReqDoc = getProtocolRequirementDocComment(MC, PE, D))
|
|
return ReqDoc;
|
|
|
|
return None;
|
|
}
|