//===--- CommentSema.cpp - Doxygen comment semantic analysis --------------===// // // The LLVM Compiler Infrastructure // // This file is distributed under the University of Illinois Open Source // License. See LICENSE.TXT for details. // //===----------------------------------------------------------------------===// #include "clang/AST/CommentSema.h" #include "clang/AST/CommentDiagnostic.h" #include "clang/AST/Decl.h" #include "clang/AST/DeclObjC.h" #include "clang/Basic/SourceManager.h" #include "llvm/ADT/StringSwitch.h" namespace clang { namespace comments { Sema::Sema(llvm::BumpPtrAllocator &Allocator, const SourceManager &SourceMgr, DiagnosticsEngine &Diags) : Allocator(Allocator), SourceMgr(SourceMgr), Diags(Diags), ThisDecl(NULL), IsThisDeclInspected(false) { } void Sema::setDecl(const Decl *D) { ThisDecl = D; } ParagraphComment *Sema::actOnParagraphComment( ArrayRef Content) { return new (Allocator) ParagraphComment(Content); } BlockCommandComment *Sema::actOnBlockCommandStart(SourceLocation LocBegin, SourceLocation LocEnd, StringRef Name) { return new (Allocator) BlockCommandComment(LocBegin, LocEnd, Name); } BlockCommandComment *Sema::actOnBlockCommandArgs( BlockCommandComment *Command, ArrayRef Args) { Command->setArgs(Args); return Command; } BlockCommandComment *Sema::actOnBlockCommandFinish( BlockCommandComment *Command, ParagraphComment *Paragraph) { Command->setParagraph(Paragraph); checkBlockCommandEmptyParagraph(Command); return Command; } ParamCommandComment *Sema::actOnParamCommandStart(SourceLocation LocBegin, SourceLocation LocEnd, StringRef Name) { ParamCommandComment *Command = new (Allocator) ParamCommandComment(LocBegin, LocEnd, Name); if (!isFunctionDecl()) Diag(Command->getLocation(), diag::warn_doc_param_not_attached_to_a_function_decl) << Command->getCommandNameRange(); return Command; } ParamCommandComment *Sema::actOnParamCommandDirectionArg( ParamCommandComment *Command, SourceLocation ArgLocBegin, SourceLocation ArgLocEnd, StringRef Arg) { ParamCommandComment::PassDirection Direction; std::string ArgLower = Arg.lower(); // TODO: optimize: lower Name first (need an API in SmallString for that), // after that StringSwitch. if (ArgLower == "[in]") Direction = ParamCommandComment::In; else if (ArgLower == "[out]") Direction = ParamCommandComment::Out; else if (ArgLower == "[in,out]" || ArgLower == "[out,in]") Direction = ParamCommandComment::InOut; else { // Remove spaces. std::string::iterator O = ArgLower.begin(); for (std::string::iterator I = ArgLower.begin(), E = ArgLower.end(); I != E; ++I) { const char C = *I; if (C != ' ' && C != '\n' && C != '\r' && C != '\t' && C != '\v' && C != '\f') *O++ = C; } ArgLower.resize(O - ArgLower.begin()); bool RemovingWhitespaceHelped = false; if (ArgLower == "[in]") { Direction = ParamCommandComment::In; RemovingWhitespaceHelped = true; } else if (ArgLower == "[out]") { Direction = ParamCommandComment::Out; RemovingWhitespaceHelped = true; } else if (ArgLower == "[in,out]" || ArgLower == "[out,in]") { Direction = ParamCommandComment::InOut; RemovingWhitespaceHelped = true; } else { Direction = ParamCommandComment::In; RemovingWhitespaceHelped = false; } SourceRange ArgRange(ArgLocBegin, ArgLocEnd); if (RemovingWhitespaceHelped) Diag(ArgLocBegin, diag::warn_doc_param_spaces_in_direction) << ArgRange << FixItHint::CreateReplacement( ArgRange, ParamCommandComment::getDirectionAsString(Direction)); else Diag(ArgLocBegin, diag::warn_doc_param_invalid_direction) << ArgRange; } Command->setDirection(Direction, /* Explicit = */ true); return Command; } ParamCommandComment *Sema::actOnParamCommandParamNameArg( ParamCommandComment *Command, SourceLocation ArgLocBegin, SourceLocation ArgLocEnd, StringRef Arg) { // Parser will not feed us more arguments than needed. assert(Command->getNumArgs() == 0); if (!Command->isDirectionExplicit()) { // User didn't provide a direction argument. Command->setDirection(ParamCommandComment::In, /* Explicit = */ false); } typedef BlockCommandComment::Argument Argument; Argument *A = new (Allocator) Argument(SourceRange(ArgLocBegin, ArgLocEnd), Arg); Command->setArgs(llvm::makeArrayRef(A, 1)); if (!isFunctionDecl()) { // We already warned that this \\param is not attached to a function decl. return Command; } ArrayRef ParamVars = getParamVars(); // Check that referenced parameter name is in the function decl. const unsigned ResolvedParamIndex = resolveParmVarReference(Arg, ParamVars); if (ResolvedParamIndex != ParamCommandComment::InvalidParamIndex) { Command->setParamIndex(ResolvedParamIndex); return Command; } SourceRange ArgRange(ArgLocBegin, ArgLocEnd); Diag(ArgLocBegin, diag::warn_doc_param_not_found) << Arg << ArgRange; unsigned CorrectedParamIndex = ParamCommandComment::InvalidParamIndex; if (ParamVars.size() == 1) { // If function has only one parameter then only that parameter // can be documented. CorrectedParamIndex = 0; } else { // Do typo correction. CorrectedParamIndex = correctTypoInParmVarReference(Arg, ParamVars); } if (CorrectedParamIndex != ParamCommandComment::InvalidParamIndex) { const ParmVarDecl *CorrectedPVD = ParamVars[CorrectedParamIndex]; if (const IdentifierInfo *CorrectedII = CorrectedPVD->getIdentifier()) Diag(ArgLocBegin, diag::note_doc_param_name_suggestion) << CorrectedII->getName() << FixItHint::CreateReplacement(ArgRange, CorrectedII->getName()); } return Command; } ParamCommandComment *Sema::actOnParamCommandFinish(ParamCommandComment *Command, ParagraphComment *Paragraph) { Command->setParagraph(Paragraph); checkBlockCommandEmptyParagraph(Command); return Command; } InlineCommandComment *Sema::actOnInlineCommand(SourceLocation CommandLocBegin, SourceLocation CommandLocEnd, StringRef CommandName) { ArrayRef Args; return new (Allocator) InlineCommandComment( CommandLocBegin, CommandLocEnd, CommandName, getInlineCommandRenderKind(CommandName), Args); } InlineCommandComment *Sema::actOnInlineCommand(SourceLocation CommandLocBegin, SourceLocation CommandLocEnd, StringRef CommandName, SourceLocation ArgLocBegin, SourceLocation ArgLocEnd, StringRef Arg) { typedef InlineCommandComment::Argument Argument; Argument *A = new (Allocator) Argument(SourceRange(ArgLocBegin, ArgLocEnd), Arg); return new (Allocator) InlineCommandComment( CommandLocBegin, CommandLocEnd, CommandName, getInlineCommandRenderKind(CommandName), llvm::makeArrayRef(A, 1)); } InlineContentComment *Sema::actOnUnknownCommand(SourceLocation LocBegin, SourceLocation LocEnd, StringRef Name) { ArrayRef Args; return new (Allocator) InlineCommandComment( LocBegin, LocEnd, Name, InlineCommandComment::RenderNormal, Args); } TextComment *Sema::actOnText(SourceLocation LocBegin, SourceLocation LocEnd, StringRef Text) { return new (Allocator) TextComment(LocBegin, LocEnd, Text); } VerbatimBlockComment *Sema::actOnVerbatimBlockStart(SourceLocation Loc, StringRef Name) { return new (Allocator) VerbatimBlockComment( Loc, Loc.getLocWithOffset(1 + Name.size()), Name); } VerbatimBlockLineComment *Sema::actOnVerbatimBlockLine(SourceLocation Loc, StringRef Text) { return new (Allocator) VerbatimBlockLineComment(Loc, Text); } VerbatimBlockComment *Sema::actOnVerbatimBlockFinish( VerbatimBlockComment *Block, SourceLocation CloseNameLocBegin, StringRef CloseName, ArrayRef Lines) { Block->setCloseName(CloseName, CloseNameLocBegin); Block->setLines(Lines); return Block; } VerbatimLineComment *Sema::actOnVerbatimLine(SourceLocation LocBegin, StringRef Name, SourceLocation TextBegin, StringRef Text) { return new (Allocator) VerbatimLineComment( LocBegin, TextBegin.getLocWithOffset(Text.size()), Name, TextBegin, Text); } HTMLStartTagComment *Sema::actOnHTMLStartTagStart(SourceLocation LocBegin, StringRef TagName) { return new (Allocator) HTMLStartTagComment(LocBegin, TagName); } HTMLStartTagComment *Sema::actOnHTMLStartTagFinish( HTMLStartTagComment *Tag, ArrayRef Attrs, SourceLocation GreaterLoc, bool IsSelfClosing) { Tag->setAttrs(Attrs); Tag->setGreaterLoc(GreaterLoc); if (IsSelfClosing) Tag->setSelfClosing(); else if (!isHTMLEndTagForbidden(Tag->getTagName())) HTMLOpenTags.push_back(Tag); return Tag; } HTMLEndTagComment *Sema::actOnHTMLEndTag(SourceLocation LocBegin, SourceLocation LocEnd, StringRef TagName) { HTMLEndTagComment *HET = new (Allocator) HTMLEndTagComment(LocBegin, LocEnd, TagName); if (isHTMLEndTagForbidden(TagName)) { Diag(HET->getLocation(), diag::warn_doc_html_end_forbidden) << TagName << HET->getSourceRange(); return HET; } bool FoundOpen = false; for (SmallVectorImpl::const_reverse_iterator I = HTMLOpenTags.rbegin(), E = HTMLOpenTags.rend(); I != E; ++I) { if ((*I)->getTagName() == TagName) { FoundOpen = true; break; } } if (!FoundOpen) { Diag(HET->getLocation(), diag::warn_doc_html_end_unbalanced) << HET->getSourceRange(); return HET; } while (!HTMLOpenTags.empty()) { const HTMLStartTagComment *HST = HTMLOpenTags.back(); HTMLOpenTags.pop_back(); StringRef LastNotClosedTagName = HST->getTagName(); if (LastNotClosedTagName == TagName) break; if (isHTMLEndTagOptional(LastNotClosedTagName)) continue; bool OpenLineInvalid; const unsigned OpenLine = SourceMgr.getPresumedLineNumber( HST->getLocation(), &OpenLineInvalid); bool CloseLineInvalid; const unsigned CloseLine = SourceMgr.getPresumedLineNumber( HET->getLocation(), &CloseLineInvalid); if (OpenLineInvalid || CloseLineInvalid || OpenLine == CloseLine) Diag(HST->getLocation(), diag::warn_doc_html_start_end_mismatch) << HST->getTagName() << HET->getTagName() << HST->getSourceRange() << HET->getSourceRange(); else { Diag(HST->getLocation(), diag::warn_doc_html_start_end_mismatch) << HST->getTagName() << HET->getTagName() << HST->getSourceRange(); Diag(HET->getLocation(), diag::note_doc_html_end_tag) << HET->getSourceRange(); } } return HET; } FullComment *Sema::actOnFullComment( ArrayRef Blocks) { SmallVector Params; return new (Allocator) FullComment(Blocks); } void Sema::checkBlockCommandEmptyParagraph(BlockCommandComment *Command) { ParagraphComment *Paragraph = Command->getParagraph(); if (Paragraph->isWhitespace()) { SourceLocation DiagLoc; if (Command->getNumArgs() > 0) DiagLoc = Command->getArgRange(Command->getNumArgs() - 1).getEnd(); if (!DiagLoc.isValid()) DiagLoc = Command->getCommandNameRange().getEnd(); Diag(DiagLoc, diag::warn_doc_block_command_empty_paragraph) << Command->getCommandName() << Command->getSourceRange(); } } bool Sema::isFunctionDecl() { if (IsThisDeclInspected) return IsFunctionDecl; inspectThisDecl(); return IsFunctionDecl; } ArrayRef Sema::getParamVars() { if (IsThisDeclInspected) return ParamVars; inspectThisDecl(); return ParamVars; } void Sema::inspectThisDecl() { if (!ThisDecl) { IsFunctionDecl = false; ParamVars = ArrayRef(); } else if (const FunctionDecl *FD = dyn_cast(ThisDecl)) { IsFunctionDecl = true; ParamVars = ArrayRef(FD->param_begin(), FD->getNumParams()); } else if (const ObjCMethodDecl *MD = dyn_cast(ThisDecl)) { IsFunctionDecl = true; ParamVars = ArrayRef(MD->param_begin(), MD->param_size()); } else { IsFunctionDecl = false; ParamVars = ArrayRef(); } IsThisDeclInspected = true; } unsigned Sema::resolveParmVarReference(StringRef Name, ArrayRef ParamVars) { for (unsigned i = 0, e = ParamVars.size(); i != e; ++i) { const IdentifierInfo *II = ParamVars[i]->getIdentifier(); if (II && II->getName() == Name) return i; } return ParamCommandComment::InvalidParamIndex; } unsigned Sema::correctTypoInParmVarReference( StringRef Typo, ArrayRef ParamVars) { const unsigned MaxEditDistance = (Typo.size() + 2) / 3; unsigned BestPVDIndex = 0; unsigned BestEditDistance = MaxEditDistance + 1; for (unsigned i = 0, e = ParamVars.size(); i != e; ++i) { const IdentifierInfo *II = ParamVars[i]->getIdentifier(); if (II) { StringRef Name = II->getName(); unsigned MinPossibleEditDistance = abs((int)Name.size() - (int)Typo.size()); if (MinPossibleEditDistance > 0 && Typo.size() / MinPossibleEditDistance < 3) continue; unsigned EditDistance = Typo.edit_distance(Name, true, MaxEditDistance); if (EditDistance < BestEditDistance) { BestEditDistance = EditDistance; BestPVDIndex = i; } } } if (BestEditDistance <= MaxEditDistance) return BestPVDIndex; else return ParamCommandComment::InvalidParamIndex;; } // TODO: tablegen bool Sema::isBlockCommand(StringRef Name) { return llvm::StringSwitch(Name) .Cases("brief", "short", true) .Case("result", true) .Case("return", true) .Case("returns", true) .Case("author", true) .Case("authors", true) .Case("pre", true) .Case("post", true) .Default(false) || isParamCommand(Name); } bool Sema::isParamCommand(StringRef Name) { return llvm::StringSwitch(Name) .Case("param", true) .Case("arg", true) .Default(false); } unsigned Sema::getBlockCommandNumArgs(StringRef Name) { return llvm::StringSwitch(Name) .Cases("brief", "short", 0) .Case("pre", 0) .Case("post", 0) .Case("author", 0) .Case("authors", 0) .Default(0); } bool Sema::isInlineCommand(StringRef Name) const { return llvm::StringSwitch(Name) .Case("b", true) .Cases("c", "p", true) .Cases("a", "e", "em", true) .Default(false); } InlineCommandComment::RenderKind Sema::getInlineCommandRenderKind(StringRef Name) const { assert(isInlineCommand(Name)); return llvm::StringSwitch(Name) .Case("b", InlineCommandComment::RenderBold) .Cases("c", "p", InlineCommandComment::RenderMonospaced) .Cases("a", "e", "em", InlineCommandComment::RenderEmphasized) .Default(InlineCommandComment::RenderNormal); } bool Sema::isHTMLEndTagOptional(StringRef Name) { return llvm::StringSwitch(Name) .Case("p", true) .Case("li", true) .Case("dt", true) .Case("dd", true) .Case("tr", true) .Case("th", true) .Case("td", true) .Case("thead", true) .Case("tfoot", true) .Case("tbody", true) .Case("colgroup", true) .Default(false); } bool Sema::isHTMLEndTagForbidden(StringRef Name) { return llvm::StringSwitch(Name) .Case("br", true) .Case("hr", true) .Case("img", true) .Case("col", true) .Default(false); } } // end namespace comments } // end namespace clang