mirror of
https://github.com/apple/swift.git
synced 2025-12-14 20:36:38 +01:00
If we have both loaded a swiftdoc, and the decl we have should have had its doc comment serialized into it, we can check it without needing to fall back to the swiftsourceinfo. This requires a couple of refactorings: - Factoring out the `shouldIncludeDecl` logic into `getDocCommentSerializationTargetFor` for determining whether a doc comment should end up in the swiftdoc or not. - Factoring out `CommentProviderFinder` for searching for the doc providing comment decl for brief comments, in order to allow us to avoid querying the raw comment when searching for it. This has the added bonus of meaning we no longer need to fall back to parsing the raw comment for the brief comment if the comment is provided by another decl in the swiftdoc. This diff is best viewed without whitespace.
592 lines
17 KiB
C++
592 lines
17 KiB
C++
//===--- DocComment.cpp - Extraction of doc comments ----------------------===//
|
|
//
|
|
// 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
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
///
|
|
/// \file
|
|
/// This file implements extraction of documentation comments from a Swift
|
|
/// Markup AST tree.
|
|
///
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
#include "swift/AST/ASTContext.h"
|
|
#include "swift/AST/Comment.h"
|
|
#include "swift/AST/Decl.h"
|
|
#include "swift/AST/FileUnit.h"
|
|
#include "swift/AST/TypeCheckRequests.h"
|
|
#include "swift/AST/Types.h"
|
|
#include "swift/AST/PrettyStackTrace.h"
|
|
#include "swift/AST/RawComment.h"
|
|
#include "swift/Markup/Markup.h"
|
|
#include <queue>
|
|
|
|
using namespace swift;
|
|
|
|
void *DocComment::operator new(size_t Bytes, swift::markup::MarkupContext &MC,
|
|
unsigned Alignment) {
|
|
return MC.allocate(Bytes, Alignment);
|
|
}
|
|
|
|
namespace {
|
|
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_insensitive("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.has_value()) {
|
|
ParamFields.push_back(Param.value());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (NormalItems.size() != Children.size()) {
|
|
L->setChildren(NormalItems);
|
|
}
|
|
|
|
return NormalItems.empty();
|
|
}
|
|
|
|
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_insensitive(ParameterPrefix)) {
|
|
NormalItems.push_back(Child);
|
|
continue;
|
|
}
|
|
|
|
ParagraphContent = ParagraphContent.substr(ParameterPrefix.size());
|
|
ParagraphText->setLiteralContent(ParagraphContent.ltrim());
|
|
|
|
auto ParamField = extractParamOutlineItem(MC, I);
|
|
if (ParamField.has_value())
|
|
ParamFields.push_back(ParamField.value());
|
|
else
|
|
NormalItems.push_back(Child);
|
|
}
|
|
|
|
if (NormalItems.size() != Children.size())
|
|
L->setChildren(NormalItems);
|
|
|
|
return NormalItems.empty();
|
|
}
|
|
|
|
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 if (auto TF = dyn_cast<swift::markup::TagField>(Field)) {
|
|
llvm::SmallString<64> Scratch;
|
|
llvm::raw_svector_ostream OS(Scratch);
|
|
printInlinesUnder(TF, OS);
|
|
Parts.Tags.insert(MC.allocateCopy(OS.str()));
|
|
} else if (auto LKF = dyn_cast<markup::LocalizationKeyField>(Field)) {
|
|
Parts.LocalizationKeyField = LKF;
|
|
} else {
|
|
BodyNodes.push_back(Field);
|
|
}
|
|
}
|
|
|
|
if (NormalItems.size() != Children.size())
|
|
L->setChildren(NormalItems);
|
|
|
|
return NormalItems.empty();
|
|
}
|
|
} // namespace
|
|
|
|
void swift::printBriefComment(RawComment RC, llvm::raw_ostream &OS) {
|
|
markup::MarkupContext MC;
|
|
markup::LineList LL = MC.getLineList(RC);
|
|
auto *markupDoc = markup::parseDocument(MC, LL);
|
|
|
|
auto children = markupDoc->getChildren();
|
|
if (children.empty())
|
|
return;
|
|
auto FirstParagraph = dyn_cast<swift::markup::Paragraph>(children.front());
|
|
if (!FirstParagraph)
|
|
return;
|
|
swift::markup::printInlinesUnder(FirstParagraph, OS);
|
|
}
|
|
|
|
swift::markup::CommentParts
|
|
swift::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;
|
|
}
|
|
|
|
DocComment *DocComment::create(const Decl *D, markup::MarkupContext &MC,
|
|
RawComment RC) {
|
|
assert(!RC.isEmpty());
|
|
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);
|
|
}
|
|
|
|
void DocComment::addInheritanceNote(swift::markup::MarkupContext &MC,
|
|
TypeDecl *base) {
|
|
auto text = MC.allocateCopy("This documentation comment was inherited from ");
|
|
auto name = MC.allocateCopy(base->getNameStr());
|
|
auto period = MC.allocateCopy(".");
|
|
auto paragraph = markup::Paragraph::create(MC, {
|
|
markup::Text::create(MC, text),
|
|
markup::Code::create(MC, name),
|
|
markup::Text::create(MC, period)});
|
|
|
|
auto note = markup::NoteField::create(MC, {paragraph});
|
|
|
|
SmallVector<const markup::MarkupASTNode *, 8> BodyNodes{
|
|
Parts.BodyNodes.begin(), Parts.BodyNodes.end()};
|
|
BodyNodes.push_back(note);
|
|
Parts.BodyNodes = MC.allocateCopy(llvm::makeArrayRef(BodyNodes));
|
|
}
|
|
|
|
DocComment *swift::getSingleDocComment(swift::markup::MarkupContext &MC,
|
|
const Decl *D) {
|
|
PrettyStackTraceDecl StackTrace("parsing comment for", D);
|
|
|
|
auto RC = D->getRawComment();
|
|
if (RC.isEmpty())
|
|
return nullptr;
|
|
return DocComment::create(D, MC, RC);
|
|
}
|
|
|
|
namespace {
|
|
/// Helper class for finding the comment providing decl for either a brief or
|
|
/// raw comment.
|
|
template <typename Result>
|
|
class CommentProviderFinder final {
|
|
using ResultWithDecl = std::pair<Result, const Decl *>;
|
|
using VisitFnTy = Optional<Result>(*)(const Decl *);
|
|
|
|
VisitFnTy VisitFn;
|
|
|
|
public:
|
|
CommentProviderFinder(VisitFnTy visitFn) : VisitFn(visitFn) {}
|
|
|
|
private:
|
|
Optional<ResultWithDecl> visit(const Decl *D) {
|
|
// Adapt the provided visitor function to also return the decl.
|
|
if (auto result = VisitFn(D))
|
|
return {{*result, D}};
|
|
return None;
|
|
}
|
|
|
|
Optional<ResultWithDecl> findOverriddenDecl(const ValueDecl *VD) {
|
|
// Only applies to class member.
|
|
if (!VD->getDeclContext()->getSelfClassDecl())
|
|
return None;
|
|
|
|
while (auto *baseDecl = VD->getOverriddenDecl()) {
|
|
if (auto result = visit(baseDecl))
|
|
return result;
|
|
|
|
VD = baseDecl;
|
|
}
|
|
return None;
|
|
}
|
|
|
|
Optional<ResultWithDecl> findDefaultProvidedDecl(const ValueDecl *VD) {
|
|
// Only applies to protocol extension member.
|
|
auto *protocol = VD->getDeclContext()->getExtendedProtocolDecl();
|
|
if (!protocol)
|
|
return None;
|
|
|
|
SmallVector<ValueDecl *, 2> members;
|
|
protocol->lookupQualified(const_cast<ProtocolDecl *>(protocol),
|
|
DeclNameRef(VD->getName()),
|
|
NLOptions::NL_ProtocolMembers, members);
|
|
|
|
Optional<ResultWithDecl> result;
|
|
for (auto *member : members) {
|
|
if (!isa<ProtocolDecl>(member->getDeclContext()) ||
|
|
!member->isProtocolRequirement())
|
|
continue;
|
|
|
|
auto newResult = visit(member);
|
|
if (!newResult)
|
|
continue;
|
|
|
|
if (result) {
|
|
// Found two or more decls with doc-comment.
|
|
return None;
|
|
}
|
|
result = newResult;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
Optional<ResultWithDecl> findRequirementDecl(const ValueDecl *VD) {
|
|
std::queue<const ValueDecl *> requirements;
|
|
while (true) {
|
|
for (auto *req : VD->getSatisfiedProtocolRequirements()) {
|
|
if (auto result = visit(req))
|
|
return result;
|
|
|
|
requirements.push(req);
|
|
}
|
|
if (requirements.empty())
|
|
return None;
|
|
|
|
VD = requirements.front();
|
|
requirements.pop();
|
|
}
|
|
}
|
|
|
|
public:
|
|
Optional<ResultWithDecl> findCommentProvider(const Decl *D) {
|
|
if (auto result = visit(D))
|
|
return result;
|
|
|
|
auto *VD = dyn_cast<ValueDecl>(D);
|
|
if (!VD)
|
|
return None;
|
|
|
|
if (auto result = findOverriddenDecl(VD))
|
|
return result;
|
|
|
|
if (auto result = findDefaultProvidedDecl(VD))
|
|
return result;
|
|
|
|
if (auto result = findRequirementDecl(VD))
|
|
return result;
|
|
|
|
return None;
|
|
}
|
|
};
|
|
} // end anonymous namespace
|
|
|
|
const Decl *swift::getDocCommentProvidingDecl(const Decl *D) {
|
|
// Search for the first decl we see with a non-empty raw comment.
|
|
auto finder = CommentProviderFinder<RawComment>(
|
|
[](const Decl *D) -> Optional<RawComment> {
|
|
auto comment = D->getRawComment();
|
|
if (comment.isEmpty())
|
|
return None;
|
|
return comment;
|
|
});
|
|
|
|
auto result = finder.findCommentProvider(D);
|
|
return result ? result->second : nullptr;
|
|
}
|
|
|
|
DocComment *swift::getCascadingDocComment(swift::markup::MarkupContext &MC,
|
|
const Decl *D) {
|
|
auto *docD = getDocCommentProvidingDecl(D);
|
|
if (!docD)
|
|
return nullptr;
|
|
|
|
auto *doc = getSingleDocComment(MC, docD);
|
|
assert(doc && "getDocCommentProvidingDecl() returned decl with no comment");
|
|
|
|
// If the doc-comment is inherited from other decl, add a note about it.
|
|
if (docD != D) {
|
|
doc->setDecl(D);
|
|
if (auto baseD = docD->getDeclContext()->getSelfNominalTypeDecl()) {
|
|
doc->addInheritanceNote(MC, baseD);
|
|
|
|
// If the doc is inherited from protocol requirement, associate the
|
|
// requirement with the doc-comment.
|
|
// FIXME: This is to keep the old behavior.
|
|
if (isa<ProtocolDecl>(baseD))
|
|
doc->setDecl(docD);
|
|
}
|
|
}
|
|
|
|
return doc;
|
|
}
|
|
|
|
/// Retrieve the brief comment for a given decl \p D, without attempting to
|
|
/// walk any requirements or overrides.
|
|
static Optional<StringRef> getDirectBriefComment(const Decl *D) {
|
|
if (!D->canHaveComment())
|
|
return None;
|
|
|
|
auto *ModuleDC = D->getDeclContext()->getModuleScopeContext();
|
|
auto &Ctx = ModuleDC->getASTContext();
|
|
|
|
// If we expect the comment to be in the swiftdoc, check for it if we loaded a
|
|
// swiftdoc. If missing from the swiftdoc, we know it will not be in the
|
|
// swiftsourceinfo either, so we can bail early.
|
|
if (auto *Unit = dyn_cast<FileUnit>(ModuleDC)) {
|
|
if (Unit->hasLoadedSwiftDoc()) {
|
|
auto target = getDocCommentSerializationTargetFor(D);
|
|
if (target == DocCommentSerializationTarget::SwiftDocAndSourceInfo) {
|
|
auto C = Unit->getCommentForDecl(D);
|
|
if (!C)
|
|
return None;
|
|
|
|
return C->Brief;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Otherwise, parse the brief from the raw comment itself. This will look into
|
|
// the swiftsourceinfo if needed.
|
|
auto RC = D->getRawComment();
|
|
if (RC.isEmpty())
|
|
return None;
|
|
|
|
SmallString<256> BriefStr;
|
|
llvm::raw_svector_ostream OS(BriefStr);
|
|
printBriefComment(RC, OS);
|
|
return Ctx.AllocateCopy(BriefStr.str());
|
|
}
|
|
|
|
StringRef BriefCommentRequest::evaluate(Evaluator &evaluator,
|
|
const Decl *D) const {
|
|
// Perform a walk over the potential providers of the brief comment,
|
|
// retrieving the first one we come across.
|
|
CommentProviderFinder finder(getDirectBriefComment);
|
|
auto result = finder.findCommentProvider(D);
|
|
return result ? result->first : StringRef();
|
|
}
|
|
|
|
StringRef Decl::getBriefComment() const {
|
|
if (!this->canHaveComment())
|
|
return StringRef();
|
|
|
|
auto &eval = getASTContext().evaluator;
|
|
return evaluateOrDefault(eval, BriefCommentRequest{this}, StringRef());
|
|
}
|