//===--- RawComment.cpp - Extraction of raw comments ----------------------===// // // This source file is part of the Swift.org open source project // // Copyright (c) 2014 - 2015 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 raw comments. /// //===----------------------------------------------------------------------===// #include "swift/AST/RawComment.h" #include "swift/AST/ASTContext.h" #include "swift/AST/Decl.h" #include "swift/AST/Module.h" #include "swift/AST/PrettyStackTrace.h" #include "swift/Basic/SourceManager.h" #include "swift/ReST/Parser.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(getCommentKind(RawText)), StartLine(SourceMgr.getLineNumber(Range.getStart())), EndLine(SourceMgr.getLineNumber(Range.getEnd())) {} SingleRawComment::SingleRawComment(StringRef RawText) : RawText(RawText), Kind(getCommentKind(RawText)), StartLine(0), EndLine(0) {} static bool canHaveComment(const Decl *D) { return !D->hasClangNode() && isa(D) && (!isa(D) || isa(D)); } static void addCommentToList(SmallVectorImpl &Comments, const SingleRawComment &SRC) { // TODO: consider producing warnings when we decide not to merge comments. if (SRC.isOrdinary()) { 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, /*InSILMode=*/false, CommentRetentionMode::ReturnAsTokens, Offset, EndOffset); SmallVector 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 (!canHaveComment(this)) return RawComment(); // Check the cache in ASTContext. auto &Context = getASTContext(); if (Optional RC = Context.getRawComment(this)) return RC.getValue(); // Check the declaration itself. if (getAttrs().has(AK_raw_doc_comment)) { RawComment Result = toRawComment(Context, getAttrs().CommentRange); Context.setRawComment(this, Result); return Result; } // Ask the parent module. if (auto *Unit = dyn_cast(this->getDeclContext()->getModuleScopeContext())) { if (Optional C = Unit->getCommentForDecl(this)) { Context.setBriefComment(this, C->Brief); Context.setRawComment(this, C->Raw); return C->Raw; } } // Give up. return RawComment(); } static unsigned measureNewline(const char *BufferPtr, const char *BufferEnd) { if (BufferPtr == BufferEnd) return 0; if (*BufferPtr == '\n') return 1; assert(*BufferPtr == '\r'); unsigned Bytes = 1; if (BufferPtr != BufferEnd && *BufferPtr == '\n') Bytes++; return Bytes; } static unsigned measureNewline(StringRef S) { return measureNewline(S.data(), S.data() + S.size()); } static bool startsWithNewline(StringRef S) { return S.startswith("\n") || S.startswith("\r\n"); } static unsigned measureASCIIArt(StringRef S) { if (S.startswith(" * ")) return 3; if (S.startswith(" *\n") || S.startswith(" *\n\r")) return 2; return 0; } static llvm::rest::LineList toLineList(llvm::rest::SourceManager &RSM, RawComment RC) { llvm::rest::LineList Result; for (const auto &C : RC.Comments) { if (C.isLine()) { // Skip comment marker. unsigned CommentMarkerBytes = 2 + (C.isOrdinary() ? 0 : 1); StringRef Cleaned = C.RawText.drop_front(CommentMarkerBytes); // Drop trailing newline. Cleaned = Cleaned.rtrim("\n\r"); SourceLoc CleanedStartLoc = C.Range.getStart().getAdvancedLocOrInvalid(CommentMarkerBytes); Result.addLine(Cleaned, RSM.registerLine(Cleaned, CleanedStartLoc)); } else { // Skip comment markers at the beginning and at the end. unsigned CommentMarkerBytes = 2 + (C.isOrdinary() ? 0 : 1); StringRef Cleaned = C.RawText.drop_front(CommentMarkerBytes).drop_back(2); SourceLoc CleanedStartLoc = C.Range.getStart().getAdvancedLocOrInvalid(CommentMarkerBytes); // Determine if we have leading decorations in this block comment. bool HasASCIIArt = false; if (startsWithNewline(Cleaned)) { Result.addLine(Cleaned.substr(0, 0), RSM.registerLine(Cleaned.substr(0, 0), CleanedStartLoc)); unsigned NewlineBytes = measureNewline(Cleaned); Cleaned = Cleaned.drop_front(NewlineBytes); CleanedStartLoc = CleanedStartLoc.getAdvancedLocOrInvalid(NewlineBytes); HasASCIIArt = measureASCIIArt(Cleaned) != 0; } while (!Cleaned.empty()) { size_t Pos = Cleaned.find_first_of("\n\r"); if (Pos == StringRef::npos) Pos = Cleaned.size(); // Skip over ASCII art, if present. if (HasASCIIArt) if (unsigned ASCIIArtBytes = measureASCIIArt(Cleaned)) { Cleaned = Cleaned.drop_front(ASCIIArtBytes); CleanedStartLoc = CleanedStartLoc.getAdvancedLocOrInvalid(ASCIIArtBytes); Pos -= ASCIIArtBytes; } StringRef Line = Cleaned.substr(0, Pos); Result.addLine(Line, RSM.registerLine(Line, CleanedStartLoc)); Cleaned = Cleaned.drop_front(Pos); unsigned NewlineBytes = measureNewline(Cleaned); Cleaned = Cleaned.drop_front(NewlineBytes); Pos += NewlineBytes; CleanedStartLoc = CleanedStartLoc.getAdvancedLocOrInvalid(Pos); } } } return std::move(Result); } static StringRef extractBriefComment(ASTContext &Context, RawComment RC, const Decl *D) { PrettyStackTraceDecl StackTrace("extracting brief comment for", D); llvm::rest::SourceManager RSM; llvm::rest::LineList LL = toLineList(RSM, RC); llvm::SmallString<256> Result; llvm::rest::extractBrief(LL, Result); if (Result.empty()) return StringRef(); ArrayRef Copy = Context.AllocateCopy(Result); return StringRef(Copy.data(), Copy.size()); } StringRef Decl::getBriefComment() const { if (!canHaveComment(this)) return StringRef(); auto &Context = getASTContext(); if (Optional 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; }