зеркало из https://github.com/microsoft/clang-1.git
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). git-svn-id: https://llvm.org/svn/llvm-project/cfe/trunk@160078 91177308-0d34-0410-b5e6-96231b3b80d8
This commit is contained in:
Родитель
ff398965a5
Коммит
a5ef44ff5d
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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)
|
||||
{ }
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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
|
|
@ -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">;
|
||||
|
||||
|
|
|
@ -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
|
||||
};
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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 \
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -64,6 +64,7 @@ add_dependencies(clangAST
|
|||
ClangAttrList
|
||||
ClangAttrImpl
|
||||
ClangDiagnosticAST
|
||||
ClangDiagnosticComment
|
||||
ClangCommentNodes
|
||||
ClangDeclNodes
|
||||
ClangStmtNodes
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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";
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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");
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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()) {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -46,6 +46,7 @@ add_dependencies(clangSema
|
|||
ClangARMNeon
|
||||
ClangAttrClasses
|
||||
ClangAttrList
|
||||
ClangDiagnosticComment
|
||||
ClangDiagnosticSema
|
||||
ClangCommentNodes
|
||||
ClangDeclNodes
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -2952,7 +2952,9 @@ Decl *Sema::ActOnMethodDeclaration(
|
|||
if (InferRelatedResultType)
|
||||
ObjCMethod->SetRelatedResultType();
|
||||
}
|
||||
|
||||
|
||||
ActOnDocumentableDecl(ObjCMethod);
|
||||
|
||||
return ObjCMethod;
|
||||
}
|
||||
|
||||
|
|
|
@ -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}:"/**<"
|
||||
|
|
@ -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}:"/**<"
|
||||
|
|
@ -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"
|
||||
|
|
@ -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 {
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
|
@ -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>",
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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
|
||||
|
|
Загрузка…
Ссылка в новой задаче