blob: 403894bb1a17d5ee6de5bba0960f8ced3f88a6ba [file] [log] [blame]
//===--- RawComment.cpp - Extraction of raw 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 raw comments.
///
//===----------------------------------------------------------------------===//
#include "swift/AST/RawComment.h"
#include "swift/AST/Comment.h"
#include "swift/AST/ASTContext.h"
#include "swift/AST/Decl.h"
#include "swift/AST/Module.h"
#include "swift/AST/PrettyStackTrace.h"
#include "swift/AST/Types.h"
#include "swift/Basic/PrimitiveParsing.h"
#include "swift/Basic/SourceManager.h"
#include "swift/Markup/Markup.h"
#include "swift/Parse/Lexer.h"
#include "llvm/ADT/SmallString.h"
#include "llvm/ADT/StringRef.h"
#include "llvm/Support/raw_ostream.h"
using namespace swift;
static SingleRawComment::CommentKind getCommentKind(StringRef Comment) {
assert(Comment.size() >= 2);
assert(Comment[0] == '/');
if (Comment[1] == '/') {
if (Comment.size() < 3)
return SingleRawComment::CommentKind::OrdinaryLine;
if (Comment[2] == '/') {
return SingleRawComment::CommentKind::LineDoc;
}
return SingleRawComment::CommentKind::OrdinaryLine;
} else {
assert(Comment[1] == '*');
assert(Comment.size() >= 4);
if (Comment[2] == '*') {
return SingleRawComment::CommentKind::BlockDoc;
}
return SingleRawComment::CommentKind::OrdinaryBlock;
}
}
SingleRawComment::SingleRawComment(CharSourceRange Range,
const SourceManager &SourceMgr)
: Range(Range), RawText(SourceMgr.extractText(Range)),
Kind(static_cast<unsigned>(getCommentKind(RawText))) {
auto StartLineAndColumn = SourceMgr.getLineAndColumn(Range.getStart());
StartLine = StartLineAndColumn.first;
StartColumn = StartLineAndColumn.second;
EndLine = SourceMgr.getLineNumber(Range.getEnd());
}
SingleRawComment::SingleRawComment(StringRef RawText, unsigned StartColumn)
: RawText(RawText), Kind(static_cast<unsigned>(getCommentKind(RawText))),
StartColumn(StartColumn), StartLine(0), EndLine(0) {}
static void addCommentToList(SmallVectorImpl<SingleRawComment> &Comments,
const SingleRawComment &SRC) {
// TODO: consider producing warnings when we decide not to merge comments.
if (SRC.isOrdinary()) {
// Skip gyb comments that are line number markers.
if (SRC.RawText.startswith("// ###"))
return;
Comments.clear();
return;
}
// If this is the first documentation comment, save it (because there isn't
// anything to merge it with).
if (Comments.empty()) {
Comments.push_back(SRC);
return;
}
auto &Last = Comments.back();
// Merge comments if they are on same or consecutive lines.
if (Last.EndLine + 1 < SRC.StartLine) {
Comments.clear();
return;
}
Comments.push_back(SRC);
}
static RawComment toRawComment(ASTContext &Context, CharSourceRange Range) {
if (Range.isInvalid())
return RawComment();
auto &SourceMgr = Context.SourceMgr;
unsigned BufferID = SourceMgr.findBufferContainingLoc(Range.getStart());
unsigned Offset = SourceMgr.getLocOffsetInBuffer(Range.getStart(), BufferID);
unsigned EndOffset = SourceMgr.getLocOffsetInBuffer(Range.getEnd(), BufferID);
LangOptions FakeLangOpts;
Lexer L(FakeLangOpts, SourceMgr, BufferID, nullptr, LexerMode::Swift,
HashbangMode::Disallowed,
CommentRetentionMode::ReturnAsTokens,
TriviaRetentionMode::WithoutTrivia,
Offset, EndOffset);
SmallVector<SingleRawComment, 16> Comments;
Token Tok;
while (true) {
L.lex(Tok);
if (Tok.is(tok::eof))
break;
assert(Tok.is(tok::comment));
addCommentToList(Comments, SingleRawComment(Tok.getRange(), SourceMgr));
}
RawComment Result;
Result.Comments = Context.AllocateCopy(Comments);
return Result;
}
RawComment Decl::getRawComment() const {
if (!this->canHaveComment())
return RawComment();
// Check the cache in ASTContext.
auto &Context = getASTContext();
if (Optional<RawComment> RC = Context.getRawComment(this))
return RC.getValue();
// Check the declaration itself.
if (auto *Attr = getAttrs().getAttribute<RawDocCommentAttr>()) {
RawComment Result = toRawComment(Context, Attr->getCommentRange());
Context.setRawComment(this, Result);
return Result;
}
// Ask the parent module.
if (auto *Unit =
dyn_cast<FileUnit>(this->getDeclContext()->getModuleScopeContext())) {
if (Optional<CommentInfo> C = Unit->getCommentForDecl(this)) {
swift::markup::MarkupContext MC;
Context.setBriefComment(this, C->Brief);
Context.setRawComment(this, C->Raw);
return C->Raw;
}
}
// Give up.
return RawComment();
}
static const Decl* getGroupDecl(const Decl *D) {
auto GroupD = D;
// Extensions always exist in the same group with the nominal.
if (auto ED = dyn_cast_or_null<ExtensionDecl>(D->getDeclContext()->
getInnermostTypeContext())) {
if (auto ExtNominal = ED->getExtendedNominal())
GroupD = ExtNominal;
}
return GroupD;
}
Optional<StringRef> Decl::getGroupName() const {
if (hasClangNode())
return None;
if (auto GroupD = getGroupDecl(this)) {
// We can only get group information from deserialized module files.
if (auto *Unit =
dyn_cast<FileUnit>(GroupD->getDeclContext()->getModuleScopeContext())) {
return Unit->getGroupNameForDecl(GroupD);
}
}
return None;
}
Optional<StringRef> Decl::getSourceFileName() const {
if (hasClangNode())
return None;
if (auto GroupD = getGroupDecl(this)) {
// We can only get group information from deserialized module files.
if (auto *Unit =
dyn_cast<FileUnit>(GroupD->getDeclContext()->getModuleScopeContext())) {
return Unit->getSourceFileNameForDecl(GroupD);
}
}
return None;
}
Optional<unsigned> Decl::getSourceOrder() const {
if (hasClangNode())
return None;
// We can only get source orders from deserialized module files.
if (auto *Unit =
dyn_cast<FileUnit>(this->getDeclContext()->getModuleScopeContext())) {
return Unit->getSourceOrderForDecl(this);
}
return None;
}
static StringRef extractBriefComment(ASTContext &Context, RawComment RC,
const Decl *D) {
PrettyStackTraceDecl StackTrace("extracting brief comment for", D);
if (!D->canHaveComment())
return StringRef();
swift::markup::MarkupContext MC;
auto DC = getCascadingDocComment(MC, D);
if (!DC.hasValue())
return StringRef();
auto Brief = DC.getValue()->getBrief();
if (!Brief.hasValue())
return StringRef();
SmallString<256> BriefStr("");
llvm::raw_svector_ostream OS(BriefStr);
swift::markup::printInlinesUnder(Brief.getValue(), OS);
if (OS.str().empty())
return StringRef();
return Context.AllocateCopy(OS.str());
}
StringRef Decl::getBriefComment() const {
if (!this->canHaveComment())
return StringRef();
auto &Context = getASTContext();
if (Optional<StringRef> Comment = Context.getBriefComment(this))
return Comment.getValue();
StringRef Result;
auto RC = getRawComment();
if (!RC.isEmpty())
Result = extractBriefComment(Context, RC, this);
Context.setBriefComment(this, Result);
return Result;
}
CharSourceRange RawComment::getCharSourceRange() {
if (this->isEmpty()) {
return CharSourceRange();
}
auto Start = this->Comments.front().Range.getStart();
if (Start.isInvalid()) {
return CharSourceRange();
}
auto End = this->Comments.back().Range.getEnd();
auto Length = static_cast<const char *>(End.getOpaquePointerValue()) -
static_cast<const char *>(Start.getOpaquePointerValue());
return CharSourceRange(Start, Length);
}