blob: fa8001b327c621ade810651d21a35a2ee6ae1f35 [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) {
}
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 (!ThisDecl ||
!(isa<FunctionDecl>(ThisDecl) || isa<ObjCMethodDecl>(ThisDecl)))
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->getArgCount() == 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 (!ThisDecl)
return Command;
const ParmVarDecl * const *ParamVars;
unsigned NumParams;
if (const FunctionDecl *FD = dyn_cast<FunctionDecl>(ThisDecl)) {
ParamVars = FD->param_begin();
NumParams = FD->getNumParams();
} else if (const ObjCMethodDecl *MD = dyn_cast<ObjCMethodDecl>(ThisDecl)) {
ParamVars = MD->param_begin();
NumParams = MD->param_size();
} else {
// We already warned that this \\param is not attached to a function decl.
return Command;
}
// Check that referenced parameter name is in the function decl.
const unsigned ResolvedParamIndex = resolveParmVarReference(Arg, ParamVars,
NumParams);
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 (NumParams == 1) {
// If function has only one parameter then only that parameter
// can be documented.
CorrectedParamIndex = 0;
} else {
// Do typo correction.
CorrectedParamIndex = correctTypoInParmVarReference(Arg, ParamVars,
NumParams);
}
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,
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,
llvm::makeArrayRef(A, 1));
}
InlineContentComment *Sema::actOnUnknownCommand(SourceLocation LocBegin,
SourceLocation LocEnd,
StringRef Name) {
ArrayRef<InlineCommandComment::Argument> Args;
return new (Allocator) InlineCommandComment(LocBegin, LocEnd, Name, 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);
}
HTMLOpenTagComment *Sema::actOnHTMLOpenTagStart(SourceLocation LocBegin,
StringRef TagName) {
HTMLOpenTagComment *HOT =
new (Allocator) HTMLOpenTagComment(LocBegin, TagName);
return HOT;
}
HTMLOpenTagComment *Sema::actOnHTMLOpenTagFinish(
HTMLOpenTagComment *Tag,
ArrayRef<HTMLOpenTagComment::Attribute> Attrs,
SourceLocation GreaterLoc,
bool IsSelfClosing) {
Tag->setAttrs(Attrs);
Tag->setGreaterLoc(GreaterLoc);
if (IsSelfClosing)
Tag->setSelfClosing();
else
HTMLOpenTags.push_back(Tag);
return Tag;
}
HTMLCloseTagComment *Sema::actOnHTMLCloseTag(SourceLocation LocBegin,
SourceLocation LocEnd,
StringRef TagName) {
HTMLCloseTagComment *HCT =
new (Allocator) HTMLCloseTagComment(LocBegin, LocEnd, TagName);
bool FoundOpen = false;
for (SmallVectorImpl<HTMLOpenTagComment *>::const_reverse_iterator
I = HTMLOpenTags.rbegin(), E = HTMLOpenTags.rend();
I != E; ++I) {
if ((*I)->getTagName() == TagName) {
FoundOpen = true;
break;
}
}
if (!FoundOpen) {
Diag(HCT->getLocation(), diag::warn_doc_html_close_unbalanced)
<< HCT->getSourceRange();
return HCT;
}
while (!HTMLOpenTags.empty()) {
const HTMLOpenTagComment *HOT = HTMLOpenTags.back();
HTMLOpenTags.pop_back();
StringRef LastNotClosedTagName = HOT->getTagName();
if (LastNotClosedTagName == TagName)
break;
if (!HTMLOpenTagNeedsClosing(LastNotClosedTagName))
continue;
bool OpenLineInvalid;
const unsigned OpenLine = SourceMgr.getPresumedLineNumber(
HOT->getLocation(),
&OpenLineInvalid);
bool CloseLineInvalid;
const unsigned CloseLine = SourceMgr.getPresumedLineNumber(
HCT->getLocation(),
&CloseLineInvalid);
if (OpenLineInvalid || CloseLineInvalid || OpenLine == CloseLine)
Diag(HOT->getLocation(), diag::warn_doc_html_open_close_mismatch)
<< HOT->getTagName() << HCT->getTagName()
<< HOT->getSourceRange() << HCT->getSourceRange();
else {
Diag(HOT->getLocation(), diag::warn_doc_html_open_close_mismatch)
<< HOT->getTagName() << HCT->getTagName()
<< HOT->getSourceRange();
Diag(HCT->getLocation(), diag::note_doc_html_closing_tag)
<< HCT->getSourceRange();
}
}
return HCT;
}
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->getArgCount() > 0)
DiagLoc = Command->getArgRange(Command->getArgCount() - 1).getEnd();
if (!DiagLoc.isValid())
DiagLoc = Command->getCommandNameRange().getEnd();
Diag(DiagLoc, diag::warn_doc_block_command_empty_paragraph)
<< Command->getCommandName()
<< Command->getSourceRange();
}
}
unsigned Sema::resolveParmVarReference(StringRef Name,
const ParmVarDecl * const *ParamVars,
unsigned NumParams) {
for (unsigned i = 0; i != NumParams; ++i) {
const IdentifierInfo *II = ParamVars[i]->getIdentifier();
if (II && II->getName() == Name)
return i;
}
return ParamCommandComment::InvalidParamIndex;
}
unsigned Sema::correctTypoInParmVarReference(
StringRef Typo,
const ParmVarDecl * const *ParamVars,
unsigned NumParams) {
const unsigned MaxEditDistance = (Typo.size() + 2) / 3;
unsigned BestPVDIndex = NULL;
unsigned BestEditDistance = MaxEditDistance + 1;
for (unsigned i = 0; i != NumParams; ++i) {
const IdentifierInfo *II = ParamVars[i]->getIdentifier();
if (II) {
StringRef Name = II->getName();
unsigned MinPossibleEditDistance = abs(Name.size() - 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)
.Case("brief", 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)
.Case("brief", 0)
.Case("pre", 0)
.Case("post", 0)
.Case("author", 0)
.Case("authors", 0)
.Default(0);
}
bool Sema::isInlineCommand(StringRef Name) {
return llvm::StringSwitch<bool>(Name)
.Case("c", true)
.Case("em", true)
.Default(false);
}
bool Sema::HTMLOpenTagNeedsClosing(StringRef Name) {
return llvm::StringSwitch<bool>(Name)
.Case("br", false)
.Case("hr", false)
.Case("li", false)
.Default(true);
}
} // end namespace comments
} // end namespace clang