| //===--- 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/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_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.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_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.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, bool AllowSerialized) { |
| PrettyStackTraceDecl StackTrace("parsing comment for", D); |
| |
| auto RC = D->getRawComment(AllowSerialized); |
| if (RC.isEmpty()) |
| return nullptr; |
| return DocComment::create(D, MC, RC); |
| } |
| |
| namespace { |
| const ValueDecl *findOverriddenDeclWithDocComment(const ValueDecl *VD, |
| bool AllowSerialized = true) { |
| // Only applies to class member. |
| if (!VD->getDeclContext()->getSelfClassDecl()) |
| return nullptr; |
| |
| while (auto *baseDecl = VD->getOverriddenDecl()) { |
| if (!baseDecl->getRawComment(AllowSerialized).isEmpty()) |
| return baseDecl; |
| VD = baseDecl; |
| } |
| |
| return nullptr; |
| } |
| |
| const ValueDecl *findDefaultProvidedDeclWithDocComment(const ValueDecl *VD, |
| bool AllowSerialized = false) { |
| auto protocol = VD->getDeclContext()->getExtendedProtocolDecl(); |
| // Only applies to protocol extension member. |
| if (!protocol) |
| return nullptr; |
| |
| ValueDecl *requirement = nullptr; |
| |
| SmallVector<ValueDecl *, 2> members; |
| protocol->lookupQualified(const_cast<ProtocolDecl *>(protocol), |
| DeclNameRef(VD->getName()), |
| NLOptions::NL_ProtocolMembers, |
| members); |
| |
| for (auto *member : members) { |
| if (!isa<ProtocolDecl>(member->getDeclContext()) || |
| !member->isProtocolRequirement() || |
| member->getRawComment(AllowSerialized).isEmpty()) |
| continue; |
| if (requirement) |
| // Found two or more decls with doc-comment. |
| return nullptr; |
| |
| requirement = member; |
| } |
| return requirement; |
| } |
| |
| const ValueDecl *findRequirementDeclWithDocComment(const ValueDecl *VD, |
| bool AllowSerialized = false) { |
| std::queue<const ValueDecl *> requirements; |
| while (true) { |
| for (auto *req : VD->getSatisfiedProtocolRequirements()) { |
| if (!req->getRawComment().isEmpty()) |
| return req; |
| else |
| requirements.push(req); |
| } |
| if (requirements.empty()) |
| return nullptr; |
| VD = requirements.front(); |
| requirements.pop(); |
| } |
| } |
| } // namespace |
| |
| const Decl *swift::getDocCommentProvidingDecl(const Decl *D, |
| bool AllowSerialized) { |
| if (!D->canHaveComment()) |
| return nullptr; |
| |
| if (!D->getRawComment(AllowSerialized).isEmpty()) |
| return D; |
| |
| auto *VD = dyn_cast<ValueDecl>(D); |
| if (!VD) |
| return nullptr; |
| |
| if (auto *overriden = findOverriddenDeclWithDocComment(VD, AllowSerialized)) |
| return overriden; |
| |
| if (auto *requirement = findDefaultProvidedDeclWithDocComment(VD, AllowSerialized)) |
| return requirement; |
| |
| if (auto *requirement = findRequirementDeclWithDocComment(VD, AllowSerialized)) |
| return requirement; |
| |
| return nullptr; |
| } |
| |
| DocComment * |
| swift::getCascadingDocComment(swift::markup::MarkupContext &MC, const Decl *D, |
| bool AllowSerialized) { |
| auto *docD = getDocCommentProvidingDecl(D, AllowSerialized); |
| if (!docD) |
| return nullptr; |
| |
| auto *doc = getSingleDocComment(MC, docD, AllowSerialized); |
| 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; |
| } |
| |
| StringRef Decl::getBriefComment() const { |
| if (!this->canHaveComment()) |
| return StringRef(); |
| |
| // Check the cache in ASTContext. |
| auto &Context = getASTContext(); |
| if (Optional<StringRef> Comment = Context.getBriefComment(this)) |
| return Comment.getValue(); |
| |
| // Check if the serialized module may have the brief comment available. |
| if (auto *Unit = |
| dyn_cast<FileUnit>(this->getDeclContext()->getModuleScopeContext())) { |
| if (Optional<CommentInfo> C = Unit->getCommentForDecl(this)) { |
| Context.setBriefComment(this, C->Brief); |
| return C->Brief; |
| } |
| } |
| |
| // Otherwise, parse the brief from the raw comment itself. |
| auto RC = getRawComment(); |
| |
| StringRef Result; |
| if (RC.isEmpty()) |
| if (auto *docD = getDocCommentProvidingDecl(this)) |
| RC = docD->getRawComment(); |
| if (!RC.isEmpty()) { |
| SmallString<256> BriefStr; |
| llvm::raw_svector_ostream OS(BriefStr); |
| printBriefComment(RC, OS); |
| Result = Context.AllocateCopy(BriefStr.str()); |
| } |
| |
| // Cache it. |
| Context.setBriefComment(this, Result); |
| return Result; |
| } |