From: Dmitri Gribenko Date: Wed, 11 Jul 2012 21:38:39 +0000 (+0000) Subject: Enable comment parsing and semantic analysis to emit diagnostics. A few X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=a5ef44ff5d93a3be6ca67782828157a71894cf0c;p=clang 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,
). 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 --- diff --git a/include/clang/AST/ASTContext.h b/include/clang/AST/ASTContext.h index 4dc0a447bd..5283d6dadc 100644 --- a/include/clang/AST/ASTContext.h +++ b/include/clang/AST/ASTContext.h @@ -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); } diff --git a/include/clang/AST/Comment.h b/include/clang/AST/Comment.h index e631f38b05..16719dfd23 100644 --- a/include/clang/AST/Comment.h +++ b/include/clang/AST/Comment.h @@ -50,6 +50,16 @@ protected: }; enum { NumInlineContentCommentBitfields = 9 }; + class HTMLOpenTagCommentBitfields { + friend class HTMLOpenTagComment; + + unsigned : NumInlineContentCommentBitfields; + + /// True if this tag is self-closing (e. g.,
). This is based on tag + /// spelling in comment (plain
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(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 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(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. diff --git a/include/clang/AST/CommentDiagnostic.h b/include/clang/AST/CommentDiagnostic.h new file mode 100644 index 0000000000..6e89410579 --- /dev/null +++ b/include/clang/AST/CommentDiagnostic.h @@ -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 + diff --git a/include/clang/AST/CommentLexer.h b/include/clang/AST/CommentLexer.h index 6683788227..1ff793701d 100644 --- a/include/clang/AST/CommentLexer.h +++ b/include/clang/AST/CommentLexer.h @@ -43,6 +43,7 @@ enum TokenKind { html_equals, // = html_quoted_string, // "blah\"blah" or 'blah\'blah' html_greater, // > + html_slash_greater, // /> html_tag_close // ArrayRef copyArray(ArrayRef Source) { size_t Size = Source.size(); @@ -41,6 +48,12 @@ class Parser { return llvm::makeArrayRef(static_cast(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( diff --git a/include/clang/AST/CommentSema.h b/include/clang/AST/CommentSema.h index 4d853eb086..7b45320f6b 100644 --- a/include/clang/AST/CommentSema.h +++ b/include/clang/AST/CommentSema.h @@ -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 HTMLOpenTags; + public: - Sema(llvm::BumpPtrAllocator &Allocator); + Sema(llvm::BumpPtrAllocator &Allocator, const SourceManager &SourceMgr, + DiagnosticsEngine &Diags); + + void setDecl(const Decl *D); ParagraphComment *actOnParagraphComment( ArrayRef 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 Attrs, - SourceLocation GreaterLoc); + SourceLocation GreaterLoc, + bool IsSelfClosing); HTMLCloseTagComment *actOnHTMLCloseTag(SourceLocation LocBegin, SourceLocation LocEnd, @@ -106,6 +138,19 @@ public: FullComment *actOnFullComment(ArrayRef 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); diff --git a/include/clang/AST/RawCommentList.h b/include/clang/AST/RawCommentList.h index 6ef213bdcb..370f4124c1 100644 --- a/include/clang/AST/RawCommentList.h +++ b/include/clang/AST/RawCommentList.h @@ -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) { } diff --git a/include/clang/Basic/AllDiagnostics.h b/include/clang/Basic/AllDiagnostics.h index 9f4a25543f..7304c8f673 100644 --- a/include/clang/Basic/AllDiagnostics.h +++ b/include/clang/Basic/AllDiagnostics.h @@ -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" diff --git a/include/clang/Basic/CMakeLists.txt b/include/clang/Basic/CMakeLists.txt index 3df88c7c4a..274b94da8e 100644 --- a/include/clang/Basic/CMakeLists.txt +++ b/include/clang/Basic/CMakeLists.txt @@ -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) diff --git a/include/clang/Basic/Diagnostic.td b/include/clang/Basic/Diagnostic.td index 109cd0812c..6dfecdcb79 100644 --- a/include/clang/Basic/Diagnostic.td +++ b/include/clang/Basic/Diagnostic.td @@ -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" diff --git a/include/clang/Basic/DiagnosticCommentKinds.td b/include/clang/Basic/DiagnosticCommentKinds.td new file mode 100644 index 0000000000..7500d402df --- /dev/null +++ b/include/clang/Basic/DiagnosticCommentKinds.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, DefaultIgnore; + +def warn_doc_html_open_tag_expected_ident_or_greater : Warning< + "HTML opening tag prematurely ended, expected attribute name or '>'">, + InGroup, 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, DefaultIgnore; + +def warn_doc_html_open_close_mismatch : Warning< + "HTML opening tag '%0' closed by '%1'">, + InGroup, 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, DefaultIgnore; + +// \param command + +def warn_doc_param_invalid_direction : Warning< + "unrecognized parameter passing direction, " + "valid directions are '[in]', '[out]' and '[in,out]'">, + InGroup, DefaultIgnore; + +def warn_doc_param_spaces_in_direction : Warning< + "whitespace is not allowed in parameter passing direction">, + InGroup, 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, DefaultIgnore; + +def warn_doc_param_not_found : Warning< + "parameter '%0' not found in the function declaration">, + InGroup, DefaultIgnore; + +def note_doc_param_name_suggestion : Note< + "did you mean '%0'?">; + +} // end of documentation issue category +} // end of AST component diff --git a/include/clang/Basic/DiagnosticGroups.td b/include/clang/Basic/DiagnosticGroups.td index 3382094aef..1cce51ee31 100644 --- a/include/clang/Basic/DiagnosticGroups.td +++ b/include/clang/Basic/DiagnosticGroups.td @@ -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">; diff --git a/include/clang/Basic/DiagnosticIDs.h b/include/clang/Basic/DiagnosticIDs.h index 148a14eed0..1cf103acde 100644 --- a/include/clang/Basic/DiagnosticIDs.h +++ b/include/clang/Basic/DiagnosticIDs.h @@ -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 }; diff --git a/include/clang/Basic/DiagnosticSemaKinds.td b/include/clang/Basic/DiagnosticSemaKinds.td index 52641b86d6..ebbdb8b263 100644 --- a/include/clang/Basic/DiagnosticSemaKinds.td +++ b/include/clang/Basic/DiagnosticSemaKinds.td @@ -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, DefaultIgnore; + "not a Doxygen trailing comment">, InGroup, DefaultIgnore; } // end of documentation issue category } // end of sema component. diff --git a/include/clang/Basic/Makefile b/include/clang/Basic/Makefile index 702afac1e6..6a33133252 100644 --- a/include/clang/Basic/Makefile +++ b/include/clang/Basic/Makefile @@ -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 \ diff --git a/include/clang/Sema/Sema.h b/include/clang/Sema/Sema.h index a48cde03ba..6096466f08 100644 --- a/include/clang/Sema/Sema.h +++ b/include/clang/Sema/Sema.h @@ -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); diff --git a/lib/AST/ASTContext.cpp b/lib/AST/ASTContext.cpp index a5b624fbec..27e4de926d 100644 --- a/lib/AST/ASTContext.cpp +++ b/lib/AST/ASTContext.cpp @@ -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(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; diff --git a/lib/AST/CMakeLists.txt b/lib/AST/CMakeLists.txt index c45f721f97..5f6a09980c 100644 --- a/lib/AST/CMakeLists.txt +++ b/lib/AST/CMakeLists.txt @@ -64,6 +64,7 @@ add_dependencies(clangAST ClangAttrList ClangAttrImpl ClangDiagnosticAST + ClangDiagnosticComment ClangCommentNodes ClangDeclNodes ClangStmtNodes diff --git a/lib/AST/Comment.cpp b/lib/AST/Comment.cpp index 4681d5a143..1520d13417 100644 --- a/lib/AST/Comment.cpp +++ b/lib/AST/Comment.cpp @@ -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(*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 diff --git a/lib/AST/CommentDumper.cpp b/lib/AST/CommentDumper.cpp index fd7a3942a4..267657b76b 100644 --- a/lib/AST/CommentDumper.cpp +++ b/lib/AST/CommentDumper.cpp @@ -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"; diff --git a/lib/AST/CommentLexer.cpp b/lib/AST/CommentLexer.cpp index 55cd409a9c..1f4955d1cf 100644 --- a/lib/AST/CommentLexer.cpp +++ b/lib/AST/CommentLexer.cpp @@ -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; } } diff --git a/lib/AST/CommentParser.cpp b/lib/AST/CommentParser.cpp index 2df3759bb9..eabe61c979 100644 --- a/lib/AST/CommentParser.cpp +++ b/lib/AST/CommentParser.cpp @@ -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()); return S.actOnBlockCommandFinish(BC, PC); @@ -164,7 +164,8 @@ HTMLOpenTagComment *Parser::parseHTMLOpenTag() { SmallVector 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(), + /* IsSelfClosing = */ false); + consumeToken(); + return HOT; + + case tok::html_slash_greater: HOT = S.actOnHTMLOpenTagFinish(HOT, copyArray(llvm::makeArrayRef(Attrs)), - Tok.getLocation()); + Tok.getLocation(), + /* IsSelfClosing = */ true); consumeToken(); return HOT; - } else if (Tok.is(tok::html_equals) || - Tok.is(tok::html_quoted_string)) { - // TODO: Diag() Err expected ident + + 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"); diff --git a/lib/AST/CommentSema.cpp b/lib/AST/CommentSema.cpp index 1193e0404a..fa8001b327 100644 --- a/lib/AST/CommentSema.cpp +++ b/lib/AST/CommentSema.cpp @@ -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(ThisDecl) || isa(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]") + 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; - else if (ArgLower == "[out]") + RemovingWhitespaceHelped = true; + } else if (ArgLower == "[out]") { Direction = ParamCommandComment::Out; - else if (ArgLower == "[in,out]" || ArgLower == "[out,in]") + RemovingWhitespaceHelped = true; + } 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 + 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(ThisDecl)) { + ParamVars = FD->param_begin(); + NumParams = FD->getNumParams(); + } else if (const ObjCMethodDecl *MD = dyn_cast(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 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::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(Name) @@ -259,7 +449,9 @@ bool Sema::isInlineCommand(StringRef Name) { bool Sema::HTMLOpenTagNeedsClosing(StringRef Name) { return llvm::StringSwitch(Name) - .Case("br", true) + .Case("br", false) + .Case("hr", false) + .Case("li", false) .Default(true); } diff --git a/lib/AST/RawCommentList.cpp b/lib/AST/RawCommentList.cpp index d67eb0822f..7e183e2f2d 100644 --- a/lib/AST/RawCommentList.cpp +++ b/lib/AST/RawCommentList.cpp @@ -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()) { diff --git a/lib/Basic/DiagnosticIDs.cpp b/lib/Basic/DiagnosticIDs.cpp index d00573fec5..ca96fd2b9b 100644 --- a/lib/Basic/DiagnosticIDs.cpp +++ b/lib/Basic/DiagnosticIDs.cpp @@ -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 diff --git a/lib/Sema/CMakeLists.txt b/lib/Sema/CMakeLists.txt index ad684d3f99..58cab5aefc 100644 --- a/lib/Sema/CMakeLists.txt +++ b/lib/Sema/CMakeLists.txt @@ -46,6 +46,7 @@ add_dependencies(clangSema ClangARMNeon ClangAttrClasses ClangAttrList + ClangDiagnosticComment ClangDiagnosticSema ClangCommentNodes ClangDeclNodes diff --git a/lib/Sema/SemaDecl.cpp b/lib/Sema/SemaDecl.cpp index 607f57d74f..dfe2882166 100644 --- a/lib/Sema/SemaDecl.cpp +++ b/lib/Sema/SemaDecl.cpp @@ -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(MaybeTagDecl)) { + Group++; + NumDecls--; + } + } + + // See if there are any new comments that are not attached to a decl. + ArrayRef 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; } diff --git a/lib/Sema/SemaDeclCXX.cpp b/lib/Sema/SemaDeclCXX.cpp index 32708090c9..fa42fdd827 100644 --- a/lib/Sema/SemaDeclCXX.cpp +++ b/lib/Sema/SemaDeclCXX.cpp @@ -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 diff --git a/lib/Sema/SemaDeclObjC.cpp b/lib/Sema/SemaDeclObjC.cpp index d67fb5002a..8d4694662d 100644 --- a/lib/Sema/SemaDeclObjC.cpp +++ b/lib/Sema/SemaDeclObjC.cpp @@ -2952,7 +2952,9 @@ Decl *Sema::ActOnMethodDeclaration( if (InferRelatedResultType) ObjCMethod->SetRelatedResultType(); } - + + ActOnDocumentableDecl(ObjCMethod); + return ObjCMethod; } diff --git a/test/Sema/doxygen-comments.c b/test/Sema/doxygen-comments.c deleted file mode 100644 index 72dd090bde..0000000000 --- a/test/Sema/doxygen-comments.c +++ /dev/null @@ -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}:"/**<" - diff --git a/test/Sema/warn-documentation-almost-trailing.c b/test/Sema/warn-documentation-almost-trailing.c new file mode 100644 index 0000000000..fa17cac83a --- /dev/null +++ b/test/Sema/warn-documentation-almost-trailing.c @@ -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}:"/**<" + diff --git a/test/Sema/warn-documentation-fixits.c b/test/Sema/warn-documentation-fixits.c new file mode 100644 index 0000000000..273867de3a --- /dev/null +++ b/test/Sema/warn-documentation-fixits.c @@ -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" + diff --git a/test/Sema/warn-documentation.cpp b/test/Sema/warn-documentation.cpp new file mode 100644 index 0000000000..3949b2ee49 --- /dev/null +++ b/test/Sema/warn-documentation.cpp @@ -0,0 +1,272 @@ +// RUN: %clang_cc1 -fsyntax-only -Wdocumentation -Wdocumentation-pedantic -verify %s + +// expected-warning@+1 {{expected quoted string after equals sign}} +/// +int test_html1(int); + +// expected-warning@+1 {{expected quoted string after equals sign}} +/// +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 '>'}} +/// '}} +/// +int test_html4(int); + +// expected-warning@+1 {{HTML opening tag prematurely ended, expected attribute name or '>'}} +/// +int test_html5(int); + +// expected-warning@+1 {{HTML opening tag prematurely ended, expected attribute name or '>'}} +/// +int test_html6(int); + +// expected-warning@+1 {{HTML opening tag prematurely ended, expected attribute name or '>'}} +/// +int test_html7(int); + +// expected-warning@+1 {{HTML opening tag prematurely ended, expected attribute name or '>'}} +/// '}} expected-note@+1 {{HTML tag started here}} +/** Aaa bbb'}} +/** Aaa bbbMeow +int test_html_nesting1(int); + +/// Meow +int test_html_nesting2(int); + +///

Aaa
+/// Bbb

+int test_html_nesting3(int); + +///

Aaa
+/// Bbb

+int test_html_nesting4(int); + +// expected-warning@+1 {{HTML closing tag does not match any opening tag}} +/// Meow
+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}} +/// Meow +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}} +/// Meow +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 { + } +} + diff --git a/test/Sema/warn-documentation.m b/test/Sema/warn-documentation.m new file mode 100644 index 0000000000..0a02f7bb26 --- /dev/null +++ b/test/Sema/warn-documentation.m @@ -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 + diff --git a/tools/diagtool/DiagnosticNames.cpp b/tools/diagtool/DiagnosticNames.cpp index b1938023ad..31f352414f 100644 --- a/tools/diagtool/DiagnosticNames.cpp +++ b/tools/diagtool/DiagnosticNames.cpp @@ -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 diff --git a/unittests/AST/CommentLexer.cpp b/unittests/AST/CommentLexer.cpp index e1089cc5dc..471863924a 100644 --- a/unittests/AST/CommentLexer.cpp +++ b/unittests/AST/CommentLexer.cpp @@ -1142,6 +1142,60 @@ TEST_F(CommentLexerTest, HTML14) { } TEST_F(CommentLexerTest, HTML15) { + const char *Sources[] = { + "// ", + "// " + }; + + for (size_t i = 0, e = array_lengthof(Sources); i != e; i++) { + std::vector 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[] = { + "// 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 = "// 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 = "// 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 = "// 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[] = { "// ", "// ", diff --git a/unittests/AST/CommentParser.cpp b/unittests/AST/CommentParser.cpp index d5dd0a9c56..c779a881d4 100644 --- a/unittests/AST/CommentParser.cpp +++ b/unittests/AST/CommentParser.cpp @@ -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[] = { + "//
", + "//
" + }; + + 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[] = { "// ", @@ -881,7 +930,7 @@ TEST_F(CommentParserTest, HTML3) { } } -TEST_F(CommentParserTest, HTML4) { +TEST_F(CommentParserTest, HTML5) { const char *Sources[] = { "// ", @@ -904,7 +953,7 @@ TEST_F(CommentParserTest, HTML4) { } } -TEST_F(CommentParserTest, HTML5) { +TEST_F(CommentParserTest, HTML6) { const char *Source = "//
\n"
     "// Aaa\n"
diff --git a/unittests/AST/Makefile b/unittests/AST/Makefile
index b25243f0f7..31cd5de1b7 100644
--- a/unittests/AST/Makefile
+++ b/unittests/AST/Makefile
@@ -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