Enable comment parsing and semantic analysis to emit diagnostics. A few

diagnostics implemented -- see testcases.

I created a new TableGen file for comment diagnostics,
DiagnosticCommentKinds.td, because comment diagnostics don't logically
fit into AST diagnostics file.  But I don't feel strongly about it.

This also implements support for self-closing HTML tags in comment
lexer and parser (for example, <br />).

In order to issue precise diagnostics CommentSema needs to know the
declaration the comment is attached to.  There is no easy way to find a decl by 
comment, so we match comments and decls in lockstep: after parsing one
declgroup we check if we have any new, not yet attached comments.  If we do --
then we do the usual comment-finding process.

It is interesting that this automatically handles trailing comments.
We pick up not only comments that precede the declaration, but also
comments that *follow* the declaration -- thanks to the lookahead in
the lexer: after parsing the declgroup we've consumed the semicolon
and looked ahead through comments.

Added -Wdocumentation-html flag for semantic HTML errors to allow the user to 
disable only HTML warnings (but not HTML parse errors, which we emit as
warnings in -Wdocumentation).

llvm-svn: 160078
This commit is contained in:
Dmitri Gribenko 2012-07-11 21:38:39 +00:00
parent a46ec4534d
commit f26054f0fb
38 changed files with 1149 additions and 151 deletions

View File

@ -392,6 +392,11 @@ public:
SourceManager& getSourceManager() { return SourceMgr; }
const SourceManager& getSourceManager() const { return SourceMgr; }
llvm::BumpPtrAllocator &getAllocator() const {
return BumpAlloc;
}
void *Allocate(unsigned Size, unsigned Align = 8) const {
return BumpAlloc.Allocate(Size, Align);
}
@ -436,9 +441,13 @@ public:
/// \brief Return the documentation comment attached to a given declaration,
/// without looking into cache.
const RawComment *getRawCommentForDeclNoCache(const Decl *D) const;
RawComment *getRawCommentForDeclNoCache(const Decl *D) const;
public:
RawCommentList &getRawCommentList() {
return Comments;
}
void addComment(const RawComment &RC) {
Comments.addComment(RC, BumpAlloc);
}

View File

@ -50,6 +50,16 @@ protected:
};
enum { NumInlineContentCommentBitfields = 9 };
class HTMLOpenTagCommentBitfields {
friend class HTMLOpenTagComment;
unsigned : NumInlineContentCommentBitfields;
/// True if this tag is self-closing (e. g., <br />). This is based on tag
/// spelling in comment (plain <br> would not set this flag).
unsigned IsSelfClosing : 1;
};
class ParamCommandCommentBitfields {
friend class ParamCommandComment;
@ -66,6 +76,7 @@ protected:
union {
CommentBitfields CommentBits;
InlineContentCommentBitfields InlineContentCommentBits;
HTMLOpenTagCommentBitfields HTMLOpenTagCommentBits;
ParamCommandCommentBitfields ParamCommandCommentBits;
};
@ -107,8 +118,6 @@ public:
static bool classof(const Comment *) { return true; }
typedef Comment * const *child_iterator;
SourceRange getSourceRange() const LLVM_READONLY { return Range; }
SourceLocation getLocStart() const LLVM_READONLY {
@ -121,9 +130,13 @@ public:
SourceLocation getLocation() const LLVM_READONLY { return Loc; }
typedef Comment * const *child_iterator;
child_iterator child_begin() const;
child_iterator child_end() const;
// TODO: const child iterator
unsigned child_count() const {
return child_end() - child_begin();
}
@ -180,6 +193,8 @@ public:
child_iterator child_end() const { return NULL; }
StringRef getText() const LLVM_READONLY { return Text; }
bool isWhitespace() const;
};
/// A command with word-like arguments that is considered inline content.
@ -325,8 +340,9 @@ public:
LocBegin, LocBegin.getLocWithOffset(1 + TagName.size()),
TagName,
LocBegin.getLocWithOffset(1),
LocBegin.getLocWithOffset(1 + TagName.size()))
{ }
LocBegin.getLocWithOffset(1 + TagName.size())) {
HTMLOpenTagCommentBits.IsSelfClosing = false;
}
static bool classof(const Comment *C) {
return C->getCommentKind() == HTMLOpenTagCommentKind;
@ -362,6 +378,14 @@ public:
void setGreaterLoc(SourceLocation GreaterLoc) {
Range.setEnd(GreaterLoc);
}
bool isSelfClosing() const {
return HTMLOpenTagCommentBits.IsSelfClosing;
}
void setSelfClosing() {
HTMLOpenTagCommentBits.IsSelfClosing = true;
}
};
/// A closing HTML tag.
@ -438,6 +462,8 @@ public:
child_iterator child_end() const {
return reinterpret_cast<child_iterator>(Content.end());
}
bool isWhitespace() const;
};
/// A command that has zero or more word-like arguments (number of word-like
@ -520,6 +546,11 @@ public:
void setArgs(llvm::ArrayRef<Argument> A) {
Args = A;
if (Args.size() > 0) {
SourceLocation NewLocEnd = Args.back().Range.getEnd();
if (NewLocEnd.isValid())
setSourceRange(SourceRange(getLocStart(), NewLocEnd));
}
}
ParagraphComment *getParagraph() const LLVM_READONLY {
@ -536,18 +567,18 @@ public:
/// Doxygen \\param command.
class ParamCommandComment : public BlockCommandComment {
public:
enum PassDirection {
In,
Out,
InOut
};
private:
/// Parameter index in the function declaration.
unsigned ParamIndex;
public:
enum { InvalidParamIndex = ~0U };
ParamCommandComment(SourceLocation LocBegin,
SourceLocation LocEnd,
StringRef Name) :
BlockCommandComment(ParamCommandCommentKind, LocBegin, LocEnd, Name) {
BlockCommandComment(ParamCommandCommentKind, LocBegin, LocEnd, Name),
ParamIndex(InvalidParamIndex) {
ParamCommandCommentBits.Direction = In;
ParamCommandCommentBits.IsDirectionExplicit = false;
}
@ -558,6 +589,14 @@ public:
static bool classof(const ParamCommandComment *) { return true; }
enum PassDirection {
In,
Out,
InOut
};
static const char *getDirectionAsString(PassDirection D);
PassDirection getDirection() const LLVM_READONLY {
return static_cast<PassDirection>(ParamCommandCommentBits.Direction);
}
@ -582,6 +621,19 @@ public:
SourceRange getParamNameRange() const {
return Args[0].Range;
}
bool isParamIndexValid() const LLVM_READONLY {
return ParamIndex != InvalidParamIndex;
}
unsigned getParamIndex() const LLVM_READONLY {
return ParamIndex;
}
void setParamIndex(unsigned Index) {
ParamIndex = Index;
assert(isParamIndexValid());
}
};
/// A line of text contained in a verbatim block.

View File

@ -0,0 +1,29 @@
//===--- CommentDiagnostic.h - Diagnostics for the AST library --*- C++ -*-===//
//
// The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
#ifndef LLVM_CLANG_COMMENTDIAGNOSTIC_H
#define LLVM_CLANG_COMMENTDIAGNOSTIC_H
#include "clang/Basic/Diagnostic.h"
namespace clang {
namespace diag {
enum {
#define DIAG(ENUM,FLAGS,DEFAULT_MAPPING,DESC,GROUP,\
SFINAE,ACCESS,NOWERROR,SHOWINSYSHEADER,CATEGORY) ENUM,
#define COMMENTSTART
#include "clang/Basic/DiagnosticCommentKinds.inc"
#undef DIAG
NUM_BUILTIN_COMMENT_DIAGNOSTICS
};
} // end namespace diag
} // end namespace clang
#endif

View File

@ -43,6 +43,7 @@ enum TokenKind {
html_equals, // =
html_quoted_string, // "blah\"blah" or 'blah\'blah'
html_greater, // >
html_slash_greater, // />
html_tag_close // </tag
};
} // end namespace tok

View File

@ -14,12 +14,15 @@
#ifndef LLVM_CLANG_AST_COMMENT_PARSER_H
#define LLVM_CLANG_AST_COMMENT_PARSER_H
#include "clang/Basic/Diagnostic.h"
#include "clang/AST/CommentLexer.h"
#include "clang/AST/Comment.h"
#include "clang/AST/CommentSema.h"
#include "llvm/Support/Allocator.h"
namespace clang {
class SourceManager;
namespace comments {
/// Doxygen comment parser.
@ -28,8 +31,12 @@ class Parser {
Sema &S;
/// Allocator for anything that goes into AST nodes.
llvm::BumpPtrAllocator &Allocator;
/// Source manager for the comment being parsed.
const SourceManager &SourceMgr;
template<typename T>
ArrayRef<T> copyArray(ArrayRef<T> Source) {
size_t Size = Source.size();
@ -41,6 +48,12 @@ class Parser {
return llvm::makeArrayRef(static_cast<T *>(NULL), 0);
}
DiagnosticsEngine &Diags;
DiagnosticBuilder Diag(SourceLocation Loc, unsigned DiagID) {
return Diags.Report(Loc, DiagID);
}
/// Current lookahead token. We can safely assume that all tokens are from
/// a single source file.
Token Tok;
@ -79,7 +92,8 @@ class Parser {
}
public:
Parser(Lexer &L, Sema &S, llvm::BumpPtrAllocator &Allocator);
Parser(Lexer &L, Sema &S, llvm::BumpPtrAllocator &Allocator,
const SourceManager &SourceMgr, DiagnosticsEngine &Diags);
/// Parse arguments for \\param command.
ParamCommandComment *parseParamCommandArgs(

View File

@ -14,6 +14,7 @@
#ifndef LLVM_CLANG_AST_COMMENT_SEMA_H
#define LLVM_CLANG_AST_COMMENT_SEMA_H
#include "clang/Basic/Diagnostic.h"
#include "clang/Basic/SourceLocation.h"
#include "clang/AST/Comment.h"
#include "llvm/ADT/ArrayRef.h"
@ -21,13 +22,37 @@
#include "llvm/Support/Allocator.h"
namespace clang {
class Decl;
class FunctionDecl;
class ParmVarDecl;
class SourceMgr;
namespace comments {
class Sema {
/// Allocator for AST nodes.
llvm::BumpPtrAllocator &Allocator;
/// Source manager for the comment being parsed.
const SourceManager &SourceMgr;
DiagnosticsEngine &Diags;
const Decl *ThisDecl;
DiagnosticBuilder Diag(SourceLocation Loc, unsigned DiagID) {
return Diags.Report(Loc, DiagID);
}
/// A stack of HTML tags that are currently open (not matched with closing
/// tags).
SmallVector<HTMLOpenTagComment *, 8> HTMLOpenTags;
public:
Sema(llvm::BumpPtrAllocator &Allocator);
Sema(llvm::BumpPtrAllocator &Allocator, const SourceManager &SourceMgr,
DiagnosticsEngine &Diags);
void setDecl(const Decl *D);
ParagraphComment *actOnParagraphComment(
ArrayRef<InlineContentComment *> Content);
@ -47,11 +72,17 @@ public:
SourceLocation LocEnd,
StringRef Name);
ParamCommandComment *actOnParamCommandArg(ParamCommandComment *Command,
ParamCommandComment *actOnParamCommandDirectionArg(
ParamCommandComment *Command,
SourceLocation ArgLocBegin,
SourceLocation ArgLocEnd,
StringRef Arg,
bool IsDirection);
StringRef Arg);
ParamCommandComment *actOnParamCommandParamNameArg(
ParamCommandComment *Command,
SourceLocation ArgLocBegin,
SourceLocation ArgLocEnd,
StringRef Arg);
ParamCommandComment *actOnParamCommandFinish(ParamCommandComment *Command,
ParagraphComment *Paragraph);
@ -98,7 +129,8 @@ public:
HTMLOpenTagComment *actOnHTMLOpenTagFinish(
HTMLOpenTagComment *Tag,
ArrayRef<HTMLOpenTagComment::Attribute> Attrs,
SourceLocation GreaterLoc);
SourceLocation GreaterLoc,
bool IsSelfClosing);
HTMLCloseTagComment *actOnHTMLCloseTag(SourceLocation LocBegin,
SourceLocation LocEnd,
@ -106,6 +138,19 @@ public:
FullComment *actOnFullComment(ArrayRef<BlockContentComment *> Blocks);
void checkBlockCommandEmptyParagraph(BlockCommandComment *Command);
/// Returns index of a function parameter with a given name.
unsigned resolveParmVarReference(StringRef Name,
const ParmVarDecl * const *ParamVars,
unsigned NumParams);
/// Returns index of a function parameter with the name closest to a given
/// typo.
unsigned correctTypoInParmVarReference(StringRef Typo,
const ParmVarDecl * const *ParamVars,
unsigned NumParams);
bool isBlockCommand(StringRef Name);
bool isParamCommand(StringRef Name);
unsigned getBlockCommandNumArgs(StringRef Name);

View File

@ -48,6 +48,14 @@ public:
return Kind == RCK_Merged;
}
bool isAttached() const LLVM_READONLY {
return IsAttached;
}
void setAttached() {
IsAttached = true;
}
/// Returns true if it is a comment that should be put after a member:
/// \code ///< stuff \endcode
/// \code //!< stuff \endcode
@ -110,6 +118,9 @@ private:
unsigned Kind : 3;
/// True if comment is attached to a declaration in ASTContext.
bool IsAttached : 1;
bool IsTrailingComment : 1;
bool IsAlmostTrailingComment : 1;
@ -122,7 +133,7 @@ private:
RawComment(SourceRange SR, CommentKind K, bool IsTrailingComment,
bool IsAlmostTrailingComment) :
Range(SR), RawTextValid(false), BriefTextValid(false), Kind(K),
IsTrailingComment(IsTrailingComment),
IsAttached(false), IsTrailingComment(IsTrailingComment),
IsAlmostTrailingComment(IsAlmostTrailingComment),
BeginLineValid(false), EndLineValid(false)
{ }

View File

@ -16,6 +16,7 @@
#define LLVM_CLANG_ALL_DIAGNOSTICS_H
#include "clang/AST/ASTDiagnostic.h"
#include "clang/AST/CommentDiagnostic.h"
#include "clang/Analysis/AnalysisDiagnostic.h"
#include "clang/Driver/DriverDiagnostic.h"
#include "clang/Frontend/FrontendDiagnostic.h"

View File

@ -7,6 +7,7 @@ endmacro(clang_diag_gen)
clang_diag_gen(Analysis)
clang_diag_gen(AST)
clang_diag_gen(Comment)
clang_diag_gen(Common)
clang_diag_gen(Driver)
clang_diag_gen(Frontend)

View File

@ -88,6 +88,7 @@ class AccessControl { bit AccessControl = 1; }
// Definitions for Diagnostics.
include "DiagnosticASTKinds.td"
include "DiagnosticAnalysisKinds.td"
include "DiagnosticCommentKinds.td"
include "DiagnosticCommonKinds.td"
include "DiagnosticDriverKinds.td"
include "DiagnosticFrontendKinds.td"

View File

@ -0,0 +1,70 @@
//==--- DiagnosticCommentKinds.td - diagnostics related to comments -------===//
//
// The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
let Component = "Comment" in {
let CategoryName = "Documentation Issue" in {
// HTML parsing errors. These are under -Wdocumentation to make sure the user
// knows that we didn't parse something as he might expect.
def warn_doc_html_open_tag_expected_quoted_string : Warning<
"expected quoted string after equals sign">,
InGroup<Documentation>, DefaultIgnore;
def warn_doc_html_open_tag_expected_ident_or_greater : Warning<
"HTML opening tag prematurely ended, expected attribute name or '>'">,
InGroup<Documentation>, DefaultIgnore;
def note_doc_html_tag_started_here : Note<
"HTML tag started here">;
// HTML semantic errors
def warn_doc_html_close_unbalanced : Warning<
"HTML closing tag does not match any opening tag">,
InGroup<DocumentationHTML>, DefaultIgnore;
def warn_doc_html_open_close_mismatch : Warning<
"HTML opening tag '%0' closed by '%1'">,
InGroup<DocumentationHTML>, DefaultIgnore;
def note_doc_html_closing_tag : Note<
"closing tag">;
// Commands
def warn_doc_block_command_empty_paragraph : Warning<
"empty paragraph passed to '\\%0' command">,
InGroup<Documentation>, DefaultIgnore;
// \param command
def warn_doc_param_invalid_direction : Warning<
"unrecognized parameter passing direction, "
"valid directions are '[in]', '[out]' and '[in,out]'">,
InGroup<Documentation>, DefaultIgnore;
def warn_doc_param_spaces_in_direction : Warning<
"whitespace is not allowed in parameter passing direction">,
InGroup<DocumentationPedantic>, DefaultIgnore;
def warn_doc_param_not_attached_to_a_function_decl : Warning<
"'\\param' command used in a comment that is not attached to "
"a function declaration">,
InGroup<Documentation>, DefaultIgnore;
def warn_doc_param_not_found : Warning<
"parameter '%0' not found in the function declaration">,
InGroup<Documentation>, DefaultIgnore;
def note_doc_param_name_suggestion : Note<
"did you mean '%0'?">;
} // end of documentation issue category
} // end of AST component

View File

@ -57,7 +57,9 @@ def DeprecatedImplementations :DiagGroup<"deprecated-implementations">;
def : DiagGroup<"disabled-optimization">;
def : DiagGroup<"discard-qual">;
def : DiagGroup<"div-by-zero">;
def Doxygen : DiagGroup<"doxygen">;
def DocumentationHTML : DiagGroup<"documentation-html">;
def DocumentationPedantic : DiagGroup<"documentation-pedantic">;
def Documentation : DiagGroup<"documentation", [DocumentationHTML]>;
def EmptyBody : DiagGroup<"empty-body">;
def ExtraTokens : DiagGroup<"extra-tokens">;

View File

@ -38,7 +38,8 @@ namespace clang {
DIAG_START_LEX = DIAG_START_SERIALIZATION + 120,
DIAG_START_PARSE = DIAG_START_LEX + 300,
DIAG_START_AST = DIAG_START_PARSE + 400,
DIAG_START_SEMA = DIAG_START_AST + 100,
DIAG_START_COMMENT = DIAG_START_AST + 100,
DIAG_START_SEMA = DIAG_START_COMMENT + 100,
DIAG_START_ANALYSIS = DIAG_START_SEMA + 3000,
DIAG_UPPER_LIMIT = DIAG_START_ANALYSIS + 100
};

View File

@ -5704,7 +5704,7 @@ def err_module_private_definition : Error<
let CategoryName = "Documentation Issue" in {
def warn_not_a_doxygen_trailing_member_comment : Warning<
"not a Doxygen trailing comment">, InGroup<Doxygen>, DefaultIgnore;
"not a Doxygen trailing comment">, InGroup<Documentation>, DefaultIgnore;
} // end of documentation issue category
} // end of sema component.

View File

@ -1,6 +1,7 @@
CLANG_LEVEL := ../../..
BUILT_SOURCES = \
DiagnosticAnalysisKinds.inc DiagnosticASTKinds.inc \
DiagnosticCommentKinds.inc \
DiagnosticCommonKinds.inc DiagnosticDriverKinds.inc \
DiagnosticFrontendKinds.inc DiagnosticLexKinds.inc \
DiagnosticParseKinds.inc DiagnosticSemaKinds.inc \

View File

@ -1313,6 +1313,12 @@ public:
unsigned NumDecls);
DeclGroupPtrTy BuildDeclaratorGroup(Decl **Group, unsigned NumDecls,
bool TypeMayContainAuto = true);
/// Should be called on all declarations that might have attached
/// documentation comments.
void ActOnDocumentableDecl(Decl *D);
void ActOnDocumentableDecls(Decl **Group, unsigned NumDecls);
void ActOnFinishKNRParamDeclarations(Scope *S, Declarator &D,
SourceLocation LocAfterDecls);
void CheckForFunctionRedefinition(FunctionDecl *FD);

View File

@ -56,7 +56,7 @@ enum FloatingRank {
HalfRank, FloatRank, DoubleRank, LongDoubleRank
};
const RawComment *ASTContext::getRawCommentForDeclNoCache(const Decl *D) const {
RawComment *ASTContext::getRawCommentForDeclNoCache(const Decl *D) const {
if (!CommentsLoaded && ExternalSource) {
ExternalSource->ReadComments();
CommentsLoaded = true;
@ -160,11 +160,13 @@ const RawComment *ASTContext::getRawCommentForDecl(const Decl *D) const {
return C.first;
}
const RawComment *RC = getRawCommentForDeclNoCache(D);
RawComment *RC = getRawCommentForDeclNoCache(D);
// If we found a comment, it should be a documentation comment.
assert(!RC || RC->isDocumentation());
DeclComments[D] =
RawAndParsedComment(RC, static_cast<comments::FullComment *>(NULL));
if (RC)
RC->setAttached();
return RC;
}
@ -187,8 +189,10 @@ comments::FullComment *ASTContext::getCommentForDecl(const Decl *D) const {
comments::Lexer L(RC->getSourceRange().getBegin(), comments::CommentOptions(),
RawText.begin(), RawText.end());
comments::Sema S(this->BumpAlloc);
comments::Parser P(L, S, this->BumpAlloc);
comments::Sema S(getAllocator(), getSourceManager(), getDiagnostics());
S.setDecl(D);
comments::Parser P(L, S, getAllocator(), getSourceManager(),
getDiagnostics());
comments::FullComment *FC = P.parseFullComment();
DeclComments[D].second = FC;

View File

@ -64,6 +64,7 @@ add_dependencies(clangAST
ClangAttrList
ClangAttrImpl
ClangDiagnosticAST
ClangDiagnosticComment
ClangCommentNodes
ClangDeclNodes
ClangStmtNodes

View File

@ -86,6 +86,38 @@ Comment::child_iterator Comment::child_end() const {
llvm_unreachable("Unknown comment kind!");
}
bool TextComment::isWhitespace() const {
for (StringRef::const_iterator I = Text.begin(), E = Text.end();
I != E; ++I) {
const char C = *I;
if (C != ' ' && C != '\n' && C != '\r' &&
C != '\t' && C != '\f' && C != '\v')
return false;
}
return true;
}
bool ParagraphComment::isWhitespace() const {
for (child_iterator I = child_begin(), E = child_end(); I != E; ++I) {
if (const TextComment *TC = dyn_cast<TextComment>(*I)) {
if (!TC->isWhitespace())
return false;
}
}
return true;
}
const char *ParamCommandComment::getDirectionAsString(PassDirection D) {
switch (D) {
case ParamCommandComment::In:
return "[in]";
case ParamCommandComment::Out:
return "[out]";
case ParamCommandComment::InOut:
return "[in,out]";
}
llvm_unreachable("unknown PassDirection");
}
} // end namespace comments
} // end namespace clang

View File

@ -121,6 +121,8 @@ void CommentDumper::visitHTMLOpenTagComment(const HTMLOpenTagComment *C) {
OS << " \"" << Attr.Name << "=\"" << Attr.Value << "\"";
}
}
if (C->isSelfClosing())
OS << " SelfClosing";
}
void CommentDumper::visitHTMLCloseTagComment(const HTMLCloseTagComment *C) {
@ -142,17 +144,7 @@ void CommentDumper::visitBlockCommandComment(const BlockCommandComment *C) {
void CommentDumper::visitParamCommandComment(const ParamCommandComment *C) {
dumpComment(C);
switch (C->getDirection()) {
case ParamCommandComment::In:
OS << " [in]";
break;
case ParamCommandComment::Out:
OS << " [out]";
break;
case ParamCommandComment::InOut:
OS << " [in,out]";
break;
}
OS << " " << ParamCommandComment::getDirectionAsString(C->getDirection());
if (C->isDirectionExplicit())
OS << " explicitly";

View File

@ -509,7 +509,7 @@ void Lexer::setupAndLexHTMLOpenTag(Token &T) {
const char C = *BufferPtr;
if (BufferPtr != CommentEnd &&
(C == '>' || isHTMLIdentifierStartingCharacter(C)))
(C == '>' || C == '/' || isHTMLIdentifierStartingCharacter(C)))
State = LS_HTMLOpenTag;
}
@ -546,6 +546,18 @@ void Lexer::lexHTMLOpenTag(Token &T) {
formTokenWithChars(T, TokenPtr, tok::html_greater);
State = LS_Normal;
return;
case '/':
TokenPtr++;
if (TokenPtr != CommentEnd && *TokenPtr == '>') {
TokenPtr++;
formTokenWithChars(T, TokenPtr, tok::html_slash_greater);
} else {
StringRef Text(BufferPtr, TokenPtr - BufferPtr);
formTokenWithChars(T, TokenPtr, tok::text);
T.setText(Text);
}
State = LS_Normal;
return;
}
}

View File

@ -9,13 +9,16 @@
#include "clang/AST/CommentParser.h"
#include "clang/AST/CommentSema.h"
#include "clang/AST/CommentDiagnostic.h"
#include "clang/Basic/SourceManager.h"
#include "llvm/Support/ErrorHandling.h"
namespace clang {
namespace comments {
Parser::Parser(Lexer &L, Sema &S, llvm::BumpPtrAllocator &Allocator):
L(L), S(S), Allocator(Allocator) {
Parser::Parser(Lexer &L, Sema &S, llvm::BumpPtrAllocator &Allocator,
const SourceManager &SourceMgr, DiagnosticsEngine &Diags):
L(L), S(S), Allocator(Allocator), SourceMgr(SourceMgr), Diags(Diags) {
consumeToken();
}
@ -26,18 +29,16 @@ ParamCommandComment *Parser::parseParamCommandArgs(
// Check if argument looks like direction specification: [dir]
// e.g., [in], [out], [in,out]
if (Retokenizer.lexDelimitedSeq(Arg, '[', ']'))
PC = S.actOnParamCommandArg(PC,
Arg.getLocation(),
Arg.getEndLocation(),
Arg.getText(),
/* IsDirection = */ true);
PC = S.actOnParamCommandDirectionArg(PC,
Arg.getLocation(),
Arg.getEndLocation(),
Arg.getText());
if (Retokenizer.lexWord(Arg))
PC = S.actOnParamCommandArg(PC,
Arg.getLocation(),
Arg.getEndLocation(),
Arg.getText(),
/* IsDirection = */ false);
PC = S.actOnParamCommandParamNameArg(PC,
Arg.getLocation(),
Arg.getEndLocation(),
Arg.getText());
return PC;
}
@ -84,7 +85,6 @@ BlockCommandComment *Parser::parseBlockCommand() {
if (Tok.is(tok::command) && S.isBlockCommand(Tok.getCommandName())) {
// Block command ahead. We can't nest block commands, so pretend that this
// command has an empty argument.
// TODO: Diag() Warn empty arg to block command
ParagraphComment *PC = S.actOnParagraphComment(
ArrayRef<InlineContentComment *>());
return S.actOnBlockCommandFinish(BC, PC);
@ -164,7 +164,8 @@ HTMLOpenTagComment *Parser::parseHTMLOpenTag() {
SmallVector<HTMLOpenTagComment::Attribute, 2> Attrs;
while (true) {
if (Tok.is(tok::html_ident)) {
switch (Tok.getKind()) {
case tok::html_ident: {
Token Ident = Tok;
consumeToken();
if (Tok.isNot(tok::html_equals)) {
@ -175,9 +176,14 @@ HTMLOpenTagComment *Parser::parseHTMLOpenTag() {
Token Equals = Tok;
consumeToken();
if (Tok.isNot(tok::html_quoted_string)) {
// TODO: Diag() expected quoted string
Diag(Tok.getLocation(),
diag::warn_doc_html_open_tag_expected_quoted_string)
<< SourceRange(Equals.getLocation());
Attrs.push_back(HTMLOpenTagComment::Attribute(Ident.getLocation(),
Ident.getHTMLIdent()));
while (Tok.is(tok::html_equals) ||
Tok.is(tok::html_quoted_string))
consumeToken();
continue;
}
Attrs.push_back(HTMLOpenTagComment::Attribute(
@ -189,24 +195,66 @@ HTMLOpenTagComment *Parser::parseHTMLOpenTag() {
Tok.getHTMLQuotedString()));
consumeToken();
continue;
} else if (Tok.is(tok::html_greater)) {
}
case tok::html_greater:
HOT = S.actOnHTMLOpenTagFinish(HOT,
copyArray(llvm::makeArrayRef(Attrs)),
Tok.getLocation());
Tok.getLocation(),
/* IsSelfClosing = */ false);
consumeToken();
return HOT;
} else if (Tok.is(tok::html_equals) ||
Tok.is(tok::html_quoted_string)) {
// TODO: Diag() Err expected ident
case tok::html_slash_greater:
HOT = S.actOnHTMLOpenTagFinish(HOT,
copyArray(llvm::makeArrayRef(Attrs)),
Tok.getLocation(),
/* IsSelfClosing = */ true);
consumeToken();
return HOT;
case tok::html_equals:
case tok::html_quoted_string:
Diag(Tok.getLocation(),
diag::warn_doc_html_open_tag_expected_ident_or_greater);
while (Tok.is(tok::html_equals) ||
Tok.is(tok::html_quoted_string))
consumeToken();
} else {
// Not a token from HTML open tag. Thus HTML tag prematurely ended.
// TODO: Diag() Err HTML tag prematurely ended
if (Tok.is(tok::html_ident) ||
Tok.is(tok::html_greater) ||
Tok.is(tok::html_slash_greater))
continue;
return S.actOnHTMLOpenTagFinish(HOT,
copyArray(llvm::makeArrayRef(Attrs)),
SourceLocation());
SourceLocation(),
/* IsSelfClosing = */ false);
default:
// Not a token from an HTML open tag. Thus HTML tag prematurely ended.
HOT = S.actOnHTMLOpenTagFinish(HOT,
copyArray(llvm::makeArrayRef(Attrs)),
SourceLocation(),
/* IsSelfClosing = */ false);
bool StartLineInvalid;
const unsigned StartLine = SourceMgr.getPresumedLineNumber(
HOT->getLocation(),
&StartLineInvalid);
bool EndLineInvalid;
const unsigned EndLine = SourceMgr.getPresumedLineNumber(
Tok.getLocation(),
&EndLineInvalid);
if (StartLineInvalid || EndLineInvalid || StartLine == EndLine)
Diag(Tok.getLocation(),
diag::warn_doc_html_open_tag_expected_ident_or_greater)
<< HOT->getSourceRange();
else {
Diag(Tok.getLocation(),
diag::warn_doc_html_open_tag_expected_ident_or_greater);
Diag(HOT->getLocation(), diag::note_doc_html_tag_started_here)
<< HOT->getSourceRange();
}
return HOT;
}
}
}
@ -289,6 +337,7 @@ BlockContentComment *Parser::parseParagraphOrBlockCommand() {
case tok::html_equals:
case tok::html_quoted_string:
case tok::html_greater:
case tok::html_slash_greater:
llvm_unreachable("should not see this token");
}
break;
@ -388,6 +437,7 @@ BlockContentComment *Parser::parseBlockContent() {
case tok::html_equals:
case tok::html_quoted_string:
case tok::html_greater:
case tok::html_slash_greater:
llvm_unreachable("should not see this token");
}
llvm_unreachable("bogus token kind");

View File

@ -8,13 +8,22 @@
//===----------------------------------------------------------------------===//
#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) :
Allocator(Allocator) {
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(
@ -39,83 +48,153 @@ BlockCommandComment *Sema::actOnBlockCommandFinish(
BlockCommandComment *Command,
ParagraphComment *Paragraph) {
Command->setParagraph(Paragraph);
checkBlockCommandEmptyParagraph(Command);
return Command;
}
ParamCommandComment *Sema::actOnParamCommandStart(SourceLocation LocBegin,
SourceLocation LocEnd,
StringRef Name) {
return new (Allocator) ParamCommandComment(LocBegin, LocEnd, 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::actOnParamCommandArg(ParamCommandComment *Command,
ParamCommandComment *Sema::actOnParamCommandDirectionArg(
ParamCommandComment *Command,
SourceLocation ArgLocBegin,
SourceLocation ArgLocEnd,
StringRef Arg,
bool IsDirection) {
if (IsDirection) {
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());
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;
}
// Diag() unrecognized parameter passing direction, valid directions are ...
// if (RemovingWhitespaceHelped) FixIt
}
Command->setDirection(Direction, /* Explicit = */ true);
} else {
if (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 (...) Diag() unrecognized parameter name
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 {
// Diag() \\param command requires at most 2 arguments
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;
}
@ -196,22 +275,78 @@ VerbatimLineComment *Sema::actOnVerbatimLine(SourceLocation LocBegin,
HTMLOpenTagComment *Sema::actOnHTMLOpenTagStart(SourceLocation LocBegin,
StringRef TagName) {
return new (Allocator) HTMLOpenTagComment(LocBegin, TagName);
HTMLOpenTagComment *HOT =
new (Allocator) HTMLOpenTagComment(LocBegin, TagName);
return HOT;
}
HTMLOpenTagComment *Sema::actOnHTMLOpenTagFinish(
HTMLOpenTagComment *Tag,
ArrayRef<HTMLOpenTagComment::Attribute> Attrs,
SourceLocation GreaterLoc) {
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) {
return new (Allocator) HTMLCloseTagComment(LocBegin, LocEnd, 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(
@ -219,6 +354,61 @@ FullComment *Sema::actOnFullComment(
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)
@ -259,7 +449,9 @@ bool Sema::isInlineCommand(StringRef Name) {
bool Sema::HTMLOpenTagNeedsClosing(StringRef Name) {
return llvm::StringSwitch<bool>(Name)
.Case("br", true)
.Case("br", false)
.Case("hr", false)
.Case("li", false)
.Default(true);
}

View File

@ -61,7 +61,7 @@ bool mergedCommentIsTrailingComment(StringRef Comment) {
RawComment::RawComment(const SourceManager &SourceMgr, SourceRange SR,
bool Merged) :
Range(SR), RawTextValid(false), BriefTextValid(false),
IsAlmostTrailingComment(false),
IsAttached(false), IsAlmostTrailingComment(false),
BeginLineValid(false), EndLineValid(false) {
// Extract raw comment text, if possible.
if (SR.getBegin() == SR.getEnd() || getRawText(SourceMgr).empty()) {

View File

@ -79,6 +79,7 @@ static const StaticDiagInfoRec StaticDiagInfo[] = {
#include "clang/Basic/DiagnosticLexKinds.inc"
#include "clang/Basic/DiagnosticParseKinds.inc"
#include "clang/Basic/DiagnosticASTKinds.inc"
#include "clang/Basic/DiagnosticCommentKinds.inc"
#include "clang/Basic/DiagnosticSemaKinds.inc"
#include "clang/Basic/DiagnosticAnalysisKinds.inc"
#undef DIAG

View File

@ -46,6 +46,7 @@ add_dependencies(clangSema
ClangARMNeon
ClangAttrClasses
ClangAttrList
ClangDiagnosticComment
ClangDiagnosticSema
ClangCommentNodes
ClangDeclNodes

View File

@ -21,6 +21,7 @@
#include "clang/AST/ASTConsumer.h"
#include "clang/AST/ASTContext.h"
#include "clang/AST/CXXInheritance.h"
#include "clang/AST/CommentDiagnostic.h"
#include "clang/AST/DeclCXX.h"
#include "clang/AST/DeclObjC.h"
#include "clang/AST/DeclTemplate.h"
@ -2703,6 +2704,8 @@ Decl *Sema::ParsedFreeStandingDeclSpec(Scope *S, AccessSpecifier AS,
}
}
ActOnDocumentableDecl(TagD);
return TagD;
}
@ -7104,9 +7107,55 @@ Sema::BuildDeclaratorGroup(Decl **Group, unsigned NumDecls,
}
}
ActOnDocumentableDecls(Group, NumDecls);
return DeclGroupPtrTy::make(DeclGroupRef::Create(Context, Group, NumDecls));
}
void Sema::ActOnDocumentableDecl(Decl *D) {
ActOnDocumentableDecls(&D, 1);
}
void Sema::ActOnDocumentableDecls(Decl **Group, unsigned NumDecls) {
// Don't parse the comment if Doxygen diagnostics are ignored.
if (NumDecls == 0 || !Group[0])
return;
if (Diags.getDiagnosticLevel(diag::warn_doc_param_not_found,
Group[0]->getLocation())
== DiagnosticsEngine::Ignored)
return;
if (NumDecls >= 2) {
// This is a decl group. Normally it will contain only declarations
// procuded from declarator list. But in case we have any definitions or
// additional declaration references:
// 'typedef struct S {} S;'
// 'typedef struct S *S;'
// 'struct S *pS;'
// FinalizeDeclaratorGroup adds these as separate declarations.
Decl *MaybeTagDecl = Group[0];
if (MaybeTagDecl && isa<TagDecl>(MaybeTagDecl)) {
Group++;
NumDecls--;
}
}
// See if there are any new comments that are not attached to a decl.
ArrayRef<RawComment *> Comments = Context.getRawCommentList().getComments();
if (!Comments.empty() &&
!Comments.back()->isAttached()) {
// There is at least one comment that not attached to a decl.
// Maybe it should be attached to one of these decls?
//
// Note that this way we pick up not only comments that precede the
// declaration, but also comments that *follow* the declaration -- thanks to
// the lookahead in the lexer: we've consumed the semicolon and looked
// ahead through comments.
for (unsigned i = 0; i != NumDecls; ++i)
Context.getCommentForDecl(Group[i]);
}
}
/// ActOnParamDeclarator - Called from Parser::ParseFunctionDeclarator()
/// to introduce parameters into function prototype scope.
@ -8868,6 +8917,8 @@ void Sema::ActOnTagStartDefinition(Scope *S, Decl *TagD) {
// Enter the tag context.
PushDeclContext(S, Tag);
ActOnDocumentableDecl(TagD);
}
Decl *Sema::ActOnObjCContainerStartDefinition(Decl *IDecl) {
@ -8877,6 +8928,7 @@ Decl *Sema::ActOnObjCContainerStartDefinition(Decl *IDecl) {
assert(getContainingDC(OCD) == CurContext &&
"The next DeclContext should be lexically contained in the current one.");
CurContext = OCD;
ActOnDocumentableDecl(IDecl);
return IDecl;
}
@ -10339,6 +10391,8 @@ Decl *Sema::ActOnEnumConstant(Scope *S, Decl *theEnumDecl, Decl *lastEnumConst,
PushOnScopeChains(New, S);
}
ActOnDocumentableDecl(New);
return New;
}

View File

@ -5422,6 +5422,8 @@ Decl *Sema::ActOnStartNamespaceDef(Scope *NamespcScope,
}
}
ActOnDocumentableDecl(Namespc);
// Although we could have an invalid decl (i.e. the namespace name is a
// redefinition), push it as current DeclContext and try to continue parsing.
// FIXME: We should be able to push Namespc here, so that the each DeclContext

View File

@ -2952,7 +2952,9 @@ Decl *Sema::ActOnMethodDeclaration(
if (InferRelatedResultType)
ObjCMethod->SetRelatedResultType();
}
ActOnDocumentableDecl(ObjCMethod);
return ObjCMethod;
}

View File

@ -1,14 +0,0 @@
// RUN: %clang_cc1 -fsyntax-only -Wdoxygen -verify %s
// RUN: %clang_cc1 -fsyntax-only -Wdoxygen -fdiagnostics-parseable-fixits %s 2>&1 | FileCheck %s
// RUN: cp %s %t
// RUN: %clang_cc1 -fsyntax-only -Wdoxygen -fixit %t
// RUN: %clang_cc1 -fsyntax-only -Wdoxygen -Werror %t
struct a {
int x; //< comment // expected-warning {{not a Doxygen trailing comment}}
int y; /*< comment */ // expected-warning {{not a Doxygen trailing comment}}
};
// CHECK: fix-it:"{{.*}}":{8:10-8:13}:"///<"
// CHECK: fix-it:"{{.*}}":{9:10-9:13}:"/**<"

View File

@ -0,0 +1,14 @@
// RUN: %clang_cc1 -fsyntax-only -Wdocumentation -verify %s
// RUN: %clang_cc1 -fsyntax-only -Wdocumentation -fdiagnostics-parseable-fixits %s 2>&1 | FileCheck %s
// RUN: cp %s %t
// RUN: %clang_cc1 -fsyntax-only -Wdocumentation -fixit %t
// RUN: %clang_cc1 -fsyntax-only -Wdocumentation -Werror %t
struct a {
int x; //< comment // expected-warning {{not a Doxygen trailing comment}}
int y; /*< comment */ // expected-warning {{not a Doxygen trailing comment}}
};
// CHECK: fix-it:"{{.*}}":{8:10-8:13}:"///<"
// CHECK: fix-it:"{{.*}}":{9:10-9:13}:"/**<"

View File

@ -0,0 +1,12 @@
// RUN: %clang_cc1 -fsyntax-only -Wdocumentation -verify %s
// RUN: %clang_cc1 -fsyntax-only -Wdocumentation -fdiagnostics-parseable-fixits %s 2>&1 | FileCheck %s
/// \param ZZZZZZZZZZ Blah blah. expected-warning {{parameter 'ZZZZZZZZZZ' not found in the function declaration}} expected-note {{did you mean 'a'?}}
int test1(int a);
/// \param aab Blah blah. expected-warning {{parameter 'aab' not found in the function declaration}} expected-note {{did you mean 'aaa'?}}
int test2(int aaa, int bbb);
// CHECK: fix-it:"{{.*}}":{4:12-4:22}:"a"
// CHECK: fix-it:"{{.*}}":{7:12-7:15}:"aaa"

View File

@ -0,0 +1,272 @@
// RUN: %clang_cc1 -fsyntax-only -Wdocumentation -Wdocumentation-pedantic -verify %s
// expected-warning@+1 {{expected quoted string after equals sign}}
/// <a href=>
int test_html1(int);
// expected-warning@+1 {{expected quoted string after equals sign}}
/// <a href==>
int test_html2(int);
// expected-warning@+2 {{expected quoted string after equals sign}}
// expected-warning@+1 {{HTML opening tag prematurely ended, expected attribute name or '>'}}
/// <a href= blah
int test_html3(int);
// expected-warning@+1 {{HTML opening tag prematurely ended, expected attribute name or '>'}}
/// <a =>
int test_html4(int);
// expected-warning@+1 {{HTML opening tag prematurely ended, expected attribute name or '>'}}
/// <a "aaa">
int test_html5(int);
// expected-warning@+1 {{HTML opening tag prematurely ended, expected attribute name or '>'}}
/// <a a="b" =>
int test_html6(int);
// expected-warning@+1 {{HTML opening tag prematurely ended, expected attribute name or '>'}}
/// <a a="b" "aaa">
int test_html7(int);
// expected-warning@+1 {{HTML opening tag prematurely ended, expected attribute name or '>'}}
/// <a a="b" =
int test_html8(int);
// expected-warning@+2 {{HTML opening tag prematurely ended, expected attribute name or '>'}} expected-note@+1 {{HTML tag started here}}
/** Aaa bbb<ccc ddd eee
* fff ggg.
*/
int test_html9(int);
// expected-warning@+1 {{HTML opening tag prematurely ended, expected attribute name or '>'}}
/** Aaa bbb<ccc ddd eee 42%
* fff ggg.
*/
int test_html10(int);
/// <blockquote>Meow</blockquote>
int test_html_nesting1(int);
/// <b><i>Meow</i></b>
int test_html_nesting2(int);
/// <p>Aaa<br>
/// Bbb</p>
int test_html_nesting3(int);
/// <p>Aaa<br />
/// Bbb</p>
int test_html_nesting4(int);
// expected-warning@+1 {{HTML closing tag does not match any opening tag}}
/// <b><i>Meow</a>
int test_html_nesting5(int);
// expected-warning@+2 {{HTML opening tag 'i' closed by 'b'}}
// expected-warning@+1 {{HTML closing tag does not match any opening tag}}
/// <b><i>Meow</b></b>
int test_html_nesting6(int);
// expected-warning@+2 {{HTML opening tag 'i' closed by 'b'}}
// expected-warning@+1 {{HTML closing tag does not match any opening tag}}
/// <b><i>Meow</b></i>
int test_html_nesting7(int);
// expected-warning@+1 {{empty paragraph passed to '\brief' command}}
/// \brief\brief Aaa
int test_block_command1(int);
// expected-warning@+1 {{empty paragraph passed to '\brief' command}}
/// \brief \brief Aaa
int test_block_command2(int);
// expected-warning@+1 {{empty paragraph passed to '\brief' command}}
/// \brief
/// \brief Aaa
int test_block_command3(int);
// expected-warning@+1 {{empty paragraph passed to '\brief' command}}
/// \brief
///
/// \brief Aaa
int test_block_command4(int);
// There is trailing whitespace on one of the following lines, don't remove it!
// expected-warning@+1 {{empty paragraph passed to '\brief' command}}
/// \brief
///
/// \brief Aaa
int test_block_command5(int);
// expected-warning@+1 {{'\param' command used in a comment that is not attached to a function declaration}}
/// \param a Blah blah.
int test_param1;
// expected-warning@+1 {{empty paragraph passed to '\param' command}}
/// \param
/// \param a Blah blah.
int test_param2(int a);
// expected-warning@+1 {{empty paragraph passed to '\param' command}}
/// \param a
int test_param3(int a);
/// \param a Blah blah.
int test_param4(int a);
/// \param [in] a Blah blah.
int test_param5(int a);
/// \param [out] a Blah blah.
int test_param6(int a);
/// \param [in,out] a Blah blah.
int test_param7(int a);
// expected-warning@+1 {{whitespace is not allowed in parameter passing direction}}
/// \param [ in ] a Blah blah.
int test_param8(int a);
// expected-warning@+1 {{whitespace is not allowed in parameter passing direction}}
/// \param [in, out] a Blah blah.
int test_param9(int a);
// expected-warning@+1 {{unrecognized parameter passing direction, valid directions are '[in]', '[out]' and '[in,out]'}}
/// \param [ junk] a Blah blah.
int test_param10(int a);
// expected-warning@+1 {{parameter 'A' not found in the function declaration}} expected-note@+1 {{did you mean 'a'?}}
/// \param A Blah blah.
int test_param11(int a);
// expected-warning@+1 {{parameter 'aab' not found in the function declaration}} expected-note@+1 {{did you mean 'aaa'?}}
/// \param aab Blah blah.
int test_param12(int aaa, int bbb);
// expected-warning@+1 {{parameter 'aab' not found in the function declaration}}
/// \param aab Blah blah.
int test_param13(int bbb, int ccc);
class C {
// expected-warning@+1 {{parameter 'aaa' not found in the function declaration}}
/// \param aaa Blah blah.
C(int bbb, int ccc);
// expected-warning@+1 {{parameter 'aaa' not found in the function declaration}}
/// \param aaa Blah blah.
int test_param14(int bbb, int ccc);
};
// expected-warning@+1 {{empty paragraph passed to '\brief' command}}
int test1; ///< \brief\brief Aaa
// expected-warning@+2 {{empty paragraph passed to '\brief' command}}
// expected-warning@+2 {{empty paragraph passed to '\brief' command}}
int test2, ///< \brief\brief Aaa
test3; ///< \brief\brief Aaa
// expected-warning@+1 {{empty paragraph passed to '\brief' command}}
int test4; ///< \brief
///< \brief Aaa
// Check that we attach the comment to the declaration during parsing in the
// following cases. The test is based on the fact that we don't parse
// documentation comments that are not attached to anything.
// expected-warning@+1 {{empty paragraph passed to '\brief' command}}
/// \brief\brief Aaa
int test_attach1;
// expected-warning@+1 {{empty paragraph passed to '\brief' command}}
/// \brief\brief Aaa
int test_attach2(int);
// expected-warning@+1 {{empty paragraph passed to '\brief' command}}
/// \brief\brief Aaa
struct test_attach3 {
// expected-warning@+1 {{empty paragraph passed to '\brief' command}}
/// \brief\brief Aaa
int test_attach4;
// expected-warning@+1 {{empty paragraph passed to '\brief' command}}
int test_attach5; ///< \brief\brief Aaa
// expected-warning@+1 {{empty paragraph passed to '\brief' command}}
/// \brief\brief Aaa
int test_attach6(int);
};
// expected-warning@+1 {{empty paragraph passed to '\brief' command}}
/// \brief\brief Aaa
class test_attach7 {
// expected-warning@+1 {{empty paragraph passed to '\brief' command}}
/// \brief\brief Aaa
int test_attach8;
// expected-warning@+1 {{empty paragraph passed to '\brief' command}}
int test_attach9; ///< \brief\brief Aaa
// expected-warning@+1 {{empty paragraph passed to '\brief' command}}
/// \brief\brief Aaa
int test_attach10(int);
};
// expected-warning@+1 {{empty paragraph passed to '\brief' command}}
/// \brief\brief Aaa
enum test_attach9 {
// expected-warning@+1 {{empty paragraph passed to '\brief' command}}
/// \brief\brief Aaa
test_attach10,
// expected-warning@+1 {{empty paragraph passed to '\brief' command}}
test_attach11 ///< \brief\brief Aaa
};
// expected-warning@+1 {{empty paragraph passed to '\brief' command}}
/// \brief\brief Aaa
struct test_noattach12 *test_attach13;
// expected-warning@+1 {{empty paragraph passed to '\brief' command}}
/// \brief\brief Aaa
typedef struct test_noattach14 *test_attach15;
// expected-warning@+1 {{empty paragraph passed to '\brief' command}}
/// \brief\brief Aaa
typedef struct test_attach16 { int a; } test_attach17;
struct S { int a; };
// expected-warning@+1 {{empty paragraph passed to '\brief' command}}
/// \brief\brief Aaa
struct S *test_attach18;
// expected-warning@+1 {{empty paragraph passed to '\brief' command}}
/// \brief\brief Aaa
typedef struct S *test_attach19;
// expected-warning@+1 {{empty paragraph passed to '\brief' command}}
/// \brief\brief Aaa
struct test_attach20;
// expected-warning@+1 {{empty paragraph passed to '\brief' command}}
/// \brief\brief Aaa
typedef struct test_attach21 {
// expected-warning@+1 {{empty paragraph passed to '\brief' command}}
/// \brief\brief Aaa
int test_attach22;
} test_attach23;
// expected-warning@+1 {{empty paragraph passed to '\brief' command}}
/// \brief\brief Aaa
namespace test_attach24 {
// expected-warning@+1 {{empty paragraph passed to '\brief' command}}
/// \brief\brief Aaa
namespace test_attach25 {
}
}

View File

@ -0,0 +1,24 @@
// RUN: %clang_cc1 -fsyntax-only -Wdocumentation -Wdocumentation-pedantic -verify %s
@class NSString;
// expected-warning@+2 {{empty paragraph passed to '\brief' command}}
/**
* \brief\brief Aaa
*/
@interface A
// expected-warning@+2 {{empty paragraph passed to '\brief' command}}
/**
* \brief\brief Aaa
* \param aaa Aaa
* \param bbb Bbb
*/
+ (NSString *)test1:(NSString *)aaa suffix:(NSString *)bbb;
// expected-warning@+2 {{parameter 'aab' not found in the function declaration}} expected-note@+2 {{did you mean 'aaa'?}}
/**
* \param aab Aaa
*/
+ (NSString *)test2:(NSString *)aaa;
@end

View File

@ -39,6 +39,7 @@ static const DiagnosticRecord BuiltinDiagnosticsByID[] = {
#include "clang/Basic/DiagnosticLexKinds.inc"
#include "clang/Basic/DiagnosticParseKinds.inc"
#include "clang/Basic/DiagnosticASTKinds.inc"
#include "clang/Basic/DiagnosticCommentKinds.inc"
#include "clang/Basic/DiagnosticSemaKinds.inc"
#include "clang/Basic/DiagnosticAnalysisKinds.inc"
#undef DIAG

View File

@ -1142,6 +1142,60 @@ TEST_F(CommentLexerTest, HTML14) {
}
TEST_F(CommentLexerTest, HTML15) {
const char *Sources[] = {
"// <tag/>",
"// <tag />"
};
for (size_t i = 0, e = array_lengthof(Sources); i != e; i++) {
std::vector<Token> Toks;
lexString(Sources[i], Toks);
ASSERT_EQ(4U, Toks.size());
ASSERT_EQ(tok::text, Toks[0].getKind());
ASSERT_EQ(StringRef(" "), Toks[0].getText());
ASSERT_EQ(tok::html_tag_open, Toks[1].getKind());
ASSERT_EQ(StringRef("tag"), Toks[1].getHTMLTagOpenName());
ASSERT_EQ(tok::html_slash_greater, Toks[2].getKind());
ASSERT_EQ(tok::newline, Toks[3].getKind());
}
}
TEST_F(CommentLexerTest, HTML16) {
const char *Sources[] = {
"// <tag/ Aaa",
"// <tag / Aaa"
};
for (size_t i = 0, e = array_lengthof(Sources); i != e; i++) {
std::vector<Token> Toks;
lexString(Sources[i], Toks);
ASSERT_EQ(5U, Toks.size());
ASSERT_EQ(tok::text, Toks[0].getKind());
ASSERT_EQ(StringRef(" "), Toks[0].getText());
ASSERT_EQ(tok::html_tag_open, Toks[1].getKind());
ASSERT_EQ(StringRef("tag"), Toks[1].getHTMLTagOpenName());
ASSERT_EQ(tok::text, Toks[2].getKind());
ASSERT_EQ(StringRef("/"), Toks[2].getText());
ASSERT_EQ(tok::text, Toks[3].getKind());
ASSERT_EQ(StringRef(" Aaa"), Toks[3].getText());
ASSERT_EQ(tok::newline, Toks[4].getKind());
}
}
TEST_F(CommentLexerTest, HTML17) {
const char *Source = "// </";
std::vector<Token> Toks;
@ -1159,8 +1213,7 @@ TEST_F(CommentLexerTest, HTML15) {
ASSERT_EQ(tok::newline, Toks[2].getKind());
}
TEST_F(CommentLexerTest, HTML16) {
TEST_F(CommentLexerTest, HTML18) {
const char *Source = "// </@";
std::vector<Token> Toks;
@ -1181,7 +1234,7 @@ TEST_F(CommentLexerTest, HTML16) {
ASSERT_EQ(tok::newline, Toks[3].getKind());
}
TEST_F(CommentLexerTest, HTML17) {
TEST_F(CommentLexerTest, HTML19) {
const char *Source = "// </tag";
std::vector<Token> Toks;
@ -1199,7 +1252,7 @@ TEST_F(CommentLexerTest, HTML17) {
ASSERT_EQ(tok::newline, Toks[2].getKind());
}
TEST_F(CommentLexerTest, HTML18) {
TEST_F(CommentLexerTest, HTML20) {
const char *Sources[] = {
"// </tag>",
"// </ tag>",

View File

@ -57,8 +57,8 @@ FullComment *CommentParserTest::parseString(const char *Source) {
comments::Lexer L(Begin, CommentOptions(),
Source, Source + strlen(Source));
comments::Sema S(Allocator);
comments::Parser P(L, S, Allocator);
comments::Sema S(Allocator, SourceMgr, Diags);
comments::Parser P(L, S, Allocator, SourceMgr, Diags);
comments::FullComment *FC = P.parseFullComment();
if (DEBUG) {
@ -292,6 +292,25 @@ struct NoArgs {};
return ::testing::AssertionSuccess();
}
struct SelfClosing {};
::testing::AssertionResult HasHTMLOpenTagAt(const Comment *C,
size_t Idx,
HTMLOpenTagComment *&HOT,
StringRef TagName,
SelfClosing) {
::testing::AssertionResult AR = HasHTMLOpenTagAt(C, Idx, HOT, TagName);
if (!AR)
return AR;
if (!HOT->isSelfClosing())
return ::testing::AssertionFailure()
<< "HTMLOpenTagComment is not self-closing";
return ::testing::AssertionSuccess();
}
struct NoAttrs {};
::testing::AssertionResult HasHTMLOpenTagAt(const Comment *C,
@ -303,6 +322,10 @@ struct NoAttrs {};
if (!AR)
return AR;
if (HOT->isSelfClosing())
return ::testing::AssertionFailure()
<< "HTMLOpenTagComment is self-closing";
if (HOT->getAttrCount() != 0)
return ::testing::AssertionFailure()
<< "HTMLOpenTagComment has " << HOT->getAttrCount() << " attr(s), "
@ -321,6 +344,10 @@ struct NoAttrs {};
if (!AR)
return AR;
if (HOT->isSelfClosing())
return ::testing::AssertionFailure()
<< "HTMLOpenTagComment is self-closing";
if (HOT->getAttrCount() != 1)
return ::testing::AssertionFailure()
<< "HTMLOpenTagComment has " << HOT->getAttrCount() << " attr(s), "
@ -836,6 +863,28 @@ TEST_F(CommentParserTest, HTML1) {
}
TEST_F(CommentParserTest, HTML2) {
const char *Sources[] = {
"// <br/>",
"// <br />"
};
for (size_t i = 0, e = array_lengthof(Sources); i != e; i++) {
FullComment *FC = parseString(Sources[i]);
ASSERT_TRUE(HasChildCount(FC, 1));
{
ParagraphComment *PC;
HTMLOpenTagComment *HOT;
ASSERT_TRUE(GetChildAt(FC, 0, PC));
ASSERT_TRUE(HasChildCount(PC, 2));
ASSERT_TRUE(HasTextAt(PC, 0, " "));
ASSERT_TRUE(HasHTMLOpenTagAt(PC, 1, HOT, "br", SelfClosing()));
}
}
}
TEST_F(CommentParserTest, HTML3) {
const char *Sources[] = {
"// <a href",
"// <a href ",
@ -859,7 +908,7 @@ TEST_F(CommentParserTest, HTML2) {
}
}
TEST_F(CommentParserTest, HTML3) {
TEST_F(CommentParserTest, HTML4) {
const char *Sources[] = {
"// <a href=\"bbb\"",
"// <a href=\"bbb\">",
@ -881,7 +930,7 @@ TEST_F(CommentParserTest, HTML3) {
}
}
TEST_F(CommentParserTest, HTML4) {
TEST_F(CommentParserTest, HTML5) {
const char *Sources[] = {
"// </a",
"// </a>",
@ -904,7 +953,7 @@ TEST_F(CommentParserTest, HTML4) {
}
}
TEST_F(CommentParserTest, HTML5) {
TEST_F(CommentParserTest, HTML6) {
const char *Source =
"// <pre>\n"
"// Aaa\n"

View File

@ -10,6 +10,6 @@
CLANG_LEVEL = ../..
TESTNAME = AST
LINK_COMPONENTS := support mc
USEDLIBS = clangAST.a clangBasic.a
USEDLIBS = clangAST.a clangLex.a clangBasic.a
include $(CLANG_LEVEL)/unittests/Makefile