diff options
38 files changed, 1147 insertions, 149 deletions
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., <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. 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 // </tag }; } // end namespace tok diff --git a/include/clang/AST/CommentParser.h b/include/clang/AST/CommentParser.h index e75d7978b7..d78705a808 100644 --- a/include/clang/AST/CommentParser.h +++ b/include/clang/AST/CommentParser.h @@ -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( 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<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); 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<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 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<Doxygen>, DefaultIgnore; + "not a Doxygen trailing comment">, InGroup<Documentation>, 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<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; 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<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 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<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(), + /* 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<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]") + 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<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); } 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<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; } 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}} +/// <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 { + } +} + 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[] = { + "// <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>", 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), " @@ -837,6 +864,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 ", "// <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" 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 |