blob: bb0d03ebd27a949b4f7669ce2a36ed2bd653c929 [file] [log] [blame]
//===--- 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<InlineContentComment *> 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<BlockCommandComment::Argument> 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<const ParmVarDecl *> 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);
if (ParamVarDocs[ResolvedParamIndex]) {
SourceRange ArgRange(ArgLocBegin, ArgLocEnd);
Diag(ArgLocBegin, diag::warn_doc_param_duplicate)
<< Arg << ArgRange;
ParamCommandComment *PrevCommand = ParamVarDocs[ResolvedParamIndex];
Diag(PrevCommand->getLocation(), diag::note_doc_param_previous)
<< PrevCommand->getParamNameRange();
}
ParamVarDocs[ResolvedParamIndex] = Command;
return Command;
}
SourceRange ArgRange(ArgLocBegin, ArgLocEnd);
Diag(ArgLocBegin, diag::warn_doc_param_not_found)
<< Arg << ArgRange;
// No parameters -- can't suggest a correction.
if (ParamVars.size() == 0)
return Command;
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<InlineCommandComment::Argument> 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<InlineCommandComment::Argument> 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<VerbatimBlockLineComment *> 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<HTMLStartTagComment::Attribute> 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<HTMLStartTagComment *>::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<BlockContentComment *> Blocks) {
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)
inspectThisDecl();
return IsFunctionDecl;
}
ArrayRef<const ParmVarDecl *> Sema::getParamVars() {
if (!IsThisDeclInspected)
inspectThisDecl();
return ParamVars;
}
void Sema::inspectThisDecl() {
assert(!IsThisDeclInspected);
if (!ThisDecl) {
IsFunctionDecl = false;
ParamVars = ArrayRef<const ParmVarDecl *>();
} else if (const FunctionDecl *FD = dyn_cast<FunctionDecl>(ThisDecl)) {
IsFunctionDecl = true;
ParamVars = ArrayRef<const ParmVarDecl *>(FD->param_begin(),
FD->getNumParams());
} else if (const ObjCMethodDecl *MD = dyn_cast<ObjCMethodDecl>(ThisDecl)) {
IsFunctionDecl = true;
ParamVars = ArrayRef<const ParmVarDecl *>(MD->param_begin(),
MD->param_size());
} else {
IsFunctionDecl = false;
ParamVars = ArrayRef<const ParmVarDecl *>();
}
ParamVarDocs.resize(ParamVars.size(), NULL);
IsThisDeclInspected = true;
}
unsigned Sema::resolveParmVarReference(StringRef Name,
ArrayRef<const ParmVarDecl *> 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<const ParmVarDecl *> 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<bool>(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<bool>(Name)
.Case("param", true)
.Case("arg", true)
.Default(false);
}
unsigned Sema::getBlockCommandNumArgs(StringRef Name) {
return llvm::StringSwitch<unsigned>(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<bool>(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<InlineCommandComment::RenderKind>(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<bool>(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<bool>(Name)
.Case("br", true)
.Case("hr", true)
.Case("img", true)
.Case("col", true)
.Default(false);
}
} // end namespace comments
} // end namespace clang