]> granicus.if.org Git - clang/commitdiff
Enable comment parsing and semantic analysis to emit diagnostics. A few
authorDmitri Gribenko <gribozavr@gmail.com>
Wed, 11 Jul 2012 21:38:39 +0000 (21:38 +0000)
committerDmitri Gribenko <gribozavr@gmail.com>
Wed, 11 Jul 2012 21:38:39 +0000 (21:38 +0000)
diagnostics implemented -- see testcases.

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

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

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

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

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

git-svn-id: https://llvm.org/svn/llvm-project/cfe/trunk@160078 91177308-0d34-0410-b5e6-96231b3b80d8

38 files changed:
include/clang/AST/ASTContext.h
include/clang/AST/Comment.h
include/clang/AST/CommentDiagnostic.h [new file with mode: 0644]
include/clang/AST/CommentLexer.h
include/clang/AST/CommentParser.h
include/clang/AST/CommentSema.h
include/clang/AST/RawCommentList.h
include/clang/Basic/AllDiagnostics.h
include/clang/Basic/CMakeLists.txt
include/clang/Basic/Diagnostic.td
include/clang/Basic/DiagnosticCommentKinds.td [new file with mode: 0644]
include/clang/Basic/DiagnosticGroups.td
include/clang/Basic/DiagnosticIDs.h
include/clang/Basic/DiagnosticSemaKinds.td
include/clang/Basic/Makefile
include/clang/Sema/Sema.h
lib/AST/ASTContext.cpp
lib/AST/CMakeLists.txt
lib/AST/Comment.cpp
lib/AST/CommentDumper.cpp
lib/AST/CommentLexer.cpp
lib/AST/CommentParser.cpp
lib/AST/CommentSema.cpp
lib/AST/RawCommentList.cpp
lib/Basic/DiagnosticIDs.cpp
lib/Sema/CMakeLists.txt
lib/Sema/SemaDecl.cpp
lib/Sema/SemaDeclCXX.cpp
lib/Sema/SemaDeclObjC.cpp
test/Sema/doxygen-comments.c [deleted file]
test/Sema/warn-documentation-almost-trailing.c [new file with mode: 0644]
test/Sema/warn-documentation-fixits.c [new file with mode: 0644]
test/Sema/warn-documentation.cpp [new file with mode: 0644]
test/Sema/warn-documentation.m [new file with mode: 0644]
tools/diagtool/DiagnosticNames.cpp
unittests/AST/CommentLexer.cpp
unittests/AST/CommentParser.cpp
unittests/AST/Makefile

index 4dc0a447bdf723a29ea143cfee43ba0c8598489e..5283d6dadccf5e87d50b8a3ba8e9d98d452ea4cf 100644 (file)
@@ -392,6 +392,11 @@ public:
   
   SourceManager& getSourceManager() { return SourceMgr; }
   const SourceManager& getSourceManager() const { return SourceMgr; }
+
+  llvm::BumpPtrAllocator &getAllocator() const {
+    return BumpAlloc;
+  }
+
   void *Allocate(unsigned Size, unsigned Align = 8) const {
     return BumpAlloc.Allocate(Size, Align);
   }
@@ -436,9 +441,13 @@ public:
 
   /// \brief Return the documentation comment attached to a given declaration,
   /// without looking into cache.
-  const RawComment *getRawCommentForDeclNoCache(const Decl *D) const;
+  RawComment *getRawCommentForDeclNoCache(const Decl *D) const;
 
 public:
+  RawCommentList &getRawCommentList() {
+    return Comments;
+  }
+
   void addComment(const RawComment &RC) {
     Comments.addComment(RC, BumpAlloc);
   }
index e631f38b05901b973c1d466cf717c3a543dc1d89..16719dfd232f09c5c35a860b0f5282bbe13b7b8c 100644 (file)
@@ -50,6 +50,16 @@ protected:
   };
   enum { NumInlineContentCommentBitfields = 9 };
 
+  class HTMLOpenTagCommentBitfields {
+    friend class HTMLOpenTagComment;
+
+    unsigned : NumInlineContentCommentBitfields;
+
+    /// True if this tag is self-closing (e. g., <br />).  This is based on tag
+    /// spelling in comment (plain <br> would not set this flag).
+    unsigned IsSelfClosing : 1;
+  };
+
   class ParamCommandCommentBitfields {
     friend class ParamCommandComment;
 
@@ -66,6 +76,7 @@ protected:
   union {
     CommentBitfields CommentBits;
     InlineContentCommentBitfields InlineContentCommentBits;
+    HTMLOpenTagCommentBitfields HTMLOpenTagCommentBits;
     ParamCommandCommentBitfields ParamCommandCommentBits;
   };
 
@@ -107,8 +118,6 @@ public:
 
   static bool classof(const Comment *) { return true; }
 
-  typedef Comment * const *child_iterator;
-
   SourceRange getSourceRange() const LLVM_READONLY { return Range; }
 
   SourceLocation getLocStart() const LLVM_READONLY {
@@ -121,9 +130,13 @@ public:
 
   SourceLocation getLocation() const LLVM_READONLY { return Loc; }
 
+  typedef Comment * const *child_iterator;
+
   child_iterator child_begin() const;
   child_iterator child_end() const;
 
+  // TODO: const child iterator
+
   unsigned child_count() const {
     return child_end() - child_begin();
   }
@@ -180,6 +193,8 @@ public:
   child_iterator child_end() const { return NULL; }
 
   StringRef getText() const LLVM_READONLY { return Text; }
+
+  bool isWhitespace() const;
 };
 
 /// A command with word-like arguments that is considered inline content.
@@ -325,8 +340,9 @@ public:
                      LocBegin, LocBegin.getLocWithOffset(1 + TagName.size()),
                      TagName,
                      LocBegin.getLocWithOffset(1),
-                     LocBegin.getLocWithOffset(1 + TagName.size()))
-  { }
+                     LocBegin.getLocWithOffset(1 + TagName.size())) {
+    HTMLOpenTagCommentBits.IsSelfClosing = false;
+  }
 
   static bool classof(const Comment *C) {
     return C->getCommentKind() == HTMLOpenTagCommentKind;
@@ -362,6 +378,14 @@ public:
   void setGreaterLoc(SourceLocation GreaterLoc) {
     Range.setEnd(GreaterLoc);
   }
+
+  bool isSelfClosing() const {
+    return HTMLOpenTagCommentBits.IsSelfClosing;
+  }
+
+  void setSelfClosing() {
+    HTMLOpenTagCommentBits.IsSelfClosing = true;
+  }
 };
 
 /// A closing HTML tag.
@@ -438,6 +462,8 @@ public:
   child_iterator child_end() const {
     return reinterpret_cast<child_iterator>(Content.end());
   }
+
+  bool isWhitespace() const;
 };
 
 /// A command that has zero or more word-like arguments (number of word-like
@@ -520,6 +546,11 @@ public:
 
   void setArgs(llvm::ArrayRef<Argument> A) {
     Args = A;
+    if (Args.size() > 0) {
+      SourceLocation NewLocEnd = Args.back().Range.getEnd();
+      if (NewLocEnd.isValid())
+        setSourceRange(SourceRange(getLocStart(), NewLocEnd));
+    }
   }
 
   ParagraphComment *getParagraph() const LLVM_READONLY {
@@ -536,18 +567,18 @@ public:
 
 /// Doxygen \\param command.
 class ParamCommandComment : public BlockCommandComment {
-public:
-  enum PassDirection {
-    In,
-    Out,
-    InOut
-  };
+private:
+  /// Parameter index in the function declaration.
+  unsigned ParamIndex;
 
 public:
+  enum { InvalidParamIndex = ~0U };
+
   ParamCommandComment(SourceLocation LocBegin,
                       SourceLocation LocEnd,
                       StringRef Name) :
-      BlockCommandComment(ParamCommandCommentKind, LocBegin, LocEnd, Name) {
+      BlockCommandComment(ParamCommandCommentKind, LocBegin, LocEnd, Name),
+      ParamIndex(InvalidParamIndex) {
     ParamCommandCommentBits.Direction = In;
     ParamCommandCommentBits.IsDirectionExplicit = false;
   }
@@ -558,6 +589,14 @@ public:
 
   static bool classof(const ParamCommandComment *) { return true; }
 
+  enum PassDirection {
+    In,
+    Out,
+    InOut
+  };
+
+  static const char *getDirectionAsString(PassDirection D);
+
   PassDirection getDirection() const LLVM_READONLY {
     return static_cast<PassDirection>(ParamCommandCommentBits.Direction);
   }
@@ -582,6 +621,19 @@ public:
   SourceRange getParamNameRange() const {
     return Args[0].Range;
   }
+
+  bool isParamIndexValid() const LLVM_READONLY {
+    return ParamIndex != InvalidParamIndex;
+  }
+
+  unsigned getParamIndex() const LLVM_READONLY {
+    return ParamIndex;
+  }
+
+  void setParamIndex(unsigned Index) {
+    ParamIndex = Index;
+    assert(isParamIndexValid());
+  }
 };
 
 /// A line of text contained in a verbatim block.
diff --git a/include/clang/AST/CommentDiagnostic.h b/include/clang/AST/CommentDiagnostic.h
new file mode 100644 (file)
index 0000000..6e89410
--- /dev/null
@@ -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
+
index 6683788227df7744706e6cd5b6d6e1e413486283..1ff793701d596f0667377fafbf1854c8c5ee2100 100644 (file)
@@ -43,6 +43,7 @@ enum TokenKind {
   html_equals,        // =
   html_quoted_string, // "blah\"blah" or 'blah\'blah'
   html_greater,       // >
+  html_slash_greater, // />
   html_tag_close      // </tag
 };
 } // end namespace tok
index e75d7978b77a19a2b87c4e86b0a6d1acd4c53108..d78705a80853a52678bd2ce26a3cdceee838100e 100644 (file)
 #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(
index 4d853eb086845bcf9d3919e96c433a3953494247..7b45320f6bee01f07c2a6646af27ee688da89f1b 100644 (file)
@@ -14,6 +14,7 @@
 #ifndef LLVM_CLANG_AST_COMMENT_SEMA_H
 #define LLVM_CLANG_AST_COMMENT_SEMA_H
 
+#include "clang/Basic/Diagnostic.h"
 #include "clang/Basic/SourceLocation.h"
 #include "clang/AST/Comment.h"
 #include "llvm/ADT/ArrayRef.h"
 #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);
index 6ef213bdcb43f7745d287933b03ba407f8c0894c..370f4124c13b3beddc29503c3a88f88f4eb9acad 100644 (file)
@@ -48,6 +48,14 @@ public:
     return Kind == RCK_Merged;
   }
 
+  bool isAttached() const LLVM_READONLY {
+    return IsAttached;
+  }
+
+  void setAttached() {
+    IsAttached = true;
+  }
+
   /// Returns true if it is a comment that should be put after a member:
   /// \code ///< stuff \endcode
   /// \code //!< stuff \endcode
@@ -110,6 +118,9 @@ private:
 
   unsigned Kind : 3;
 
+  /// True if comment is attached to a declaration in ASTContext.
+  bool IsAttached : 1;
+
   bool IsTrailingComment : 1;
   bool IsAlmostTrailingComment : 1;
 
@@ -122,7 +133,7 @@ private:
   RawComment(SourceRange SR, CommentKind K, bool IsTrailingComment,
              bool IsAlmostTrailingComment) :
     Range(SR), RawTextValid(false), BriefTextValid(false), Kind(K),
-    IsTrailingComment(IsTrailingComment),
+    IsAttached(false), IsTrailingComment(IsTrailingComment),
     IsAlmostTrailingComment(IsAlmostTrailingComment),
     BeginLineValid(false), EndLineValid(false)
   { }
index 9f4a25543fd026396763efc9b2704bf76dc39581..7304c8f673e665dbdfb887ba101894aca9714435 100644 (file)
@@ -16,6 +16,7 @@
 #define LLVM_CLANG_ALL_DIAGNOSTICS_H
 
 #include "clang/AST/ASTDiagnostic.h"
+#include "clang/AST/CommentDiagnostic.h"
 #include "clang/Analysis/AnalysisDiagnostic.h"
 #include "clang/Driver/DriverDiagnostic.h"
 #include "clang/Frontend/FrontendDiagnostic.h"
index 3df88c7c4a5e96005da3e8ffaa847b91f9d6980a..274b94da8eda144b9597e7181aaaba05cccbdb59 100644 (file)
@@ -7,6 +7,7 @@ endmacro(clang_diag_gen)
 
 clang_diag_gen(Analysis)
 clang_diag_gen(AST)
+clang_diag_gen(Comment)
 clang_diag_gen(Common)
 clang_diag_gen(Driver)
 clang_diag_gen(Frontend)
index 109cd0812c6866d993fa48e7413b2dff4096d981..6dfecdcb79729bf097443227698af9f1ec423286 100644 (file)
@@ -88,6 +88,7 @@ class AccessControl { bit AccessControl = 1; }
 // Definitions for Diagnostics.
 include "DiagnosticASTKinds.td"
 include "DiagnosticAnalysisKinds.td"
+include "DiagnosticCommentKinds.td"
 include "DiagnosticCommonKinds.td"
 include "DiagnosticDriverKinds.td"
 include "DiagnosticFrontendKinds.td"
diff --git a/include/clang/Basic/DiagnosticCommentKinds.td b/include/clang/Basic/DiagnosticCommentKinds.td
new file mode 100644 (file)
index 0000000..7500d40
--- /dev/null
@@ -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
index 3382094aef6292f7acfb2f70410c21b713e1751d..1cce51ee311172e5550f8b89b5cabdc961392a05 100644 (file)
@@ -57,7 +57,9 @@ def DeprecatedImplementations :DiagGroup<"deprecated-implementations">;
 def : DiagGroup<"disabled-optimization">;
 def : DiagGroup<"discard-qual">;
 def : DiagGroup<"div-by-zero">;
-def Doxygen : DiagGroup<"doxygen">;
+def DocumentationHTML : DiagGroup<"documentation-html">;
+def DocumentationPedantic : DiagGroup<"documentation-pedantic">;
+def Documentation : DiagGroup<"documentation", [DocumentationHTML]>;
 def EmptyBody : DiagGroup<"empty-body">;
 def ExtraTokens : DiagGroup<"extra-tokens">;
 
index 148a14eed037056864b47ac556743f96bdcc4e14..1cf103acdedc76998b7ccf98adaadccd05e7ad3e 100644 (file)
@@ -38,7 +38,8 @@ namespace clang {
       DIAG_START_LEX           = DIAG_START_SERIALIZATION   +  120,
       DIAG_START_PARSE         = DIAG_START_LEX             +  300,
       DIAG_START_AST           = DIAG_START_PARSE           +  400,
-      DIAG_START_SEMA          = DIAG_START_AST             +  100,
+      DIAG_START_COMMENT       = DIAG_START_AST             +  100,
+      DIAG_START_SEMA          = DIAG_START_COMMENT         +  100,
       DIAG_START_ANALYSIS      = DIAG_START_SEMA            + 3000,
       DIAG_UPPER_LIMIT         = DIAG_START_ANALYSIS        +  100
     };
index 52641b86d69293f66cff6e993cea8d0402e46148..ebbdb8b263a0bde2367c30ed86208f590d9b475b 100644 (file)
@@ -5704,7 +5704,7 @@ def err_module_private_definition : Error<
 
 let CategoryName = "Documentation Issue" in {
 def warn_not_a_doxygen_trailing_member_comment : Warning<
-  "not a Doxygen trailing comment">, InGroup<Doxygen>, DefaultIgnore;
+  "not a Doxygen trailing comment">, InGroup<Documentation>, DefaultIgnore;
 } // end of documentation issue category
 
 } // end of sema component.
index 702afac1e6b91d458d948d0f7d0863ec3344186a..6a3313325216fbabaf314c32004fda1a5aa5950a 100644 (file)
@@ -1,6 +1,7 @@
 CLANG_LEVEL := ../../..
 BUILT_SOURCES = \
        DiagnosticAnalysisKinds.inc DiagnosticASTKinds.inc \
+       DiagnosticCommentKinds.inc \
        DiagnosticCommonKinds.inc DiagnosticDriverKinds.inc \
        DiagnosticFrontendKinds.inc DiagnosticLexKinds.inc \
        DiagnosticParseKinds.inc DiagnosticSemaKinds.inc \
index a48cde03ba6c061a6707e53f99d365e755f08898..6096466f08062f64eae6af69f66831fe56d45734 100644 (file)
@@ -1313,6 +1313,12 @@ public:
                                          unsigned NumDecls);
   DeclGroupPtrTy BuildDeclaratorGroup(Decl **Group, unsigned NumDecls,
                                       bool TypeMayContainAuto = true);
+
+  /// Should be called on all declarations that might have attached
+  /// documentation comments.
+  void ActOnDocumentableDecl(Decl *D);
+  void ActOnDocumentableDecls(Decl **Group, unsigned NumDecls);
+
   void ActOnFinishKNRParamDeclarations(Scope *S, Declarator &D,
                                        SourceLocation LocAfterDecls);
   void CheckForFunctionRedefinition(FunctionDecl *FD);
index a5b624fbece13da294520578325e1645fa9836e1..27e4de926d3d001b77854a8774eea9fb52d75662 100644 (file)
@@ -56,7 +56,7 @@ enum FloatingRank {
   HalfRank, FloatRank, DoubleRank, LongDoubleRank
 };
 
-const RawComment *ASTContext::getRawCommentForDeclNoCache(const Decl *D) const {
+RawComment *ASTContext::getRawCommentForDeclNoCache(const Decl *D) const {
   if (!CommentsLoaded && ExternalSource) {
     ExternalSource->ReadComments();
     CommentsLoaded = true;
@@ -160,11 +160,13 @@ const RawComment *ASTContext::getRawCommentForDecl(const Decl *D) const {
     return C.first;
   }
 
-  const RawComment *RC = getRawCommentForDeclNoCache(D);
+  RawComment *RC = getRawCommentForDeclNoCache(D);
   // If we found a comment, it should be a documentation comment.
   assert(!RC || RC->isDocumentation());
   DeclComments[D] =
       RawAndParsedComment(RC, static_cast<comments::FullComment *>(NULL));
+  if (RC)
+    RC->setAttached();
   return RC;
 }
 
@@ -187,8 +189,10 @@ comments::FullComment *ASTContext::getCommentForDecl(const Decl *D) const {
   comments::Lexer L(RC->getSourceRange().getBegin(), comments::CommentOptions(),
                     RawText.begin(), RawText.end());
 
-  comments::Sema S(this->BumpAlloc);
-  comments::Parser P(L, S, this->BumpAlloc);
+  comments::Sema S(getAllocator(), getSourceManager(), getDiagnostics());
+  S.setDecl(D);
+  comments::Parser P(L, S, getAllocator(), getSourceManager(),
+                     getDiagnostics());
 
   comments::FullComment *FC = P.parseFullComment();
   DeclComments[D].second = FC;
index c45f721f97d2d5a75b29aa35e0a0eddb7067179a..5f6a09980cca18e8049b979ffb1b33167b63703a 100644 (file)
@@ -64,6 +64,7 @@ add_dependencies(clangAST
   ClangAttrList
   ClangAttrImpl
   ClangDiagnosticAST
+  ClangDiagnosticComment
   ClangCommentNodes
   ClangDeclNodes
   ClangStmtNodes
index 4681d5a143d45a406d39183c748647d692c0570a..1520d1341722fcf8f7102244cdebb0133faf9ad2 100644 (file)
@@ -86,6 +86,38 @@ Comment::child_iterator Comment::child_end() const {
   llvm_unreachable("Unknown comment kind!");
 }
 
+bool TextComment::isWhitespace() const {
+  for (StringRef::const_iterator I = Text.begin(), E = Text.end();
+       I != E; ++I) {
+    const char C = *I;
+    if (C != ' ' && C != '\n' && C != '\r' &&
+        C != '\t' && C != '\f' && C != '\v')
+      return false;
+  }
+  return true;
+}
+
+bool ParagraphComment::isWhitespace() const {
+  for (child_iterator I = child_begin(), E = child_end(); I != E; ++I) {
+    if (const TextComment *TC = dyn_cast<TextComment>(*I)) {
+      if (!TC->isWhitespace())
+        return false;
+    }
+  }
+  return true;
+}
+
+const char *ParamCommandComment::getDirectionAsString(PassDirection D) {
+  switch (D) {
+  case ParamCommandComment::In:
+    return "[in]";
+  case ParamCommandComment::Out:
+    return "[out]";
+  case ParamCommandComment::InOut:
+    return "[in,out]";
+  }
+  llvm_unreachable("unknown PassDirection");
+}
 
 } // end namespace comments
 } // end namespace clang
index fd7a3942a43868f8c56ec78c515407a290b324fa..267657b76b4f9adcf31462cfe1f9c8827d1c34fc 100644 (file)
@@ -121,6 +121,8 @@ void CommentDumper::visitHTMLOpenTagComment(const HTMLOpenTagComment *C) {
       OS << " \"" << Attr.Name << "=\"" << Attr.Value << "\"";
     }
   }
+  if (C->isSelfClosing())
+    OS << " SelfClosing";
 }
 
 void CommentDumper::visitHTMLCloseTagComment(const HTMLCloseTagComment *C) {
@@ -142,17 +144,7 @@ void CommentDumper::visitBlockCommandComment(const BlockCommandComment *C) {
 void CommentDumper::visitParamCommandComment(const ParamCommandComment *C) {
   dumpComment(C);
 
-  switch (C->getDirection()) {
-  case ParamCommandComment::In:
-    OS << " [in]";
-    break;
-  case ParamCommandComment::Out:
-    OS << " [out]";
-    break;
-  case ParamCommandComment::InOut:
-    OS << " [in,out]";
-    break;
-  }
+  OS << " " << ParamCommandComment::getDirectionAsString(C->getDirection());
 
   if (C->isDirectionExplicit())
     OS << " explicitly";
index 55cd409a9ca16435ec549bb076335f8abd058b0d..1f4955d1cf2646905853dc85b7c823be975ac88c 100644 (file)
@@ -509,7 +509,7 @@ void Lexer::setupAndLexHTMLOpenTag(Token &T) {
 
   const char C = *BufferPtr;
   if (BufferPtr != CommentEnd &&
-      (C == '>' || isHTMLIdentifierStartingCharacter(C)))
+      (C == '>' || C == '/' || isHTMLIdentifierStartingCharacter(C)))
     State = LS_HTMLOpenTag;
 }
 
@@ -546,6 +546,18 @@ void Lexer::lexHTMLOpenTag(Token &T) {
       formTokenWithChars(T, TokenPtr, tok::html_greater);
       State = LS_Normal;
       return;
+    case '/':
+      TokenPtr++;
+      if (TokenPtr != CommentEnd && *TokenPtr == '>') {
+        TokenPtr++;
+        formTokenWithChars(T, TokenPtr, tok::html_slash_greater);
+      } else {
+        StringRef Text(BufferPtr, TokenPtr - BufferPtr);
+        formTokenWithChars(T, TokenPtr, tok::text);
+        T.setText(Text);
+      }
+      State = LS_Normal;
+      return;
     }
   }
 
index 2df3759bb961bedf3d490b1612b4c61aa08ff764..eabe61c979532b0e649d84d2cb8d72a3cb5c190b 100644 (file)
@@ -9,13 +9,16 @@
 
 #include "clang/AST/CommentParser.h"
 #include "clang/AST/CommentSema.h"
+#include "clang/AST/CommentDiagnostic.h"
+#include "clang/Basic/SourceManager.h"
 #include "llvm/Support/ErrorHandling.h"
 
 namespace clang {
 namespace comments {
 
-Parser::Parser(Lexer &L, Sema &S, llvm::BumpPtrAllocator &Allocator):
-    L(L), S(S), Allocator(Allocator) {
+Parser::Parser(Lexer &L, Sema &S, llvm::BumpPtrAllocator &Allocator,
+               const SourceManager &SourceMgr, DiagnosticsEngine &Diags):
+    L(L), S(S), Allocator(Allocator), SourceMgr(SourceMgr), Diags(Diags) {
   consumeToken();
 }
 
@@ -26,18 +29,16 @@ ParamCommandComment *Parser::parseParamCommandArgs(
   // Check if argument looks like direction specification: [dir]
   // e.g., [in], [out], [in,out]
   if (Retokenizer.lexDelimitedSeq(Arg, '[', ']'))
-    PC = S.actOnParamCommandArg(PC,
-                                Arg.getLocation(),
-                                Arg.getEndLocation(),
-                                Arg.getText(),
-                                /* IsDirection = */ true);
+    PC = S.actOnParamCommandDirectionArg(PC,
+                                         Arg.getLocation(),
+                                         Arg.getEndLocation(),
+                                         Arg.getText());
 
   if (Retokenizer.lexWord(Arg))
-    PC = S.actOnParamCommandArg(PC,
-                                Arg.getLocation(),
-                                Arg.getEndLocation(),
-                                Arg.getText(),
-                                /* IsDirection = */ false);
+    PC = S.actOnParamCommandParamNameArg(PC,
+                                         Arg.getLocation(),
+                                         Arg.getEndLocation(),
+                                         Arg.getText());
 
   return PC;
 }
@@ -84,7 +85,6 @@ BlockCommandComment *Parser::parseBlockCommand() {
   if (Tok.is(tok::command) && S.isBlockCommand(Tok.getCommandName())) {
     // Block command ahead.  We can't nest block commands, so pretend that this
     // command has an empty argument.
-    // TODO: Diag() Warn empty arg to block command
     ParagraphComment *PC = S.actOnParagraphComment(
                                 ArrayRef<InlineContentComment *>());
     return S.actOnBlockCommandFinish(BC, PC);
@@ -164,7 +164,8 @@ HTMLOpenTagComment *Parser::parseHTMLOpenTag() {
 
   SmallVector<HTMLOpenTagComment::Attribute, 2> Attrs;
   while (true) {
-    if (Tok.is(tok::html_ident)) {
+    switch (Tok.getKind()) {
+    case tok::html_ident: {
       Token Ident = Tok;
       consumeToken();
       if (Tok.isNot(tok::html_equals)) {
@@ -175,9 +176,14 @@ HTMLOpenTagComment *Parser::parseHTMLOpenTag() {
       Token Equals = Tok;
       consumeToken();
       if (Tok.isNot(tok::html_quoted_string)) {
-        // TODO: Diag() expected quoted string
+        Diag(Tok.getLocation(),
+             diag::warn_doc_html_open_tag_expected_quoted_string)
+          << SourceRange(Equals.getLocation());
         Attrs.push_back(HTMLOpenTagComment::Attribute(Ident.getLocation(),
                                                       Ident.getHTMLIdent()));
+        while (Tok.is(tok::html_equals) ||
+               Tok.is(tok::html_quoted_string))
+          consumeToken();
         continue;
       }
       Attrs.push_back(HTMLOpenTagComment::Attribute(
@@ -189,24 +195,66 @@ HTMLOpenTagComment *Parser::parseHTMLOpenTag() {
                               Tok.getHTMLQuotedString()));
       consumeToken();
       continue;
-    } else if (Tok.is(tok::html_greater)) {
+    }
+
+    case tok::html_greater:
+      HOT = S.actOnHTMLOpenTagFinish(HOT,
+                                     copyArray(llvm::makeArrayRef(Attrs)),
+                                     Tok.getLocation(),
+                                     /* 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");
index 1193e0404a0ffa21256b18be592aee55c5b0ef71..fa8001b327c621ade810651d21a35a2ee6ae1f35 100644 (file)
@@ -8,13 +8,22 @@
 //===----------------------------------------------------------------------===//
 
 #include "clang/AST/CommentSema.h"
+#include "clang/AST/CommentDiagnostic.h"
+#include "clang/AST/Decl.h"
+#include "clang/AST/DeclObjC.h"
+#include "clang/Basic/SourceManager.h"
 #include "llvm/ADT/StringSwitch.h"
 
 namespace clang {
 namespace comments {
 
-Sema::Sema(llvm::BumpPtrAllocator &Allocator) :
-    Allocator(Allocator) {
+Sema::Sema(llvm::BumpPtrAllocator &Allocator, const SourceManager &SourceMgr,
+           DiagnosticsEngine &Diags) :
+    Allocator(Allocator), SourceMgr(SourceMgr), Diags(Diags), ThisDecl(NULL) {
+}
+
+void Sema::setDecl(const Decl *D) {
+  ThisDecl = D;
 }
 
 ParagraphComment *Sema::actOnParagraphComment(
@@ -39,83 +48,153 @@ BlockCommandComment *Sema::actOnBlockCommandFinish(
                               BlockCommandComment *Command,
                               ParagraphComment *Paragraph) {
   Command->setParagraph(Paragraph);
+  checkBlockCommandEmptyParagraph(Command);
   return Command;
 }
 
 ParamCommandComment *Sema::actOnParamCommandStart(SourceLocation LocBegin,
                                                   SourceLocation LocEnd,
                                                   StringRef Name) {
-  return new (Allocator) ParamCommandComment(LocBegin, LocEnd, Name);
+  ParamCommandComment *Command =
+      new (Allocator) ParamCommandComment(LocBegin, LocEnd, Name);
+
+  if (!ThisDecl ||
+      !(isa<FunctionDecl>(ThisDecl) || isa<ObjCMethodDecl>(ThisDecl)))
+    Diag(Command->getLocation(),
+         diag::warn_doc_param_not_attached_to_a_function_decl)
+      << Command->getCommandNameRange();
+
+  return Command;
 }
 
-ParamCommandComment *Sema::actOnParamCommandArg(ParamCommandComment *Command,
+ParamCommandComment *Sema::actOnParamCommandDirectionArg(
+                                                ParamCommandComment *Command,
                                                 SourceLocation ArgLocBegin,
                                                 SourceLocation ArgLocEnd,
-                                                StringRef Arg,
-                                                bool IsDirection) {
-  if (IsDirection) {
-    ParamCommandComment::PassDirection Direction;
-    std::string ArgLower = Arg.lower();
-    // TODO: optimize: lower Name first (need an API in SmallString for that),
-    // after that StringSwitch.
-    if (ArgLower == "[in]")
+                                                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);
 }
 
index d67eb0822f2b1797774c932f690f163ed2c66944..7e183e2f2d3268dcf7716813d196574098399647 100644 (file)
@@ -61,7 +61,7 @@ bool mergedCommentIsTrailingComment(StringRef Comment) {
 RawComment::RawComment(const SourceManager &SourceMgr, SourceRange SR,
                        bool Merged) :
     Range(SR), RawTextValid(false), BriefTextValid(false),
-    IsAlmostTrailingComment(false),
+    IsAttached(false), IsAlmostTrailingComment(false),
     BeginLineValid(false), EndLineValid(false) {
   // Extract raw comment text, if possible.
   if (SR.getBegin() == SR.getEnd() || getRawText(SourceMgr).empty()) {
index d00573fec523fe220a49340eb35210cab7e2fd43..ca96fd2b9b243afe00f970843f90e538b1b5c6cd 100644 (file)
@@ -79,6 +79,7 @@ static const StaticDiagInfoRec StaticDiagInfo[] = {
 #include "clang/Basic/DiagnosticLexKinds.inc"
 #include "clang/Basic/DiagnosticParseKinds.inc"
 #include "clang/Basic/DiagnosticASTKinds.inc"
+#include "clang/Basic/DiagnosticCommentKinds.inc"
 #include "clang/Basic/DiagnosticSemaKinds.inc"
 #include "clang/Basic/DiagnosticAnalysisKinds.inc"
 #undef DIAG
index ad684d3f99e475b93f5cad63e6a4bff0602c34e6..58cab5aefca2b713cc1f40cbc4302e7b11c27631 100644 (file)
@@ -46,6 +46,7 @@ add_dependencies(clangSema
   ClangARMNeon
   ClangAttrClasses
   ClangAttrList
+  ClangDiagnosticComment
   ClangDiagnosticSema
   ClangCommentNodes
   ClangDeclNodes
index 607f57d74fd6b6b41cb687431ec8a8ee8ace03e3..dfe2882166bd5d348028a4150afb0c12b731a32e 100644 (file)
@@ -21,6 +21,7 @@
 #include "clang/AST/ASTConsumer.h"
 #include "clang/AST/ASTContext.h"
 #include "clang/AST/CXXInheritance.h"
+#include "clang/AST/CommentDiagnostic.h"
 #include "clang/AST/DeclCXX.h"
 #include "clang/AST/DeclObjC.h"
 #include "clang/AST/DeclTemplate.h"
@@ -2703,6 +2704,8 @@ Decl *Sema::ParsedFreeStandingDeclSpec(Scope *S, AccessSpecifier AS,
     }
   }
 
+  ActOnDocumentableDecl(TagD);
+
   return TagD;
 }
 
@@ -7104,9 +7107,55 @@ Sema::BuildDeclaratorGroup(Decl **Group, unsigned NumDecls,
     }
   }
 
+  ActOnDocumentableDecls(Group, NumDecls);
+
   return DeclGroupPtrTy::make(DeclGroupRef::Create(Context, Group, NumDecls));
 }
 
+void Sema::ActOnDocumentableDecl(Decl *D) {
+  ActOnDocumentableDecls(&D, 1);
+}
+
+void Sema::ActOnDocumentableDecls(Decl **Group, unsigned NumDecls) {
+  // Don't parse the comment if Doxygen diagnostics are ignored.
+  if (NumDecls == 0 || !Group[0])
+   return;
+
+  if (Diags.getDiagnosticLevel(diag::warn_doc_param_not_found,
+                               Group[0]->getLocation())
+        == DiagnosticsEngine::Ignored)
+    return;
+
+  if (NumDecls >= 2) {
+    // This is a decl group.  Normally it will contain only declarations
+    // procuded from declarator list.  But in case we have any definitions or
+    // additional declaration references:
+    //   'typedef struct S {} S;'
+    //   'typedef struct S *S;'
+    //   'struct S *pS;'
+    // FinalizeDeclaratorGroup adds these as separate declarations.
+    Decl *MaybeTagDecl = Group[0];
+    if (MaybeTagDecl && isa<TagDecl>(MaybeTagDecl)) {
+      Group++;
+      NumDecls--;
+    }
+  }
+
+  // See if there are any new comments that are not attached to a decl.
+  ArrayRef<RawComment *> Comments = Context.getRawCommentList().getComments();
+  if (!Comments.empty() &&
+      !Comments.back()->isAttached()) {
+    // There is at least one comment that not attached to a decl.
+    // Maybe it should be attached to one of these decls?
+    //
+    // Note that this way we pick up not only comments that precede the
+    // declaration, but also comments that *follow* the declaration -- thanks to
+    // the lookahead in the lexer: we've consumed the semicolon and looked
+    // ahead through comments.
+    for (unsigned i = 0; i != NumDecls; ++i)
+      Context.getCommentForDecl(Group[i]);
+  }
+}
 
 /// ActOnParamDeclarator - Called from Parser::ParseFunctionDeclarator()
 /// to introduce parameters into function prototype scope.
@@ -8868,6 +8917,8 @@ void Sema::ActOnTagStartDefinition(Scope *S, Decl *TagD) {
   
   // Enter the tag context.
   PushDeclContext(S, Tag);
+
+  ActOnDocumentableDecl(TagD);
 }
 
 Decl *Sema::ActOnObjCContainerStartDefinition(Decl *IDecl) {
@@ -8877,6 +8928,7 @@ Decl *Sema::ActOnObjCContainerStartDefinition(Decl *IDecl) {
   assert(getContainingDC(OCD) == CurContext &&
       "The next DeclContext should be lexically contained in the current one.");
   CurContext = OCD;
+  ActOnDocumentableDecl(IDecl);
   return IDecl;
 }
 
@@ -10339,6 +10391,8 @@ Decl *Sema::ActOnEnumConstant(Scope *S, Decl *theEnumDecl, Decl *lastEnumConst,
     PushOnScopeChains(New, S);
   }
 
+  ActOnDocumentableDecl(New);
+
   return New;
 }
 
index 32708090c9d977236a2bad44941511eb74c3f842..fa42fdd827c2ae32156545e272b759485dabf93e 100644 (file)
@@ -5422,6 +5422,8 @@ Decl *Sema::ActOnStartNamespaceDef(Scope *NamespcScope,
     }
   }
 
+  ActOnDocumentableDecl(Namespc);
+
   // Although we could have an invalid decl (i.e. the namespace name is a
   // redefinition), push it as current DeclContext and try to continue parsing.
   // FIXME: We should be able to push Namespc here, so that the each DeclContext
index d67fb5002a18100563e1c7abdfddc7d59aadd5fd..8d4694662d5e89dba28fa6b8d6eaae03d178374e 100644 (file)
@@ -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 (file)
index 72dd090..0000000
+++ /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 (file)
index 0000000..fa17cac
--- /dev/null
@@ -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 (file)
index 0000000..273867d
--- /dev/null
@@ -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 (file)
index 0000000..3949b2e
--- /dev/null
@@ -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 (file)
index 0000000..0a02f7b
--- /dev/null
@@ -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
+
index b1938023adc64b68d3e4708feafaa4a8433e4482..31f352414f902e4b637285567f248180ebe3010f 100644 (file)
@@ -39,6 +39,7 @@ static const DiagnosticRecord BuiltinDiagnosticsByID[] = {
 #include "clang/Basic/DiagnosticLexKinds.inc"
 #include "clang/Basic/DiagnosticParseKinds.inc"
 #include "clang/Basic/DiagnosticASTKinds.inc"
+#include "clang/Basic/DiagnosticCommentKinds.inc"
 #include "clang/Basic/DiagnosticSemaKinds.inc"
 #include "clang/Basic/DiagnosticAnalysisKinds.inc"
 #undef DIAG
index e1089cc5dce3b6b075dd3c183cd4b84ea9f8bd31..471863924a243dec30a6204e2cc3992477a38652 100644 (file)
@@ -1142,6 +1142,60 @@ TEST_F(CommentLexerTest, HTML14) {
 }
 
 TEST_F(CommentLexerTest, HTML15) {
+  const char *Sources[] = {
+    "// <tag/>",
+    "// <tag />"
+  };
+
+  for (size_t i = 0, e = array_lengthof(Sources); i != e; i++) {
+    std::vector<Token> Toks;
+
+    lexString(Sources[i], Toks);
+
+    ASSERT_EQ(4U, Toks.size());
+
+    ASSERT_EQ(tok::text,               Toks[0].getKind());
+    ASSERT_EQ(StringRef(" "),          Toks[0].getText());
+
+    ASSERT_EQ(tok::html_tag_open,      Toks[1].getKind());
+    ASSERT_EQ(StringRef("tag"),        Toks[1].getHTMLTagOpenName());
+
+    ASSERT_EQ(tok::html_slash_greater, Toks[2].getKind());
+
+    ASSERT_EQ(tok::newline,            Toks[3].getKind());
+  }
+}
+
+TEST_F(CommentLexerTest, HTML16) {
+  const char *Sources[] = {
+    "// <tag/ Aaa",
+    "// <tag / Aaa"
+  };
+
+  for (size_t i = 0, e = array_lengthof(Sources); i != e; i++) {
+    std::vector<Token> Toks;
+
+    lexString(Sources[i], Toks);
+
+    ASSERT_EQ(5U, Toks.size());
+
+    ASSERT_EQ(tok::text,               Toks[0].getKind());
+    ASSERT_EQ(StringRef(" "),          Toks[0].getText());
+
+    ASSERT_EQ(tok::html_tag_open,      Toks[1].getKind());
+    ASSERT_EQ(StringRef("tag"),        Toks[1].getHTMLTagOpenName());
+
+    ASSERT_EQ(tok::text,               Toks[2].getKind());
+    ASSERT_EQ(StringRef("/"),          Toks[2].getText());
+
+    ASSERT_EQ(tok::text,               Toks[3].getKind());
+    ASSERT_EQ(StringRef(" Aaa"),       Toks[3].getText());
+
+    ASSERT_EQ(tok::newline,            Toks[4].getKind());
+  }
+}
+
+TEST_F(CommentLexerTest, HTML17) {
   const char *Source = "// </";
 
   std::vector<Token> Toks;
@@ -1159,8 +1213,7 @@ TEST_F(CommentLexerTest, HTML15) {
   ASSERT_EQ(tok::newline,        Toks[2].getKind());
 }
 
-
-TEST_F(CommentLexerTest, HTML16) {
+TEST_F(CommentLexerTest, HTML18) {
   const char *Source = "// </@";
 
   std::vector<Token> Toks;
@@ -1181,7 +1234,7 @@ TEST_F(CommentLexerTest, HTML16) {
   ASSERT_EQ(tok::newline,        Toks[3].getKind());
 }
 
-TEST_F(CommentLexerTest, HTML17) {
+TEST_F(CommentLexerTest, HTML19) {
   const char *Source = "// </tag";
 
   std::vector<Token> Toks;
@@ -1199,7 +1252,7 @@ TEST_F(CommentLexerTest, HTML17) {
   ASSERT_EQ(tok::newline,        Toks[2].getKind());
 }
 
-TEST_F(CommentLexerTest, HTML18) {
+TEST_F(CommentLexerTest, HTML20) {
   const char *Sources[] = {
     "// </tag>",
     "// </ tag>",
index d5dd0a9c56b14d8eae3fbeb29a18c0f4608fb01d..c779a881d40ea925f869c92aa605414a8331a1da 100644 (file)
@@ -57,8 +57,8 @@ FullComment *CommentParserTest::parseString(const char *Source) {
   comments::Lexer L(Begin, CommentOptions(),
                     Source, Source + strlen(Source));
 
-  comments::Sema S(Allocator);
-  comments::Parser P(L, S, Allocator);
+  comments::Sema S(Allocator, SourceMgr, Diags);
+  comments::Parser P(L, S, Allocator, SourceMgr, Diags);
   comments::FullComment *FC = P.parseFullComment();
 
   if (DEBUG) {
@@ -292,6 +292,25 @@ struct NoArgs {};
   return ::testing::AssertionSuccess();
 }
 
+struct SelfClosing {};
+
+::testing::AssertionResult HasHTMLOpenTagAt(const Comment *C,
+                                            size_t Idx,
+                                            HTMLOpenTagComment *&HOT,
+                                            StringRef TagName,
+                                            SelfClosing) {
+  ::testing::AssertionResult AR = HasHTMLOpenTagAt(C, Idx, HOT, TagName);
+  if (!AR)
+    return AR;
+
+  if (!HOT->isSelfClosing())
+    return ::testing::AssertionFailure()
+        << "HTMLOpenTagComment is not self-closing";
+
+  return ::testing::AssertionSuccess();
+}
+
+
 struct NoAttrs {};
 
 ::testing::AssertionResult HasHTMLOpenTagAt(const Comment *C,
@@ -303,6 +322,10 @@ struct NoAttrs {};
   if (!AR)
     return AR;
 
+  if (HOT->isSelfClosing())
+    return ::testing::AssertionFailure()
+        << "HTMLOpenTagComment is self-closing";
+
   if (HOT->getAttrCount() != 0)
     return ::testing::AssertionFailure()
         << "HTMLOpenTagComment has " << HOT->getAttrCount() << " attr(s), "
@@ -321,6 +344,10 @@ struct NoAttrs {};
   if (!AR)
     return AR;
 
+  if (HOT->isSelfClosing())
+    return ::testing::AssertionFailure()
+        << "HTMLOpenTagComment is self-closing";
+
   if (HOT->getAttrCount() != 1)
     return ::testing::AssertionFailure()
         << "HTMLOpenTagComment has " << HOT->getAttrCount() << " attr(s), "
@@ -836,6 +863,28 @@ TEST_F(CommentParserTest, HTML1) {
 }
 
 TEST_F(CommentParserTest, HTML2) {
+  const char *Sources[] = {
+    "// <br/>",
+    "// <br />"
+  };
+
+  for (size_t i = 0, e = array_lengthof(Sources); i != e; i++) {
+    FullComment *FC = parseString(Sources[i]);
+    ASSERT_TRUE(HasChildCount(FC, 1));
+
+    {
+      ParagraphComment *PC;
+      HTMLOpenTagComment *HOT;
+      ASSERT_TRUE(GetChildAt(FC, 0, PC));
+
+      ASSERT_TRUE(HasChildCount(PC, 2));
+        ASSERT_TRUE(HasTextAt(PC, 0, " "));
+        ASSERT_TRUE(HasHTMLOpenTagAt(PC, 1, HOT, "br", SelfClosing()));
+    }
+  }
+}
+
+TEST_F(CommentParserTest, HTML3) {
   const char *Sources[] = {
     "// <a href",
     "// <a href ",
@@ -859,7 +908,7 @@ TEST_F(CommentParserTest, HTML2) {
   }
 }
 
-TEST_F(CommentParserTest, HTML3) {
+TEST_F(CommentParserTest, HTML4) {
   const char *Sources[] = {
     "// <a href=\"bbb\"",
     "// <a href=\"bbb\">",
@@ -881,7 +930,7 @@ TEST_F(CommentParserTest, HTML3) {
   }
 }
 
-TEST_F(CommentParserTest, HTML4) {
+TEST_F(CommentParserTest, HTML5) {
   const char *Sources[] = {
     "// </a",
     "// </a>",
@@ -904,7 +953,7 @@ TEST_F(CommentParserTest, HTML4) {
   }
 }
 
-TEST_F(CommentParserTest, HTML5) {
+TEST_F(CommentParserTest, HTML6) {
   const char *Source =
     "// <pre>\n"
     "// Aaa\n"
index b25243f0f70f8dc51b36d4a633e6c177a252312d..31cd5de1b741a832fab03f2599e9f3ae09452f04 100644 (file)
@@ -10,6 +10,6 @@
 CLANG_LEVEL = ../..
 TESTNAME = AST
 LINK_COMPONENTS := support mc
-USEDLIBS = clangAST.a clangBasic.a
+USEDLIBS = clangAST.a clangLex.a clangBasic.a
 
 include $(CLANG_LEVEL)/unittests/Makefile