Comment AST: TableGen'ize all command lists in CommentCommandTraits.cpp.

Now we have a list of all commands.  This is a good thing in itself, but it
also enables us to easily implement typo correction for command names.

With this change we have objects that contain information about each command,
so it makes sense to resolve command name just once during lexing (currently we
store command names as strings and do a linear search every time some property
value is needed).  Thus comment token and AST nodes were changed to contain a
command ID -- index into a tables of builtin and registered commands.  Unknown
commands are registered during parsing and thus are also uniformly assigned an
ID.  Using an ID instead of a StringRef is also a nice memory optimization
since ID is a small integer that fits into a common bitfield in Comment class.

This change implies that to get any information about a command (even a command
name) we need a CommandTraits object to resolve the command ID to CommandInfo*.
Currently a fresh temporary CommandTraits object is created whenever it is
needed since it does not have any state.  But with this change it has state --
new commands can be registered, so a CommandTraits object was added to
ASTContext.

Also, in libclang CXComment has to be expanded to include a CXTranslationUnit
so that all functions working on comment AST nodes can get a CommandTraits
object.  This breaks binary compatibility of CXComment APIs.

Now clang_FullComment_getAsXML(CXTranslationUnit TU, CXComment CXC) doesn't
need TU parameter anymore, so it was removed.  This is a source-incompatible
change for this C API.



git-svn-id: https://llvm.org/svn/llvm-project/cfe/trunk@163540 91177308-0d34-0410-b5e6-96231b3b80d8
diff --git a/include/clang-c/Index.h b/include/clang-c/Index.h
index cf21d23..912e657 100644
--- a/include/clang-c/Index.h
+++ b/include/clang-c/Index.h
@@ -2040,7 +2040,8 @@
  * \brief A comment AST node.
  */
 typedef struct {
-  const void *Data;
+  const void *ASTNode;
+  CXTranslationUnit TranslationUnit;
 } CXComment;
 
 /**
@@ -3692,14 +3693,11 @@
  * A Relax NG schema for the XML can be found in comment-xml-schema.rng file
  * inside clang source tree.
  *
- * \param TU the translation unit \c Comment belongs to.
- *
  * \param Comment a \c CXComment_FullComment AST node.
  *
  * \returns string containing an XML document.
  */
-CINDEX_LINKAGE CXString clang_FullComment_getAsXML(CXTranslationUnit TU,
-                                                   CXComment Comment);
+CINDEX_LINKAGE CXString clang_FullComment_getAsXML(CXComment Comment);
 
 /**
  * @}
diff --git a/include/clang/AST/ASTContext.h b/include/clang/AST/ASTContext.h
index 93ed1c4..0897582 100644
--- a/include/clang/AST/ASTContext.h
+++ b/include/clang/AST/ASTContext.h
@@ -29,6 +29,7 @@
 #include "clang/AST/Type.h"
 #include "clang/AST/CanonicalType.h"
 #include "clang/AST/RawCommentList.h"
+#include "clang/AST/CommentCommandTraits.h"
 #include "llvm/ADT/DenseMap.h"
 #include "llvm/ADT/FoldingSet.h"
 #include "llvm/ADT/IntrusiveRefCntPtr.h"
@@ -529,6 +530,14 @@
   /// Returns NULL if no comment is attached.
   comments::FullComment *getCommentForDecl(const Decl *D) const;
 
+private:
+  mutable comments::CommandTraits CommentCommandTraits;
+
+public:
+  comments::CommandTraits &getCommentCommandTraits() const {
+    return CommentCommandTraits;
+  }
+
   /// \brief Retrieve the attributes for the given declaration.
   AttrVec& getDeclAttrs(const Decl *D);
 
diff --git a/include/clang/AST/CMakeLists.txt b/include/clang/AST/CMakeLists.txt
index 339250b..4c4c0fb 100644
--- a/include/clang/AST/CMakeLists.txt
+++ b/include/clang/AST/CMakeLists.txt
@@ -28,3 +28,7 @@
   SOURCE CommentHTMLTags.td
   TARGET ClangCommentHTMLTagsProperties)
 
+clang_tablegen(CommentCommandInfo.inc -gen-clang-comment-command-info
+  SOURCE CommentCommands.td
+  TARGET ClangCommentCommandInfo)
+
diff --git a/include/clang/AST/Comment.h b/include/clang/AST/Comment.h
index 09d0a26..cf43fc3 100644
--- a/include/clang/AST/Comment.h
+++ b/include/clang/AST/Comment.h
@@ -16,6 +16,7 @@
 
 #include "clang/Basic/SourceLocation.h"
 #include "clang/AST/Type.h"
+#include "clang/AST/CommentCommandTraits.h"
 #include "llvm/ADT/ArrayRef.h"
 #include "llvm/ADT/StringRef.h"
 
@@ -74,8 +75,9 @@
     unsigned : NumInlineContentCommentBits;
 
     unsigned RenderKind : 2;
+    unsigned CommandID : 8;
   };
-  enum { NumInlineCommandCommentBits = NumInlineContentCommentBits + 1 };
+  enum { NumInlineCommandCommentBits = NumInlineContentCommentBits + 10 };
 
   class HTMLStartTagCommentBitfields {
     friend class HTMLStartTagComment;
@@ -101,10 +103,19 @@
   };
   enum { NumParagraphCommentBits = NumCommentBits + 2 };
 
+  class BlockCommandCommentBitfields {
+    friend class BlockCommandComment;
+
+    unsigned : NumCommentBits;
+
+    unsigned CommandID : 8;
+  };
+  enum { NumBlockCommandCommentBits = NumCommentBits + 8 };
+
   class ParamCommandCommentBitfields {
     friend class ParamCommandComment;
 
-    unsigned : NumCommentBits;
+    unsigned : NumBlockCommandCommentBits;
 
     /// Parameter passing direction, see ParamCommandComment::PassDirection.
     unsigned Direction : 2;
@@ -112,7 +123,7 @@
     /// True if direction was specified explicitly in the comment.
     unsigned IsDirectionExplicit : 1;
   };
-  enum { NumParamCommandCommentBits = 11 };
+  enum { NumParamCommandCommentBits = NumBlockCommandCommentBits + 3 };
 
   union {
     CommentBitfields CommentBits;
@@ -121,6 +132,7 @@
     InlineCommandCommentBitfields InlineCommandCommentBits;
     HTMLStartTagCommentBitfields HTMLStartTagCommentBits;
     ParagraphCommentBitfields ParagraphCommentBits;
+    BlockCommandCommentBitfields BlockCommandCommentBits;
     ParamCommandCommentBitfields ParamCommandCommentBits;
   };
 
@@ -158,8 +170,9 @@
   const char *getCommentKindName() const;
 
   LLVM_ATTRIBUTE_USED void dump() const;
-  LLVM_ATTRIBUTE_USED void dump(SourceManager &SM) const;
-  void dump(llvm::raw_ostream &OS, SourceManager *SM) const;
+  LLVM_ATTRIBUTE_USED void dump(const ASTContext &Context) const;
+  void dump(llvm::raw_ostream &OS, const CommandTraits *Traits,
+            const SourceManager *SM) const;
 
   static bool classof(const Comment *) { return true; }
 
@@ -273,21 +286,19 @@
   };
 
 protected:
-  /// Command name.
-  StringRef Name;
-
   /// Command arguments.
   llvm::ArrayRef<Argument> Args;
 
 public:
   InlineCommandComment(SourceLocation LocBegin,
                        SourceLocation LocEnd,
-                       StringRef Name,
+                       unsigned CommandID,
                        RenderKind RK,
                        llvm::ArrayRef<Argument> Args) :
       InlineContentComment(InlineCommandCommentKind, LocBegin, LocEnd),
-      Name(Name), Args(Args) {
+      Args(Args) {
     InlineCommandCommentBits.RenderKind = RK;
+    InlineCommandCommentBits.CommandID = CommandID;
   }
 
   static bool classof(const Comment *C) {
@@ -300,8 +311,12 @@
 
   child_iterator child_end() const { return NULL; }
 
-  StringRef getCommandName() const {
-    return Name;
+  unsigned getCommandID() const {
+    return InlineCommandCommentBits.CommandID;
+  }
+
+  StringRef getCommandName(const CommandTraits &Traits) const {
+    return Traits.getCommandInfo(getCommandID())->Name;
   }
 
   SourceRange getCommandNameRange() const {
@@ -566,9 +581,6 @@
   };
 
 protected:
-  /// Command name.
-  StringRef Name;
-
   /// Word-like arguments.
   llvm::ArrayRef<Argument> Args;
 
@@ -578,21 +590,21 @@
   BlockCommandComment(CommentKind K,
                       SourceLocation LocBegin,
                       SourceLocation LocEnd,
-                      StringRef Name) :
+                      unsigned CommandID) :
       BlockContentComment(K, LocBegin, LocEnd),
-      Name(Name),
       Paragraph(NULL) {
-    setLocation(getCommandNameRange().getBegin());
+    setLocation(getCommandNameBeginLoc());
+    BlockCommandCommentBits.CommandID = CommandID;
   }
 
 public:
   BlockCommandComment(SourceLocation LocBegin,
                       SourceLocation LocEnd,
-                      StringRef Name) :
+                      unsigned CommandID) :
       BlockContentComment(BlockCommandCommentKind, LocBegin, LocEnd),
-      Name(Name),
       Paragraph(NULL) {
-    setLocation(getCommandNameRange().getBegin());
+    setLocation(getCommandNameBeginLoc());
+    BlockCommandCommentBits.CommandID = CommandID;
   }
 
   static bool classof(const Comment *C) {
@@ -610,12 +622,21 @@
     return reinterpret_cast<child_iterator>(&Paragraph + 1);
   }
 
-  StringRef getCommandName() const {
-    return Name;
+  unsigned getCommandID() const {
+    return BlockCommandCommentBits.CommandID;
   }
 
-  SourceRange getCommandNameRange() const {
-    return SourceRange(getLocStart().getLocWithOffset(1),
+  StringRef getCommandName(const CommandTraits &Traits) const {
+    return Traits.getCommandInfo(getCommandID())->Name;
+  }
+
+  SourceLocation getCommandNameBeginLoc() const {
+    return getLocStart().getLocWithOffset(1);
+  }
+
+  SourceRange getCommandNameRange(const CommandTraits &Traits) const {
+    StringRef Name = getCommandName(Traits);
+    return SourceRange(getCommandNameBeginLoc(),
                        getLocStart().getLocWithOffset(1 + Name.size()));
   }
 
@@ -667,8 +688,9 @@
 
   ParamCommandComment(SourceLocation LocBegin,
                       SourceLocation LocEnd,
-                      StringRef Name) :
-      BlockCommandComment(ParamCommandCommentKind, LocBegin, LocEnd, Name),
+                      unsigned CommandID) :
+      BlockCommandComment(ParamCommandCommentKind, LocBegin, LocEnd,
+                          CommandID),
       ParamIndex(InvalidParamIndex) {
     ParamCommandCommentBits.Direction = In;
     ParamCommandCommentBits.IsDirectionExplicit = false;
@@ -748,8 +770,8 @@
 public:
   TParamCommandComment(SourceLocation LocBegin,
                        SourceLocation LocEnd,
-                       StringRef Name) :
-      BlockCommandComment(TParamCommandCommentKind, LocBegin, LocEnd, Name)
+                       unsigned CommandID) :
+      BlockCommandComment(TParamCommandCommentKind, LocBegin, LocEnd, CommandID)
   { }
 
   static bool classof(const Comment *C) {
@@ -830,9 +852,9 @@
 public:
   VerbatimBlockComment(SourceLocation LocBegin,
                        SourceLocation LocEnd,
-                       StringRef Name) :
+                       unsigned CommandID) :
       BlockCommandComment(VerbatimBlockCommentKind,
-                          LocBegin, LocEnd, Name)
+                          LocBegin, LocEnd, CommandID)
   { }
 
   static bool classof(const Comment *C) {
@@ -882,12 +904,12 @@
 public:
   VerbatimLineComment(SourceLocation LocBegin,
                       SourceLocation LocEnd,
-                      StringRef Name,
+                      unsigned CommandID,
                       SourceLocation TextBegin,
                       StringRef Text) :
       BlockCommandComment(VerbatimLineCommentKind,
                           LocBegin, LocEnd,
-                          Name),
+                          CommandID),
       Text(Text),
       TextBegin(TextBegin)
   { }
diff --git a/include/clang/AST/CommentCommandTraits.h b/include/clang/AST/CommentCommandTraits.h
index 5f0269a..07d9a48 100644
--- a/include/clang/AST/CommentCommandTraits.h
+++ b/include/clang/AST/CommentCommandTraits.h
@@ -19,135 +19,124 @@
 #include "clang/Basic/LLVM.h"
 #include "llvm/ADT/StringRef.h"
 #include "llvm/ADT/SmallVector.h"
-#include "llvm/ADT/StringSwitch.h"
+#include "llvm/Support/Allocator.h"
+#include "llvm/Support/ErrorHandling.h"
 
 namespace clang {
 namespace comments {
 
-/// This class provides informaiton about commands that can be used
-/// in comments.
-class CommandTraits {
-public:
-  CommandTraits() { }
+/// \brief Information about a single command.
+///
+/// When reordering, adding or removing members please update the corresponding
+/// TableGen backend.
+struct CommandInfo {
+  unsigned getID() const {
+    return ID;
+  }
 
-  /// \brief Check if a given command is a verbatim-like block command.
+  const char *Name;
+
+  /// Name of the command that ends the verbatim block.
+  const char *EndCommandName;
+
+  unsigned ID : 8;
+
+  /// Number of word-like arguments for a given block command, except for
+  /// \\param and \\tparam commands -- these have special argument parsers.
+  unsigned NumArgs : 4;
+
+  /// True if this command is a inline command (of any kind).
+  unsigned IsInlineCommand : 1;
+
+  /// True if this command is a block command (of any kind).
+  unsigned IsBlockCommand : 1;
+
+  /// True if this command is introducing a brief documentation
+  /// paragraph (\\brief or an alias).
+  unsigned IsBriefCommand : 1;
+
+  /// True if this command is \\returns or an alias.
+  unsigned IsReturnsCommand : 1;
+
+  /// True if this command is introducing documentation for a function
+  /// parameter (\\param or an alias).
+  unsigned IsParamCommand : 1;
+
+  /// True if this command is introducing documentation for
+  /// a template parameter (\\tparam or an alias).
+  unsigned IsTParamCommand : 1;
+
+  /// \brief True if this command is a verbatim-like block command.
   ///
   /// A verbatim-like block command eats every character (except line starting
   /// decorations) until matching end command is seen or comment end is hit.
-  ///
-  /// \param StartName name of the command that starts the verbatim block.
-  /// \param [out] EndName name of the command that ends the verbatim block.
-  ///
-  /// \returns true if a given command is a verbatim block command.
-  bool isVerbatimBlockCommand(StringRef StartName, StringRef &EndName) const;
+  unsigned IsVerbatimBlockCommand : 1;
 
-  /// \brief Register a new verbatim block command.
-  void addVerbatimBlockCommand(StringRef StartName, StringRef EndName);
+  /// \brief True if this command is an end command for a verbatim-like block.
+  unsigned IsVerbatimBlockEndCommand : 1;
 
-  /// \brief Check if a given command is a verbatim line command.
+  /// \brief True if this command is a verbatim line command.
   ///
   /// A verbatim-like line command eats everything until a newline is seen or
   /// comment end is hit.
-  bool isVerbatimLineCommand(StringRef Name) const;
+  unsigned IsVerbatimLineCommand : 1;
 
-  /// \brief Check if a given command is a command that contains a declaration
-  /// for the entity being documented.
+  /// \brief True if this command contains a declaration for the entity being
+  /// documented.
   ///
   /// For example:
   /// \code
   ///   \fn void f(int a);
   /// \endcode
-  bool isDeclarationCommand(StringRef Name) const;
+  unsigned IsDeclarationCommand : 1;
 
-  /// \brief Register a new verbatim line command.
-  void addVerbatimLineCommand(StringRef Name);
-
-  /// \brief Check if a given command is a block command (of any kind).
-  bool isBlockCommand(StringRef Name) const;
-
-  /// \brief Check if a given command is introducing documentation for
-  /// a function parameter (\\param or an alias).
-  bool isParamCommand(StringRef Name) const;
-
-  /// \brief Check if a given command is introducing documentation for
-  /// a template parameter (\\tparam or an alias).
-  bool isTParamCommand(StringRef Name) const;
-
-  /// \brief Check if a given command is introducing a brief documentation
-  /// paragraph (\\brief or an alias).
-  bool isBriefCommand(StringRef Name) const;
-
-  /// \brief Check if a given command is \\brief or an alias.
-  bool isReturnsCommand(StringRef Name) const;
-
-  /// \returns the number of word-like arguments for a given block command,
-  /// except for \\param and \\tparam commands -- these have special argument
-  /// parsers.
-  unsigned getBlockCommandNumArgs(StringRef Name) const;
-
-  /// \brief Check if a given command is a inline command (of any kind).
-  bool isInlineCommand(StringRef Name) const;
-
-private:
-  struct VerbatimBlockCommand {
-    StringRef StartName;
-    StringRef EndName;
-  };
-
-  typedef SmallVector<VerbatimBlockCommand, 4> VerbatimBlockCommandVector;
-
-  /// Registered additional verbatim-like block commands.
-  VerbatimBlockCommandVector VerbatimBlockCommands;
-
-  struct VerbatimLineCommand {
-    StringRef Name;
-  };
-
-  typedef SmallVector<VerbatimLineCommand, 4> VerbatimLineCommandVector;
-
-  /// Registered verbatim-like line commands.
-  VerbatimLineCommandVector VerbatimLineCommands;
+  /// \brief True if this command is unknown.  This \c CommandInfo object was
+  /// created during parsing.
+  unsigned IsUnknownCommand : 1;
 };
 
-inline bool CommandTraits::isBlockCommand(StringRef Name) const {
-  return isBriefCommand(Name) || isReturnsCommand(Name) ||
-      isParamCommand(Name) || isTParamCommand(Name) ||
-      llvm::StringSwitch<bool>(Name)
-      .Case("author", true)
-      .Case("authors", true)
-      .Case("pre", true)
-      .Case("post", true)
-      .Default(false);
-}
+/// This class provides information about commands that can be used
+/// in comments.
+class CommandTraits {
+public:
+  CommandTraits(llvm::BumpPtrAllocator &Allocator);
 
-inline bool CommandTraits::isParamCommand(StringRef Name) const {
-  return Name == "param";
-}
+  /// \returns a CommandInfo object for a given command name or
+  /// NULL if no CommandInfo object exists for this command.
+  const CommandInfo *getCommandInfoOrNULL(StringRef Name) const;
 
-inline bool CommandTraits::isTParamCommand(StringRef Name) const {
-  return Name == "tparam" || // Doxygen
-         Name == "templatefield"; // HeaderDoc
-}
+  const CommandInfo *getCommandInfo(StringRef Name) const {
+    if (const CommandInfo *Info = getCommandInfoOrNULL(Name))
+      return Info;
+    llvm_unreachable("the command should be known");
+  }
 
-inline bool CommandTraits::isBriefCommand(StringRef Name) const {
-  return Name == "brief" || Name == "short";
-}
+  const CommandInfo *getCommandInfo(unsigned CommandID) const;
 
-inline bool CommandTraits::isReturnsCommand(StringRef Name) const {
-  return Name == "returns" || Name == "return" || Name == "result";
-}
+  const CommandInfo *registerUnknownCommand(StringRef CommandName);
 
-inline unsigned CommandTraits::getBlockCommandNumArgs(StringRef Name) const {
-  return 0;
-}
+  /// \returns a CommandInfo object for a given command name or
+  /// NULL if \c Name is not a builtin command.
+  static const CommandInfo *getBuiltinCommandInfo(StringRef Name);
 
-inline bool CommandTraits::isInlineCommand(StringRef Name) const {
-  return llvm::StringSwitch<bool>(Name)
-      .Case("b", true)
-      .Cases("c", "p", true)
-      .Cases("a", "e", "em", true)
-      .Default(false);
-}
+  /// \returns a CommandInfo object for a given command ID or
+  /// NULL if \c CommandID is not a builtin command.
+  static const CommandInfo *getBuiltinCommandInfo(unsigned CommandID);
+
+private:
+  CommandTraits(const CommandTraits &) LLVM_DELETED_FUNCTION;
+  void operator=(const CommandTraits &) LLVM_DELETED_FUNCTION;
+
+  const CommandInfo *getRegisteredCommandInfo(StringRef Name) const;
+  const CommandInfo *getRegisteredCommandInfo(unsigned CommandID) const;
+
+  unsigned NextID;
+
+  /// Allocator for CommandInfo objects.
+  llvm::BumpPtrAllocator &Allocator;
+
+  SmallVector<CommandInfo *, 4> RegisteredCommands;
+};
 
 } // end namespace comments
 } // end namespace clang
diff --git a/include/clang/AST/CommentCommands.td b/include/clang/AST/CommentCommands.td
new file mode 100644
index 0000000..16ce4fc
--- /dev/null
+++ b/include/clang/AST/CommentCommands.td
@@ -0,0 +1,133 @@
+class Command<string name> {
+  string Name = name;
+  string EndCommandName = "";
+
+  int NumArgs = 0;
+
+  bit IsInlineCommand = 0;
+
+  bit IsBlockCommand = 0;
+  bit IsBriefCommand = 0;
+  bit IsReturnsCommand = 0;
+  bit IsParamCommand = 0;
+  bit IsTParamCommand = 0;
+
+  bit IsVerbatimBlockCommand = 0;
+  bit IsVerbatimBlockEndCommand = 0;
+  bit IsVerbatimLineCommand = 0;
+  bit IsDeclarationCommand = 0;
+}
+
+class InlineCommand<string name> : Command<name> {
+  let IsInlineCommand = 1;
+}
+
+class BlockCommand<string name> : Command<name> {
+  let IsBlockCommand = 1;
+}
+
+class VerbatimBlockCommand<string name> : Command<name> {
+  let EndCommandName = name;
+  let IsVerbatimBlockCommand = 1;
+}
+
+multiclass VerbatimBlockCommand<string name, string endCommandName> {
+  def Begin : Command<name> {
+    let EndCommandName = endCommandName;
+    let IsVerbatimBlockCommand = 1;
+  }
+
+  def End : Command<endCommandName> {
+    let IsVerbatimBlockEndCommand = 1;
+  }
+}
+
+class VerbatimLineCommand<string name> : Command<name> {
+  let IsVerbatimLineCommand = 1;
+}
+
+class DeclarationVerbatimLineCommand<string name> :
+      VerbatimLineCommand<name> {
+  let IsDeclarationCommand = 1;
+}
+
+def B  : InlineCommand<"b">;
+def C  : InlineCommand<"c">;
+def P  : InlineCommand<"p">;
+def A  : InlineCommand<"a">;
+def E  : InlineCommand<"e">;
+def Em : InlineCommand<"em">;
+
+def Brief : BlockCommand<"brief"> { let IsBriefCommand = 1; }
+def Short : BlockCommand<"short"> { let IsBriefCommand = 1; }
+
+def Returns : BlockCommand<"returns"> { let IsReturnsCommand = 1; }
+def Return  : BlockCommand<"return"> { let IsReturnsCommand = 1; }
+def Result  : BlockCommand<"result"> { let IsReturnsCommand = 1; }
+
+def Param : BlockCommand<"param"> { let IsParamCommand = 1; }
+
+// Doxygen
+def Tparam : BlockCommand<"tparam"> { let IsTParamCommand = 1; }
+
+// HeaderDoc
+def Templatefield : BlockCommand<"templatefield"> { let IsTParamCommand = 1; }
+
+def Author  : BlockCommand<"author">;
+def Authors : BlockCommand<"authors">;
+def Pre     : BlockCommand<"pre">;
+def Post    : BlockCommand<"post">;
+
+defm Code      : VerbatimBlockCommand<"code", "endcode">;
+defm Verbatim  : VerbatimBlockCommand<"verbatim", "endverbatim">;
+defm Htmlonly  : VerbatimBlockCommand<"htmlonly", "endhtmlonly">;
+defm Latexonly : VerbatimBlockCommand<"latexonly", "endlatexonly">;
+defm Xmlonly   : VerbatimBlockCommand<"xmlonly", "endxmlonly">;
+defm Manonly   : VerbatimBlockCommand<"manonly", "endmanonly">;
+defm Rtfonly   : VerbatimBlockCommand<"rtfonly", "endrtfonly">;
+
+defm Dot : VerbatimBlockCommand<"dot", "enddot">;
+defm Msc : VerbatimBlockCommand<"msc", "endmsc">;
+
+// These commands have special support in lexer.
+def  FDollar  : VerbatimBlockCommand<"f$">; // Inline LaTeX formula
+defm FBracket : VerbatimBlockCommand<"f[", "f]">; // Displayed LaTeX formula
+defm FBrace   : VerbatimBlockCommand<"f{", "f}">; // LaTeX environment
+
+def Defgroup   : VerbatimLineCommand<"defgroup">;
+def Ingroup    : VerbatimLineCommand<"ingroup">;
+def Addtogroup : VerbatimLineCommand<"addtogroup">;
+def Weakgroup  : VerbatimLineCommand<"weakgroup">;
+def Name       : VerbatimLineCommand<"name">;
+
+def Section       : VerbatimLineCommand<"section">;
+def Subsection    : VerbatimLineCommand<"subsection">;
+def Subsubsection : VerbatimLineCommand<"subsubsection">;
+def Paragraph     : VerbatimLineCommand<"paragraph">;
+
+def Mainpage : VerbatimLineCommand<"mainpage">;
+def Subpage  : VerbatimLineCommand<"subpage">;
+def Ref      : VerbatimLineCommand<"ref">;
+
+// Doxygen commands.
+def Fn       : DeclarationVerbatimLineCommand<"fn">;
+def Var      : DeclarationVerbatimLineCommand<"var">;
+def Property : DeclarationVerbatimLineCommand<"property">;
+def Typedef  : DeclarationVerbatimLineCommand<"typedef">;
+def Overload : DeclarationVerbatimLineCommand<"overload">;
+
+// HeaderDoc commands.
+def Class     : DeclarationVerbatimLineCommand<"class">;
+def Interface : DeclarationVerbatimLineCommand<"interface">;
+def Protocol  : DeclarationVerbatimLineCommand<"protocol">;
+def Category  : DeclarationVerbatimLineCommand<"category">;
+def Template  : DeclarationVerbatimLineCommand<"template">;
+def Function  : DeclarationVerbatimLineCommand<"function">;
+def Method    : DeclarationVerbatimLineCommand<"method">;
+def Callback  : DeclarationVerbatimLineCommand<"callback">;
+def Const     : DeclarationVerbatimLineCommand<"const">;
+def Constant  : DeclarationVerbatimLineCommand<"constant">;
+def Struct    : DeclarationVerbatimLineCommand<"struct">;
+def Union     : DeclarationVerbatimLineCommand<"union">;
+def Enum      : DeclarationVerbatimLineCommand<"enum">;
+
diff --git a/include/clang/AST/CommentLexer.h b/include/clang/AST/CommentLexer.h
index e720521..99b95d3 100644
--- a/include/clang/AST/CommentLexer.h
+++ b/include/clang/AST/CommentLexer.h
@@ -26,6 +26,7 @@
 
 class Lexer;
 class TextTokenRetokenizer;
+struct CommandInfo;
 class CommandTraits;
 
 namespace tok {
@@ -33,6 +34,7 @@
   eof,
   newline,
   text,
+  unknown_command,
   command,
   verbatim_block_begin,
   verbatim_block_line,
@@ -65,8 +67,14 @@
   unsigned Length;
 
   /// Contains text value associated with a token.
-  const char *TextPtr1;
-  unsigned TextLen1;
+  const char *TextPtr;
+
+  /// Integer value associated with a token.
+  ///
+  /// If the token is a konwn command, contains command ID and TextPtr is
+  /// unused (command spelling can be found with CommandTraits).  Otherwise,
+  /// contains the length of the string that starts at TextPtr.
+  unsigned IntVal;
 
 public:
   SourceLocation getLocation() const LLVM_READONLY { return Loc; }
@@ -89,113 +97,120 @@
 
   StringRef getText() const LLVM_READONLY {
     assert(is(tok::text));
-    return StringRef(TextPtr1, TextLen1);
+    return StringRef(TextPtr, IntVal);
   }
 
   void setText(StringRef Text) {
     assert(is(tok::text));
-    TextPtr1 = Text.data();
-    TextLen1 = Text.size();
+    TextPtr = Text.data();
+    IntVal = Text.size();
   }
 
-  StringRef getCommandName() const LLVM_READONLY {
+  StringRef getUnknownCommandName() const LLVM_READONLY {
+    assert(is(tok::unknown_command));
+    return StringRef(TextPtr, IntVal);
+  }
+
+  void setUnknownCommandName(StringRef Name) {
+    assert(is(tok::unknown_command));
+    TextPtr = Name.data();
+    IntVal = Name.size();
+  }
+
+  unsigned getCommandID() const LLVM_READONLY {
     assert(is(tok::command));
-    return StringRef(TextPtr1, TextLen1);
+    return IntVal;
   }
 
-  void setCommandName(StringRef Name) {
+  void setCommandID(unsigned ID) {
     assert(is(tok::command));
-    TextPtr1 = Name.data();
-    TextLen1 = Name.size();
+    IntVal = ID;
   }
 
-  StringRef getVerbatimBlockName() const LLVM_READONLY {
+  unsigned getVerbatimBlockID() const LLVM_READONLY {
     assert(is(tok::verbatim_block_begin) || is(tok::verbatim_block_end));
-    return StringRef(TextPtr1, TextLen1);
+    return IntVal;
   }
 
-  void setVerbatimBlockName(StringRef Name) {
+  void setVerbatimBlockID(unsigned ID) {
     assert(is(tok::verbatim_block_begin) || is(tok::verbatim_block_end));
-    TextPtr1 = Name.data();
-    TextLen1 = Name.size();
+    IntVal = ID;
   }
 
   StringRef getVerbatimBlockText() const LLVM_READONLY {
     assert(is(tok::verbatim_block_line));
-    return StringRef(TextPtr1, TextLen1);
+    return StringRef(TextPtr, IntVal);
   }
 
   void setVerbatimBlockText(StringRef Text) {
     assert(is(tok::verbatim_block_line));
-    TextPtr1 = Text.data();
-    TextLen1 = Text.size();
+    TextPtr = Text.data();
+    IntVal = Text.size();
   }
 
-  /// Returns the name of verbatim line command.
-  StringRef getVerbatimLineName() const LLVM_READONLY {
+  unsigned getVerbatimLineID() const LLVM_READONLY {
     assert(is(tok::verbatim_line_name));
-    return StringRef(TextPtr1, TextLen1);
+    return IntVal;
   }
 
-  void setVerbatimLineName(StringRef Name) {
+  void setVerbatimLineID(unsigned ID) {
     assert(is(tok::verbatim_line_name));
-    TextPtr1 = Name.data();
-    TextLen1 = Name.size();
+    IntVal = ID;
   }
 
   StringRef getVerbatimLineText() const LLVM_READONLY {
     assert(is(tok::verbatim_line_text));
-    return StringRef(TextPtr1, TextLen1);
+    return StringRef(TextPtr, IntVal);
   }
 
   void setVerbatimLineText(StringRef Text) {
     assert(is(tok::verbatim_line_text));
-    TextPtr1 = Text.data();
-    TextLen1 = Text.size();
+    TextPtr = Text.data();
+    IntVal = Text.size();
   }
 
   StringRef getHTMLTagStartName() const LLVM_READONLY {
     assert(is(tok::html_start_tag));
-    return StringRef(TextPtr1, TextLen1);
+    return StringRef(TextPtr, IntVal);
   }
 
   void setHTMLTagStartName(StringRef Name) {
     assert(is(tok::html_start_tag));
-    TextPtr1 = Name.data();
-    TextLen1 = Name.size();
+    TextPtr = Name.data();
+    IntVal = Name.size();
   }
 
   StringRef getHTMLIdent() const LLVM_READONLY {
     assert(is(tok::html_ident));
-    return StringRef(TextPtr1, TextLen1);
+    return StringRef(TextPtr, IntVal);
   }
 
   void setHTMLIdent(StringRef Name) {
     assert(is(tok::html_ident));
-    TextPtr1 = Name.data();
-    TextLen1 = Name.size();
+    TextPtr = Name.data();
+    IntVal = Name.size();
   }
 
   StringRef getHTMLQuotedString() const LLVM_READONLY {
     assert(is(tok::html_quoted_string));
-    return StringRef(TextPtr1, TextLen1);
+    return StringRef(TextPtr, IntVal);
   }
 
   void setHTMLQuotedString(StringRef Str) {
     assert(is(tok::html_quoted_string));
-    TextPtr1 = Str.data();
-    TextLen1 = Str.size();
+    TextPtr = Str.data();
+    IntVal = Str.size();
   }
 
   StringRef getHTMLTagEndName() const LLVM_READONLY {
     assert(is(tok::html_end_tag));
-    return StringRef(TextPtr1, TextLen1);
+    return StringRef(TextPtr, IntVal);
   }
 
   void setHTMLTagEndName(StringRef Name) {
     assert(is(tok::html_end_tag));
-    TextPtr1 = Name.data();
-    TextLen1 = Name.size();
+    TextPtr = Name.data();
+    IntVal = Name.size();
   }
 
   void dump(const Lexer &L, const SourceManager &SM) const;
@@ -280,8 +295,8 @@
     Result.setKind(Kind);
     Result.setLength(TokLen);
 #ifndef NDEBUG
-    Result.TextPtr1 = "<UNSET>";
-    Result.TextLen1 = 7;
+    Result.TextPtr = "<UNSET>";
+    Result.IntVal = 7;
 #endif
     BufferPtr = TokEnd;
   }
@@ -308,13 +323,14 @@
 
   void setupAndLexVerbatimBlock(Token &T,
                                 const char *TextBegin,
-                                char Marker, StringRef EndName);
+                                char Marker, const CommandInfo *Info);
 
   void lexVerbatimBlockFirstLine(Token &T);
 
   void lexVerbatimBlockBody(Token &T);
 
-  void setupAndLexVerbatimLine(Token &T, const char *TextBegin);
+  void setupAndLexVerbatimLine(Token &T, const char *TextBegin,
+                               const CommandInfo *Info);
 
   void lexVerbatimLineText(Token &T);
 
diff --git a/include/clang/AST/CommentSema.h b/include/clang/AST/CommentSema.h
index 3252181..5ebf5f0 100644
--- a/include/clang/AST/CommentSema.h
+++ b/include/clang/AST/CommentSema.h
@@ -41,7 +41,7 @@
 
   DiagnosticsEngine &Diags;
 
-  const CommandTraits &Traits;
+  CommandTraits &Traits;
 
   /// Information about the declaration this comment is attached to.
   DeclInfo *ThisDeclInfo;
@@ -68,7 +68,7 @@
 
 public:
   Sema(llvm::BumpPtrAllocator &Allocator, const SourceManager &SourceMgr,
-       DiagnosticsEngine &Diags, const CommandTraits &Traits);
+       DiagnosticsEngine &Diags, CommandTraits &Traits);
 
   void setDecl(const Decl *D);
 
@@ -89,7 +89,7 @@
 
   BlockCommandComment *actOnBlockCommandStart(SourceLocation LocBegin,
                                               SourceLocation LocEnd,
-                                              StringRef Name);
+                                              unsigned CommandID);
 
   void actOnBlockCommandArgs(BlockCommandComment *Command,
                              ArrayRef<BlockCommandComment::Argument> Args);
@@ -99,7 +99,7 @@
 
   ParamCommandComment *actOnParamCommandStart(SourceLocation LocBegin,
                                               SourceLocation LocEnd,
-                                              StringRef Name);
+                                              unsigned CommandID);
 
   void actOnParamCommandDirectionArg(ParamCommandComment *Command,
                                      SourceLocation ArgLocBegin,
@@ -116,7 +116,7 @@
 
   TParamCommandComment *actOnTParamCommandStart(SourceLocation LocBegin,
                                                 SourceLocation LocEnd,
-                                                StringRef Name);
+                                                unsigned CommandID);
 
   void actOnTParamCommandParamNameArg(TParamCommandComment *Command,
                                       SourceLocation ArgLocBegin,
@@ -128,11 +128,11 @@
 
   InlineCommandComment *actOnInlineCommand(SourceLocation CommandLocBegin,
                                            SourceLocation CommandLocEnd,
-                                           StringRef CommandName);
+                                           unsigned CommandID);
 
   InlineCommandComment *actOnInlineCommand(SourceLocation CommandLocBegin,
                                            SourceLocation CommandLocEnd,
-                                           StringRef CommandName,
+                                           unsigned CommandID,
                                            SourceLocation ArgLocBegin,
                                            SourceLocation ArgLocEnd,
                                            StringRef Arg);
@@ -146,7 +146,7 @@
                          StringRef Text);
 
   VerbatimBlockComment *actOnVerbatimBlockStart(SourceLocation Loc,
-                                                StringRef Name);
+                                                unsigned CommandID);
 
   VerbatimBlockLineComment *actOnVerbatimBlockLine(SourceLocation Loc,
                                                    StringRef Text);
@@ -157,7 +157,7 @@
                                 ArrayRef<VerbatimBlockLineComment *> Lines);
 
   VerbatimLineComment *actOnVerbatimLine(SourceLocation LocBegin,
-                                         StringRef Name,
+                                         unsigned CommandID,
                                          SourceLocation TextBegin,
                                          StringRef Text);
 
diff --git a/include/clang/AST/Makefile b/include/clang/AST/Makefile
index dd3a765..7fb33f2 100644
--- a/include/clang/AST/Makefile
+++ b/include/clang/AST/Makefile
@@ -2,7 +2,7 @@
 TD_SRC_DIR = $(PROJ_SRC_DIR)/../Basic
 BUILT_SOURCES = Attrs.inc AttrImpl.inc StmtNodes.inc DeclNodes.inc \
                 CommentNodes.inc CommentHTMLTags.inc \
-                CommentHTMLTagsProperties.inc
+                CommentHTMLTagsProperties.inc CommentCommandInfo.inc
 
 TABLEGEN_INC_FILES_COMMON = 1
 
@@ -45,3 +45,8 @@
 	$(Echo) "Building Clang comment HTML tag properties with tblgen"
 	$(Verb) $(ClangTableGen) -gen-clang-comment-html-tags-properties -o $(call SYSPATH, $@) $<
 
+$(ObjDir)/CommentCommandInfo.inc.tmp : $(PROJ_SRC_DIR)/CommentCommands.td \
+                                              $(CLANG_TBLGEN) $(ObjDir)/.dir
+	$(Echo) "Building Clang comment command info with tblgen"
+	$(Verb) $(ClangTableGen) -gen-clang-comment-command-info -o $(call SYSPATH, $@) $<
+
diff --git a/lib/AST/ASTContext.cpp b/lib/AST/ASTContext.cpp
index d64ed11..9fd40b2 100644
--- a/lib/AST/ASTContext.cpp
+++ b/lib/AST/ASTContext.cpp
@@ -573,6 +573,7 @@
     DeclarationNames(*this),
     ExternalSource(0), Listener(0),
     Comments(SM), CommentsLoaded(false),
+    CommentCommandTraits(BumpAlloc),
     LastSDM(0, 0),
     UniqueBlockByRefTypeID(0) 
 {
diff --git a/lib/AST/CMakeLists.txt b/lib/AST/CMakeLists.txt
index 14c0aa5..d20d77e 100644
--- a/lib/AST/CMakeLists.txt
+++ b/lib/AST/CMakeLists.txt
@@ -64,6 +64,7 @@
   ClangAttrClasses
   ClangAttrList
   ClangAttrImpl
+  ClangCommentCommandInfo
   ClangCommentNodes
   ClangCommentHTMLTags
   ClangCommentHTMLTagsProperties
diff --git a/lib/AST/Comment.cpp b/lib/AST/Comment.cpp
index 2af3896..a6a21f3 100644
--- a/lib/AST/Comment.cpp
+++ b/lib/AST/Comment.cpp
@@ -7,6 +7,7 @@
 //
 //===----------------------------------------------------------------------===//
 
+#include "clang/AST/ASTContext.h"
 #include "clang/AST/Comment.h"
 #include "clang/AST/Decl.h"
 #include "clang/AST/DeclObjC.h"
@@ -37,11 +38,12 @@
   // in CommentDumper.cpp, that object file would be removed by linker because
   // none of its functions are referenced by other object files, despite the
   // LLVM_ATTRIBUTE_USED.
-  dump(llvm::errs(), NULL);
+  dump(llvm::errs(), NULL, NULL);
 }
 
-void Comment::dump(SourceManager &SM) const {
-  dump(llvm::errs(), &SM);
+void Comment::dump(const ASTContext &Context) const {
+  dump(llvm::errs(), &Context.getCommentCommandTraits(),
+       &Context.getSourceManager());
 }
 
 namespace {
diff --git a/lib/AST/CommentBriefParser.cpp b/lib/AST/CommentBriefParser.cpp
index 5a9b10d..95daa7e 100644
--- a/lib/AST/CommentBriefParser.cpp
+++ b/lib/AST/CommentBriefParser.cpp
@@ -79,14 +79,14 @@
     }
 
     if (Tok.is(tok::command)) {
-      StringRef Name = Tok.getCommandName();
-      if (Traits.isBriefCommand(Name)) {
+      const CommandInfo *Info = Traits.getCommandInfo(Tok.getCommandID());
+      if (Info->IsBriefCommand) {
         FirstParagraphOrBrief.clear();
         InBrief = true;
         ConsumeToken();
         continue;
       }
-      if (Traits.isReturnsCommand(Name)) {
+      if (Info->IsReturnsCommand) {
         InReturns = true;
         InBrief = false;
         InFirstParagraph = false;
@@ -95,7 +95,7 @@
         continue;
       }
       // Block commands implicitly start a new paragraph.
-      if (Traits.isBlockCommand(Name)) {
+      if (Info->IsBlockCommand) {
         // We found an implicit paragraph end.
         InFirstParagraph = false;
         if (InBrief)
diff --git a/lib/AST/CommentCommandTraits.cpp b/lib/AST/CommentCommandTraits.cpp
index dc7a0bd..26e2dee 100644
--- a/lib/AST/CommentCommandTraits.cpp
+++ b/lib/AST/CommentCommandTraits.cpp
@@ -8,125 +8,63 @@
 //===----------------------------------------------------------------------===//
 
 #include "clang/AST/CommentCommandTraits.h"
-#include "llvm/ADT/StringSwitch.h"
+#include "llvm/ADT/STLExtras.h"
 
 namespace clang {
 namespace comments {
 
-// TODO: tablegen
+#include "clang/AST/CommentCommandInfo.inc"
 
-bool CommandTraits::isVerbatimBlockCommand(StringRef StartName,
-                                           StringRef &EndName) const {
-  const char *Result = llvm::StringSwitch<const char *>(StartName)
-    .Case("code", "endcode")
-    .Case("verbatim", "endverbatim")
-    .Case("htmlonly", "endhtmlonly")
-    .Case("latexonly", "endlatexonly")
-    .Case("xmlonly", "endxmlonly")
-    .Case("manonly", "endmanonly")
-    .Case("rtfonly", "endrtfonly")
+CommandTraits::CommandTraits(llvm::BumpPtrAllocator &Allocator) :
+    NextID(llvm::array_lengthof(Commands)), Allocator(Allocator)
+{ }
 
-    .Case("dot", "enddot")
-    .Case("msc", "endmsc")
+const CommandInfo *CommandTraits::getCommandInfoOrNULL(StringRef Name) const {
+  if (const CommandInfo *Info = getBuiltinCommandInfo(Name))
+    return Info;
+  return getRegisteredCommandInfo(Name);
+}
 
-    .Case("f$", "f$") // Inline LaTeX formula
-    .Case("f[", "f]") // Displayed LaTeX formula
-    .Case("f{", "f}") // LaTeX environment
+const CommandInfo *CommandTraits::getCommandInfo(unsigned CommandID) const {
+  if (const CommandInfo *Info = getBuiltinCommandInfo(CommandID))
+    return Info;
+  return getRegisteredCommandInfo(CommandID);
+}
 
-    .Default(NULL);
+const CommandInfo *CommandTraits::registerUnknownCommand(StringRef CommandName) {
+  char *Name = Allocator.Allocate<char>(CommandName.size());
+  memcpy(Name, CommandName.data(), CommandName.size());
+  Name[CommandName.size() + 1] = '\0';
 
-  if (Result) {
-    EndName = Result;
-    return true;
+  // Value-initialize (=zero-initialize in this case) a new CommandInfo.
+  CommandInfo *Info = new (Allocator) CommandInfo();
+  Info->Name = Name;
+  Info->ID = NextID++;
+
+  RegisteredCommands.push_back(Info);
+
+  return Info;
+}
+
+const CommandInfo *CommandTraits::getBuiltinCommandInfo(
+                                                  unsigned CommandID) {
+  if (CommandID < llvm::array_lengthof(Commands))
+    return &Commands[CommandID];
+  return NULL;
+}
+
+const CommandInfo *CommandTraits::getRegisteredCommandInfo(
+                                                  StringRef Name) const {
+  for (unsigned i = 0, e = RegisteredCommands.size(); i != e; ++i) {
+    if (RegisteredCommands[i]->Name == Name)
+      return RegisteredCommands[i];
   }
-
-  for (VerbatimBlockCommandVector::const_iterator
-           I = VerbatimBlockCommands.begin(),
-           E = VerbatimBlockCommands.end();
-       I != E; ++I)
-    if (I->StartName == StartName) {
-      EndName = I->EndName;
-      return true;
-    }
-
-  return false;
+  return NULL;
 }
 
-bool CommandTraits::isVerbatimLineCommand(StringRef Name) const {
-  bool Result = isDeclarationCommand(Name) || llvm::StringSwitch<bool>(Name)
-  .Case("defgroup", true)
-  .Case("ingroup", true)
-  .Case("addtogroup", true)
-  .Case("weakgroup", true)
-  .Case("name", true)
-
-  .Case("section", true)
-  .Case("subsection", true)
-  .Case("subsubsection", true)
-  .Case("paragraph", true)
-
-  .Case("mainpage", true)
-  .Case("subpage", true)
-  .Case("ref", true)
-
-  .Default(false);
-
-  if (Result)
-    return true;
-
-  for (VerbatimLineCommandVector::const_iterator
-           I = VerbatimLineCommands.begin(),
-           E = VerbatimLineCommands.end();
-       I != E; ++I)
-    if (I->Name == Name)
-      return true;
-
-  return false;
-}
-
-bool CommandTraits::isDeclarationCommand(StringRef Name) const {
-  return llvm::StringSwitch<bool>(Name)
-      // Doxygen commands.
-      .Case("fn", true)
-      .Case("var", true)
-      .Case("property", true)
-      .Case("typedef", true)
-
-      .Case("overload", true)
-
-      // HeaderDoc commands.
-      .Case("class", true)
-      .Case("interface", true)
-      .Case("protocol", true)
-      .Case("category", true)
-      .Case("template", true)
-      .Case("function", true)
-      .Case("method", true)
-      .Case("callback", true)
-      .Case("var", true)
-      .Case("const", true)
-      .Case("constant", true)
-      .Case("property", true)
-      .Case("struct", true)
-      .Case("union", true)
-      .Case("typedef", true)
-      .Case("enum", true)
-
-      .Default(false);
-}
-
-void CommandTraits::addVerbatimBlockCommand(StringRef StartName,
-                                            StringRef EndName) {
-  VerbatimBlockCommand VBC;
-  VBC.StartName = StartName;
-  VBC.EndName = EndName;
-  VerbatimBlockCommands.push_back(VBC);
-}
-
-void CommandTraits::addVerbatimLineCommand(StringRef Name) {
-  VerbatimLineCommand VLC;
-  VLC.Name = Name;
-  VerbatimLineCommands.push_back(VLC);
+const CommandInfo *CommandTraits::getRegisteredCommandInfo(
+                                                  unsigned CommandID) const {
+  return RegisteredCommands[CommandID - llvm::array_lengthof(Commands)];
 }
 
 } // end namespace comments
diff --git a/lib/AST/CommentDumper.cpp b/lib/AST/CommentDumper.cpp
index dffc823..f6fb3b1 100644
--- a/lib/AST/CommentDumper.cpp
+++ b/lib/AST/CommentDumper.cpp
@@ -16,12 +16,15 @@
 namespace {
 class CommentDumper: public comments::ConstCommentVisitor<CommentDumper> {
   raw_ostream &OS;
-  SourceManager *SM;
+  const CommandTraits *Traits;
+  const SourceManager *SM;
   unsigned IndentLevel;
 
 public:
-  CommentDumper(raw_ostream &OS, SourceManager *SM) :
-      OS(OS), SM(SM), IndentLevel(0)
+  CommentDumper(raw_ostream &OS,
+                const CommandTraits *Traits,
+                const SourceManager *SM) :
+      OS(OS), Traits(Traits), SM(SM), IndentLevel(0)
   { }
 
   void dumpIndent() const {
@@ -56,6 +59,15 @@
   void visitVerbatimLineComment(const VerbatimLineComment *C);
 
   void visitFullComment(const FullComment *C);
+
+  const char *getCommandName(unsigned CommandID) {
+    if (Traits)
+      return Traits->getCommandInfo(CommandID)->Name;
+    const CommandInfo *Info = CommandTraits::getBuiltinCommandInfo(CommandID);
+    if (Info)
+      return Info->Name;
+    return "<not a builtin command>";
+  }
 };
 
 void CommentDumper::dumpSourceRange(const Comment *C) {
@@ -107,7 +119,7 @@
 void CommentDumper::visitInlineCommandComment(const InlineCommandComment *C) {
   dumpComment(C);
 
-  OS << " Name=\"" << C->getCommandName() << "\"";
+  OS << " Name=\"" << getCommandName(C->getCommandID()) << "\"";
   switch (C->getRenderKind()) {
   case InlineCommandComment::RenderNormal:
     OS << " RenderNormal";
@@ -155,7 +167,7 @@
 void CommentDumper::visitBlockCommandComment(const BlockCommandComment *C) {
   dumpComment(C);
 
-  OS << " Name=\"" << C->getCommandName() << "\"";
+  OS << " Name=\"" << getCommandName(C->getCommandID()) << "\"";
   for (unsigned i = 0, e = C->getNumArgs(); i != e; ++i)
     OS << " Arg[" << i << "]=\"" << C->getArgText(i) << "\"";
 }
@@ -198,7 +210,7 @@
 void CommentDumper::visitVerbatimBlockComment(const VerbatimBlockComment *C) {
   dumpComment(C);
 
-  OS << " Name=\"" << C->getCommandName() << "\""
+  OS << " Name=\"" << getCommandName(C->getCommandID()) << "\""
         " CloseName=\"" << C->getCloseName() << "\"";
 }
 
@@ -220,8 +232,9 @@
 
 } // unnamed namespace
 
-void Comment::dump(llvm::raw_ostream &OS, SourceManager *SM) const {
-  CommentDumper D(llvm::errs(), SM);
+void Comment::dump(llvm::raw_ostream &OS, const CommandTraits *Traits,
+                   const SourceManager *SM) const {
+  CommentDumper D(llvm::errs(), Traits, SM);
   D.dumpSubtree(this);
   llvm::errs() << '\n';
 }
diff --git a/lib/AST/CommentLexer.cpp b/lib/AST/CommentLexer.cpp
index 53440c1..fde2c40 100644
--- a/lib/AST/CommentLexer.cpp
+++ b/lib/AST/CommentLexer.cpp
@@ -359,18 +359,23 @@
         }
 
         const StringRef CommandName(BufferPtr + 1, Length);
-        StringRef EndName;
 
-        if (Traits.isVerbatimBlockCommand(CommandName, EndName)) {
-          setupAndLexVerbatimBlock(T, TokenPtr, *BufferPtr, EndName);
+        const CommandInfo *Info = Traits.getCommandInfoOrNULL(CommandName);
+        if (!Info) {
+          formTokenWithChars(T, TokenPtr, tok::unknown_command);
+          T.setUnknownCommandName(CommandName);
           return;
         }
-        if (Traits.isVerbatimLineCommand(CommandName)) {
-          setupAndLexVerbatimLine(T, TokenPtr);
+        if (Info->IsVerbatimBlockCommand) {
+          setupAndLexVerbatimBlock(T, TokenPtr, *BufferPtr, Info);
+          return;
+        }
+        if (Info->IsVerbatimLineCommand) {
+          setupAndLexVerbatimLine(T, TokenPtr, Info);
           return;
         }
         formTokenWithChars(T, TokenPtr, tok::command);
-        T.setCommandName(CommandName);
+        T.setCommandID(Info->getID());
         return;
       }
 
@@ -423,14 +428,15 @@
 
 void Lexer::setupAndLexVerbatimBlock(Token &T,
                                      const char *TextBegin,
-                                     char Marker, StringRef EndName) {
+                                     char Marker, const CommandInfo *Info) {
+  assert(Info->IsVerbatimBlockCommand);
+
   VerbatimBlockEndCommandName.clear();
   VerbatimBlockEndCommandName.append(Marker == '\\' ? "\\" : "@");
-  VerbatimBlockEndCommandName.append(EndName);
+  VerbatimBlockEndCommandName.append(Info->EndCommandName);
 
-  StringRef Name(BufferPtr + 1, TextBegin - (BufferPtr + 1));
   formTokenWithChars(T, TextBegin, tok::verbatim_block_begin);
-  T.setVerbatimBlockName(Name);
+  T.setVerbatimBlockID(Info->getID());
 
   // If there is a newline following the verbatim opening command, skip the
   // newline so that we don't create an tok::verbatim_block_line with empty
@@ -471,7 +477,7 @@
     const char *End = BufferPtr + VerbatimBlockEndCommandName.size();
     StringRef Name(BufferPtr + 1, End - (BufferPtr + 1));
     formTokenWithChars(T, End, tok::verbatim_block_end);
-    T.setVerbatimBlockName(Name);
+    T.setVerbatimBlockID(Traits.getCommandInfo(Name)->getID());
     State = LS_Normal;
     return;
   } else {
@@ -501,10 +507,11 @@
   lexVerbatimBlockFirstLine(T);
 }
 
-void Lexer::setupAndLexVerbatimLine(Token &T, const char *TextBegin) {
-  const StringRef Name(BufferPtr + 1, TextBegin - BufferPtr - 1);
+void Lexer::setupAndLexVerbatimLine(Token &T, const char *TextBegin,
+                                    const CommandInfo *Info) {
+  assert(Info->IsVerbatimLineCommand);
   formTokenWithChars(T, TextBegin, tok::verbatim_line_name);
-  T.setVerbatimLineName(Name);
+  T.setVerbatimLineID(Info->getID());
 
   State = LS_VerbatimLineText;
 }
diff --git a/lib/AST/CommentParser.cpp b/lib/AST/CommentParser.cpp
index 43abf6a..f6acd96 100644
--- a/lib/AST/CommentParser.cpp
+++ b/lib/AST/CommentParser.cpp
@@ -132,8 +132,8 @@
     Result.setKind(tok::text);
     Result.setLength(TokLength);
 #ifndef NDEBUG
-    Result.TextPtr1 = "<UNSET>";
-    Result.TextLen1 = 7;
+    Result.TextPtr = "<UNSET>";
+    Result.IntVal = 7;
 #endif
     Result.setText(Text);
   }
@@ -312,26 +312,26 @@
   BlockCommandComment *BC;
   bool IsParam = false;
   bool IsTParam = false;
-  unsigned NumArgs = 0;
-  if (Traits.isParamCommand(Tok.getCommandName())) {
+  const CommandInfo *Info = Traits.getCommandInfo(Tok.getCommandID());
+  if (Info->IsParamCommand) {
     IsParam = true;
     PC = S.actOnParamCommandStart(Tok.getLocation(),
                                   Tok.getEndLocation(),
-                                  Tok.getCommandName());
-  } if (Traits.isTParamCommand(Tok.getCommandName())) {
+                                  Tok.getCommandID());
+  } if (Info->IsTParamCommand) {
     IsTParam = true;
     TPC = S.actOnTParamCommandStart(Tok.getLocation(),
                                     Tok.getEndLocation(),
-                                    Tok.getCommandName());
+                                    Tok.getCommandID());
   } else {
-    NumArgs = Traits.getBlockCommandNumArgs(Tok.getCommandName());
     BC = S.actOnBlockCommandStart(Tok.getLocation(),
                                   Tok.getEndLocation(),
-                                  Tok.getCommandName());
+                                  Tok.getCommandID());
   }
   consumeToken();
 
-  if (Tok.is(tok::command) && Traits.isBlockCommand(Tok.getCommandName())) {
+  if (Tok.is(tok::command) &&
+      Traits.getCommandInfo(Tok.getCommandID())->IsBlockCommand) {
     // Block command ahead.  We can't nest block commands, so pretend that this
     // command has an empty argument.
     ParagraphComment *Paragraph = S.actOnParagraphComment(
@@ -348,7 +348,7 @@
     }
   }
 
-  if (IsParam || IsTParam || NumArgs > 0) {
+  if (IsParam || IsTParam || Info->NumArgs > 0) {
     // In order to parse command arguments we need to retokenize a few
     // following text tokens.
     TextTokenRetokenizer Retokenizer(Allocator, *this);
@@ -358,7 +358,7 @@
     else if (IsTParam)
       parseTParamCommandArgs(TPC, Retokenizer);
     else
-      parseBlockCommandArgs(BC, Retokenizer, NumArgs);
+      parseBlockCommandArgs(BC, Retokenizer, Info->NumArgs);
 
     Retokenizer.putBackLeftoverTokens();
   }
@@ -394,14 +394,14 @@
   if (ArgTokValid) {
     IC = S.actOnInlineCommand(CommandTok.getLocation(),
                               CommandTok.getEndLocation(),
-                              CommandTok.getCommandName(),
+                              CommandTok.getCommandID(),
                               ArgTok.getLocation(),
                               ArgTok.getEndLocation(),
                               ArgTok.getText());
   } else {
     IC = S.actOnInlineCommand(CommandTok.getLocation(),
                               CommandTok.getEndLocation(),
-                              CommandTok.getCommandName());
+                              CommandTok.getCommandID());
   }
 
   Retokenizer.putBackLeftoverTokens();
@@ -540,23 +540,24 @@
       assert(Content.size() != 0);
       break; // Block content or EOF ahead, finish this parapgaph.
 
-    case tok::command:
-      if (Traits.isBlockCommand(Tok.getCommandName())) {
+    case tok::unknown_command:
+      Content.push_back(S.actOnUnknownCommand(Tok.getLocation(),
+                                              Tok.getEndLocation(),
+                                              Tok.getUnknownCommandName()));
+      consumeToken();
+      continue;
+
+    case tok::command: {
+      const CommandInfo *Info = Traits.getCommandInfo(Tok.getCommandID());
+      if (Info->IsBlockCommand) {
         if (Content.size() == 0)
           return parseBlockCommand();
         break; // Block command ahead, finish this parapgaph.
       }
-      if (Traits.isInlineCommand(Tok.getCommandName())) {
-        Content.push_back(parseInlineCommand());
-        continue;
-      }
-
-      // Not a block command, not an inline command ==> an unknown command.
-      Content.push_back(S.actOnUnknownCommand(Tok.getLocation(),
-                                              Tok.getEndLocation(),
-                                              Tok.getCommandName()));
-      consumeToken();
+      assert(Info->IsInlineCommand);
+      Content.push_back(parseInlineCommand());
       continue;
+    }
 
     case tok::newline: {
       consumeToken();
@@ -606,7 +607,7 @@
 
   VerbatimBlockComment *VB =
       S.actOnVerbatimBlockStart(Tok.getLocation(),
-                                Tok.getVerbatimBlockName());
+                                Tok.getVerbatimBlockID());
   consumeToken();
 
   // Don't create an empty line if verbatim opening command is followed
@@ -634,8 +635,9 @@
   }
 
   if (Tok.is(tok::verbatim_block_end)) {
+    const CommandInfo *Info = Traits.getCommandInfo(Tok.getVerbatimBlockID());
     S.actOnVerbatimBlockFinish(VB, Tok.getLocation(),
-                               Tok.getVerbatimBlockName(),
+                               Info->Name,
                                S.copyArray(llvm::makeArrayRef(Lines)));
     consumeToken();
   } else {
@@ -666,7 +668,7 @@
   }
 
   VerbatimLineComment *VL = S.actOnVerbatimLine(NameTok.getLocation(),
-                                                NameTok.getVerbatimLineName(),
+                                                NameTok.getVerbatimLineID(),
                                                 TextBegin,
                                                 Text);
   consumeToken();
@@ -676,6 +678,7 @@
 BlockContentComment *Parser::parseBlockContent() {
   switch (Tok.getKind()) {
   case tok::text:
+  case tok::unknown_command:
   case tok::command:
   case tok::html_start_tag:
   case tok::html_end_tag:
diff --git a/lib/AST/CommentSema.cpp b/lib/AST/CommentSema.cpp
index ffb6e86..953afe1 100644
--- a/lib/AST/CommentSema.cpp
+++ b/lib/AST/CommentSema.cpp
@@ -23,7 +23,7 @@
 } // unnamed namespace
 
 Sema::Sema(llvm::BumpPtrAllocator &Allocator, const SourceManager &SourceMgr,
-           DiagnosticsEngine &Diags, const CommandTraits &Traits) :
+           DiagnosticsEngine &Diags, CommandTraits &Traits) :
     Allocator(Allocator), SourceMgr(SourceMgr), Diags(Diags), Traits(Traits),
     ThisDeclInfo(NULL), BriefCommand(NULL), ReturnsCommand(NULL) {
 }
@@ -44,8 +44,8 @@
 
 BlockCommandComment *Sema::actOnBlockCommandStart(SourceLocation LocBegin,
                                                   SourceLocation LocEnd,
-                                                  StringRef Name) {
-  return new (Allocator) BlockCommandComment(LocBegin, LocEnd, Name);
+                                                  unsigned CommandID) {
+  return new (Allocator) BlockCommandComment(LocBegin, LocEnd, CommandID);
 }
 
 void Sema::actOnBlockCommandArgs(BlockCommandComment *Command,
@@ -63,14 +63,14 @@
 
 ParamCommandComment *Sema::actOnParamCommandStart(SourceLocation LocBegin,
                                                   SourceLocation LocEnd,
-                                                  StringRef Name) {
+                                                  unsigned CommandID) {
   ParamCommandComment *Command =
-      new (Allocator) ParamCommandComment(LocBegin, LocEnd, Name);
+      new (Allocator) ParamCommandComment(LocBegin, LocEnd, CommandID);
 
   if (!isFunctionDecl())
     Diag(Command->getLocation(),
          diag::warn_doc_param_not_attached_to_a_function_decl)
-      << Command->getCommandNameRange();
+      << Command->getCommandNameRange(Traits);
 
   return Command;
 }
@@ -156,14 +156,14 @@
 
 TParamCommandComment *Sema::actOnTParamCommandStart(SourceLocation LocBegin,
                                                     SourceLocation LocEnd,
-                                                    StringRef Name) {
+                                                    unsigned CommandID) {
   TParamCommandComment *Command =
-      new (Allocator) TParamCommandComment(LocBegin, LocEnd, Name);
+      new (Allocator) TParamCommandComment(LocBegin, LocEnd, CommandID);
 
   if (!isTemplateOrSpecialization())
     Diag(Command->getLocation(),
          diag::warn_doc_tparam_not_attached_to_a_template_decl)
-      << Command->getCommandNameRange();
+      << Command->getCommandNameRange(Traits);
 
   return Command;
 }
@@ -239,19 +239,20 @@
 
 InlineCommandComment *Sema::actOnInlineCommand(SourceLocation CommandLocBegin,
                                                SourceLocation CommandLocEnd,
-                                               StringRef CommandName) {
+                                               unsigned CommandID) {
   ArrayRef<InlineCommandComment::Argument> Args;
+  StringRef CommandName = Traits.getCommandInfo(CommandID)->Name;
   return new (Allocator) InlineCommandComment(
                                   CommandLocBegin,
                                   CommandLocEnd,
-                                  CommandName,
+                                  CommandID,
                                   getInlineCommandRenderKind(CommandName),
                                   Args);
 }
 
 InlineCommandComment *Sema::actOnInlineCommand(SourceLocation CommandLocBegin,
                                                SourceLocation CommandLocEnd,
-                                               StringRef CommandName,
+                                               unsigned CommandID,
                                                SourceLocation ArgLocBegin,
                                                SourceLocation ArgLocEnd,
                                                StringRef Arg) {
@@ -259,11 +260,12 @@
   Argument *A = new (Allocator) Argument(SourceRange(ArgLocBegin,
                                                      ArgLocEnd),
                                          Arg);
+  StringRef CommandName = Traits.getCommandInfo(CommandID)->Name;
 
   return new (Allocator) InlineCommandComment(
                                   CommandLocBegin,
                                   CommandLocEnd,
-                                  CommandName,
+                                  CommandID,
                                   getInlineCommandRenderKind(CommandName),
                                   llvm::makeArrayRef(A, 1));
 }
@@ -272,8 +274,9 @@
                                                 SourceLocation LocEnd,
                                                 StringRef Name) {
   ArrayRef<InlineCommandComment::Argument> Args;
+  unsigned CommandID = Traits.registerUnknownCommand(Name)->getID();
   return new (Allocator) InlineCommandComment(
-                                  LocBegin, LocEnd, Name,
+                                  LocBegin, LocEnd, CommandID,
                                   InlineCommandComment::RenderNormal,
                                   Args);
 }
@@ -285,11 +288,12 @@
 }
 
 VerbatimBlockComment *Sema::actOnVerbatimBlockStart(SourceLocation Loc,
-                                                    StringRef Name) {
+                                                    unsigned CommandID) {
+  StringRef CommandName = Traits.getCommandInfo(CommandID)->Name;
   return new (Allocator) VerbatimBlockComment(
                                   Loc,
-                                  Loc.getLocWithOffset(1 + Name.size()),
-                                  Name);
+                                  Loc.getLocWithOffset(1 + CommandName.size()),
+                                  CommandID);
 }
 
 VerbatimBlockLineComment *Sema::actOnVerbatimBlockLine(SourceLocation Loc,
@@ -307,13 +311,13 @@
 }
 
 VerbatimLineComment *Sema::actOnVerbatimLine(SourceLocation LocBegin,
-                                             StringRef Name,
+                                             unsigned CommandID,
                                              SourceLocation TextBegin,
                                              StringRef Text) {
   return new (Allocator) VerbatimLineComment(
                               LocBegin,
                               TextBegin.getLocWithOffset(Text.size()),
-                              Name,
+                              CommandID,
                               TextBegin,
                               Text);
 }
@@ -411,15 +415,15 @@
     if (Command->getNumArgs() > 0)
       DiagLoc = Command->getArgRange(Command->getNumArgs() - 1).getEnd();
     if (!DiagLoc.isValid())
-      DiagLoc = Command->getCommandNameRange().getEnd();
+      DiagLoc = Command->getCommandNameRange(Traits).getEnd();
     Diag(DiagLoc, diag::warn_doc_block_command_empty_paragraph)
-      << Command->getCommandName()
+      << Command->getCommandName(Traits)
       << Command->getSourceRange();
   }
 }
 
 void Sema::checkReturnsCommand(const BlockCommandComment *Command) {
-  if (!Traits.isReturnsCommand(Command->getCommandName()))
+  if (!Traits.getCommandInfo(Command->getCommandID())->IsReturnsCommand)
     return;
   if (isFunctionDecl()) {
     if (ThisDeclInfo->ResultType->isVoidType()) {
@@ -440,7 +444,7 @@
       }
       Diag(Command->getLocation(),
            diag::warn_doc_returns_attached_to_a_void_function)
-        << Command->getCommandName()
+        << Command->getCommandName(Traits)
         << DiagKind
         << Command->getSourceRange();
     }
@@ -448,20 +452,20 @@
   }
   Diag(Command->getLocation(),
        diag::warn_doc_returns_not_attached_to_a_function_decl)
-    << Command->getCommandName()
+    << Command->getCommandName(Traits)
     << Command->getSourceRange();
 }
 
 void Sema::checkBlockCommandDuplicate(const BlockCommandComment *Command) {
-  StringRef Name = Command->getCommandName();
+  const CommandInfo *Info = Traits.getCommandInfo(Command->getCommandID());
   const BlockCommandComment *PrevCommand = NULL;
-  if (Traits.isBriefCommand(Name)) {
+  if (Info->IsBriefCommand) {
     if (!BriefCommand) {
       BriefCommand = Command;
       return;
     }
     PrevCommand = BriefCommand;
-  } else if (Traits.isReturnsCommand(Name)) {
+  } else if (Info->IsReturnsCommand) {
     if (!ReturnsCommand) {
       ReturnsCommand = Command;
       return;
@@ -471,18 +475,20 @@
     // We don't want to check this command for duplicates.
     return;
   }
+  StringRef CommandName = Command->getCommandName(Traits);
+  StringRef PrevCommandName = PrevCommand->getCommandName(Traits);
   Diag(Command->getLocation(), diag::warn_doc_block_command_duplicate)
-      << Name
+      << CommandName
       << Command->getSourceRange();
-  if (Name == PrevCommand->getCommandName())
+  if (CommandName == PrevCommandName)
     Diag(PrevCommand->getLocation(), diag::note_doc_block_command_previous)
-        << PrevCommand->getCommandName()
-        << Command->getSourceRange();
+        << PrevCommandName
+        << PrevCommand->getSourceRange();
   else
     Diag(PrevCommand->getLocation(),
          diag::note_doc_block_command_previous_alias)
-        << PrevCommand->getCommandName()
-        << Name;
+        << PrevCommandName
+        << CommandName;
 }
 
 void Sema::resolveParamCommandIndexes(const FullComment *FC) {
@@ -740,7 +746,7 @@
 
 InlineCommandComment::RenderKind
 Sema::getInlineCommandRenderKind(StringRef Name) const {
-  assert(Traits.isInlineCommand(Name));
+  assert(Traits.getCommandInfo(Name)->IsInlineCommand);
 
   return llvm::StringSwitch<InlineCommandComment::RenderKind>(Name)
       .Case("b", InlineCommandComment::RenderBold)
diff --git a/lib/AST/RawCommentList.cpp b/lib/AST/RawCommentList.cpp
index ae5740e..28ef611 100644
--- a/lib/AST/RawCommentList.cpp
+++ b/lib/AST/RawCommentList.cpp
@@ -143,10 +143,10 @@
   // a separate allocator for all temporary stuff.
   llvm::BumpPtrAllocator Allocator;
 
-  comments::CommandTraits Traits;
-  comments::Lexer L(Allocator, Traits, Range.getBegin(),
+  comments::Lexer L(Allocator, Context.getCommentCommandTraits(),
+                    Range.getBegin(),
                     RawText.begin(), RawText.end());
-  comments::BriefParser P(L, Traits);
+  comments::BriefParser P(L, Context.getCommentCommandTraits());
 
   const std::string Result = P.Parse();
   const unsigned BriefTextLength = Result.size();
@@ -163,15 +163,16 @@
   // Make sure that RawText is valid.
   getRawText(Context.getSourceManager());
 
-  comments::CommandTraits Traits;
-  comments::Lexer L(Context.getAllocator(), Traits,
+  comments::Lexer L(Context.getAllocator(), Context.getCommentCommandTraits(),
                     getSourceRange().getBegin(),
                     RawText.begin(), RawText.end());
   comments::Sema S(Context.getAllocator(), Context.getSourceManager(),
-                   Context.getDiagnostics(), Traits);
+                   Context.getDiagnostics(),
+                   Context.getCommentCommandTraits());
   S.setDecl(D);
   comments::Parser P(L, S, Context.getAllocator(), Context.getSourceManager(),
-                     Context.getDiagnostics(), Traits);
+                     Context.getDiagnostics(),
+                     Context.getCommentCommandTraits());
 
   return P.parseFullComment();
 }
diff --git a/test/Index/annotate-comments.cpp b/test/Index/annotate-comments.cpp
index 1dc7001..18754c3 100644
--- a/test/Index/annotate-comments.cpp
+++ b/test/Index/annotate-comments.cpp
@@ -370,27 +370,30 @@
 /// Blah blah.
 void comment_to_html_conversion_25();
 
-/// \b Aaa
+/// \unknown
 void comment_to_html_conversion_26();
 
-/// \c Aaa \p Bbb
+/// \b Aaa
 void comment_to_html_conversion_27();
 
-/// \a Aaa \e Bbb \em Ccc
+/// \c Aaa \p Bbb
 void comment_to_html_conversion_28();
 
-/// \a 1<2 \e 3<4 \em 5<6 \param 7<8 aaa \tparam 9<10 bbb
+/// \a Aaa \e Bbb \em Ccc
 void comment_to_html_conversion_29();
 
-/// \\ \@ \& \$ \# \< \> \% \" \. \::
+/// \a 1<2 \e 3<4 \em 5<6 \param 7<8 aaa \tparam 9<10 bbb
 void comment_to_html_conversion_30();
 
-/// &amp; &lt; &gt; &quot;
+/// \\ \@ \& \$ \# \< \> \% \" \. \::
 void comment_to_html_conversion_31();
 
-/// <em>0&lt;i</em>
+/// &amp; &lt; &gt; &quot;
 void comment_to_html_conversion_32();
 
+/// <em>0&lt;i</em>
+void comment_to_html_conversion_33();
+
 /// Aaa.
 class comment_to_xml_conversion_01 {
   /// \param aaa Blah blah.
@@ -838,13 +841,19 @@
 // CHECK:       (CXComment_VerbatimLine Text=[ foo])
 // CHECK:       (CXComment_Paragraph
 // CHECK:         (CXComment_Text Text=[ Blah blah.])))]
-// CHECK: annotate-comments.cpp:374:6: FunctionDecl=comment_to_html_conversion_26:{{.*}} FullCommentAsHTML=[<p class="para-brief"> <b>Aaa</b></p>] FullCommentAsXML=[<Function file="{{[^"]+}}annotate-comments.cpp" line="374" column="6"><Name>comment_to_html_conversion_26</Name><USR>c:@F@comment_to_html_conversion_26#</USR><Abstract><Para> <bold>Aaa</bold></Para></Abstract></Function>]
+// CHECK: annotate-comments.cpp:374:6: FunctionDecl=comment_to_html_conversion_26:{{.*}} FullCommentAsHTML=[<p class="para-brief"> </p>] FullCommentAsXML=[<Function file="{{[^"]+}}annotate-comments.cpp" line="374" column="6"><Name>comment_to_html_conversion_26</Name><USR>c:@F@comment_to_html_conversion_26#</USR><Abstract><Para> </Para></Abstract></Function>]
+// CHECK:  CommentAST=[
+// CHECK:    (CXComment_FullComment
+// CHECK:       (CXComment_Paragraph
+// CHECK:         (CXComment_Text Text=[ ] IsWhitespace)
+// CHECK:         (CXComment_InlineCommand CommandName=[unknown] RenderNormal)))]
+// CHECK: annotate-comments.cpp:377:6: FunctionDecl=comment_to_html_conversion_27:{{.*}} FullCommentAsHTML=[<p class="para-brief"> <b>Aaa</b></p>] FullCommentAsXML=[<Function file="{{[^"]+}}annotate-comments.cpp" line="377" column="6"><Name>comment_to_html_conversion_27</Name><USR>c:@F@comment_to_html_conversion_27#</USR><Abstract><Para> <bold>Aaa</bold></Para></Abstract></Function>]
 // CHECK-NEXT:  CommentAST=[
 // CHECK-NEXT:    (CXComment_FullComment
 // CHECK-NEXT:       (CXComment_Paragraph
 // CHECK-NEXT:         (CXComment_Text Text=[ ] IsWhitespace)
 // CHECK-NEXT:         (CXComment_InlineCommand CommandName=[b] RenderBold Arg[0]=Aaa)))]
-// CHECK: annotate-comments.cpp:377:6: FunctionDecl=comment_to_html_conversion_27:{{.*}} FullCommentAsHTML=[<p class="para-brief"> <tt>Aaa</tt> <tt>Bbb</tt></p>] FullCommentAsXML=[<Function file="{{[^"]+}}annotate-comments.cpp" line="377" column="6"><Name>comment_to_html_conversion_27</Name><USR>c:@F@comment_to_html_conversion_27#</USR><Abstract><Para> <monospaced>Aaa</monospaced> <monospaced>Bbb</monospaced></Para></Abstract></Function>]
+// CHECK: annotate-comments.cpp:380:6: FunctionDecl=comment_to_html_conversion_28:{{.*}} FullCommentAsHTML=[<p class="para-brief"> <tt>Aaa</tt> <tt>Bbb</tt></p>] FullCommentAsXML=[<Function file="{{[^"]+}}annotate-comments.cpp" line="380" column="6"><Name>comment_to_html_conversion_28</Name><USR>c:@F@comment_to_html_conversion_28#</USR><Abstract><Para> <monospaced>Aaa</monospaced> <monospaced>Bbb</monospaced></Para></Abstract></Function>]
 // CHECK-NEXT:  CommentAST=[
 // CHECK-NEXT:    (CXComment_FullComment
 // CHECK-NEXT:       (CXComment_Paragraph
@@ -852,7 +861,7 @@
 // CHECK-NEXT:         (CXComment_InlineCommand CommandName=[c] RenderMonospaced Arg[0]=Aaa)
 // CHECK-NEXT:         (CXComment_Text Text=[ ] IsWhitespace)
 // CHECK-NEXT:         (CXComment_InlineCommand CommandName=[p] RenderMonospaced Arg[0]=Bbb)))]
-// CHECK: annotate-comments.cpp:380:6: FunctionDecl=comment_to_html_conversion_28:{{.*}} FullCommentAsHTML=[<p class="para-brief"> <em>Aaa</em> <em>Bbb</em> <em>Ccc</em></p>] FullCommentAsXML=[<Function file="{{[^"]+}}annotate-comments.cpp" line="380" column="6"><Name>comment_to_html_conversion_28</Name><USR>c:@F@comment_to_html_conversion_28#</USR><Abstract><Para> <emphasized>Aaa</emphasized> <emphasized>Bbb</emphasized> <emphasized>Ccc</emphasized></Para></Abstract></Function>]
+// CHECK: annotate-comments.cpp:383:6: FunctionDecl=comment_to_html_conversion_29:{{.*}} FullCommentAsHTML=[<p class="para-brief"> <em>Aaa</em> <em>Bbb</em> <em>Ccc</em></p>] FullCommentAsXML=[<Function file="{{[^"]+}}annotate-comments.cpp" line="383" column="6"><Name>comment_to_html_conversion_29</Name><USR>c:@F@comment_to_html_conversion_29#</USR><Abstract><Para> <emphasized>Aaa</emphasized> <emphasized>Bbb</emphasized> <emphasized>Ccc</emphasized></Para></Abstract></Function>]
 // CHECK-NEXT:  CommentAST=[
 // CHECK-NEXT:    (CXComment_FullComment
 // CHECK-NEXT:       (CXComment_Paragraph
@@ -862,7 +871,7 @@
 // CHECK-NEXT:         (CXComment_InlineCommand CommandName=[e] RenderEmphasized Arg[0]=Bbb)
 // CHECK-NEXT:         (CXComment_Text Text=[ ] IsWhitespace)
 // CHECK-NEXT:         (CXComment_InlineCommand CommandName=[em] RenderEmphasized Arg[0]=Ccc)))]
-// CHECK: annotate-comments.cpp:383:6: FunctionDecl=comment_to_html_conversion_29:{{.*}} FullCommentAsHTML=[<p class="para-brief"> <em>1&lt;2</em> <em>3&lt;4</em> <em>5&lt;6</em> </p><dl><dt class="tparam-name-index-invalid">9&lt;10</dt><dd class="tparam-descr-index-invalid"> bbb</dd></dl><dl><dt class="param-name-index-invalid">7&lt;8</dt><dd class="param-descr-index-invalid"> aaa </dd></dl>] FullCommentAsXML=[<Function file="{{[^"]+}}annotate-comments.cpp" line="383" column="6"><Name>comment_to_html_conversion_29</Name><USR>c:@F@comment_to_html_conversion_29#</USR><Abstract><Para> <emphasized>1&lt;2</emphasized> <emphasized>3&lt;4</emphasized> <emphasized>5&lt;6</emphasized> </Para></Abstract><TemplateParameters><Parameter><Name>9&lt;10</Name><Discussion><Para> bbb</Para></Discussion></Parameter></TemplateParameters><Parameters><Parameter><Name>7&lt;8</Name><Direction isExplicit="0">in</Direction><Discussion><Para> aaa </Para></Discussion></Parameter></Parameters></Function>]
+// CHECK: annotate-comments.cpp:386:6: FunctionDecl=comment_to_html_conversion_30:{{.*}} FullCommentAsHTML=[<p class="para-brief"> <em>1&lt;2</em> <em>3&lt;4</em> <em>5&lt;6</em> </p><dl><dt class="tparam-name-index-invalid">9&lt;10</dt><dd class="tparam-descr-index-invalid"> bbb</dd></dl><dl><dt class="param-name-index-invalid">7&lt;8</dt><dd class="param-descr-index-invalid"> aaa </dd></dl>] FullCommentAsXML=[<Function file="{{[^"]+}}annotate-comments.cpp" line="386" column="6"><Name>comment_to_html_conversion_30</Name><USR>c:@F@comment_to_html_conversion_30#</USR><Abstract><Para> <emphasized>1&lt;2</emphasized> <emphasized>3&lt;4</emphasized> <emphasized>5&lt;6</emphasized> </Para></Abstract><TemplateParameters><Parameter><Name>9&lt;10</Name><Discussion><Para> bbb</Para></Discussion></Parameter></TemplateParameters><Parameters><Parameter><Name>7&lt;8</Name><Direction isExplicit="0">in</Direction><Discussion><Para> aaa </Para></Discussion></Parameter></Parameters></Function>]
 // CHECK-NEXT:  CommentAST=[
 // CHECK-NEXT:    (CXComment_FullComment
 // CHECK-NEXT:       (CXComment_Paragraph
@@ -879,7 +888,7 @@
 // CHECK-NEXT:       (CXComment_TParamCommand ParamName=[9<10] ParamPosition=Invalid
 // CHECK-NEXT:         (CXComment_Paragraph
 // CHECK-NEXT:           (CXComment_Text Text=[ bbb]))))]
-// CHECK: annotate-comments.cpp:386:6: FunctionDecl=comment_to_html_conversion_30:{{.*}} FullCommentAsHTML=[<p class="para-brief"> \ @ &amp; $ # &lt; &gt; % &quot; . ::</p>] FullCommentAsXML=[<Function file="{{[^"]+}}annotate-comments.cpp" line="386" column="6"><Name>comment_to_html_conversion_30</Name><USR>c:@F@comment_to_html_conversion_30#</USR><Abstract><Para> \ @ &amp; $ # &lt; &gt; % &quot; . ::</Para></Abstract></Function>]
+// CHECK: annotate-comments.cpp:389:6: FunctionDecl=comment_to_html_conversion_31:{{.*}} FullCommentAsHTML=[<p class="para-brief"> \ @ &amp; $ # &lt; &gt; % &quot; . ::</p>] FullCommentAsXML=[<Function file="{{[^"]+}}annotate-comments.cpp" line="389" column="6"><Name>comment_to_html_conversion_31</Name><USR>c:@F@comment_to_html_conversion_31#</USR><Abstract><Para> \ @ &amp; $ # &lt; &gt; % &quot; . ::</Para></Abstract></Function>]
 // CHECK-NEXT:  CommentAST=[
 // CHECK-NEXT:    (CXComment_FullComment
 // CHECK-NEXT:       (CXComment_Paragraph
@@ -905,7 +914,7 @@
 // CHECK-NEXT:         (CXComment_Text Text=[.])
 // CHECK-NEXT:         (CXComment_Text Text=[ ] IsWhitespace)
 // CHECK-NEXT:         (CXComment_Text Text=[::])))]
-// CHECK: annotate-comments.cpp:389:6: FunctionDecl=comment_to_html_conversion_31:{{.*}} FullCommentAsHTML=[<p class="para-brief"> &amp; &lt; &gt; &quot;</p>] FullCommentAsXML=[<Function file="{{[^"]+}}annotate-comments.cpp" line="389" column="6"><Name>comment_to_html_conversion_31</Name><USR>c:@F@comment_to_html_conversion_31#</USR><Abstract><Para> &amp; &lt; &gt; &quot;</Para></Abstract></Function>]
+// CHECK: annotate-comments.cpp:392:6: FunctionDecl=comment_to_html_conversion_32:{{.*}} FullCommentAsHTML=[<p class="para-brief"> &amp; &lt; &gt; &quot;</p>] FullCommentAsXML=[<Function file="{{[^"]+}}annotate-comments.cpp" line="392" column="6"><Name>comment_to_html_conversion_32</Name><USR>c:@F@comment_to_html_conversion_32#</USR><Abstract><Para> &amp; &lt; &gt; &quot;</Para></Abstract></Function>]
 // CHECK-NEXT:  CommentAST=[
 // CHECK-NEXT:    (CXComment_FullComment
 // CHECK-NEXT:       (CXComment_Paragraph
@@ -917,7 +926,7 @@
 // CHECK-NEXT:         (CXComment_Text Text=[>])
 // CHECK-NEXT:         (CXComment_Text Text=[ ] IsWhitespace)
 // CHECK-NEXT:         (CXComment_Text Text=["])))]
-// CHECK: annotate-comments.cpp:392:6: FunctionDecl=comment_to_html_conversion_32:{{.*}} FullCommentAsHTML=[<p class="para-brief"> <em>0&lt;i</em></p>] FullCommentAsXML=[<Function file="{{[^"]+}}annotate-comments.cpp" line="392" column="6"><Name>comment_to_html_conversion_32</Name><USR>c:@F@comment_to_html_conversion_32#</USR><Abstract><Para> <rawHTML><![CDATA[<em>]]></rawHTML>0&lt;i<rawHTML>&lt;/em&gt;</rawHTML></Para></Abstract></Function>]
+// CHECK: annotate-comments.cpp:395:6: FunctionDecl=comment_to_html_conversion_33:{{.*}} FullCommentAsHTML=[<p class="para-brief"> <em>0&lt;i</em></p>] FullCommentAsXML=[<Function file="{{[^"]+}}annotate-comments.cpp" line="395" column="6"><Name>comment_to_html_conversion_33</Name><USR>c:@F@comment_to_html_conversion_33#</USR><Abstract><Para> <rawHTML><![CDATA[<em>]]></rawHTML>0&lt;i<rawHTML>&lt;/em&gt;</rawHTML></Para></Abstract></Function>]
 // CHECK-NEXT:  CommentAST=[
 // CHECK-NEXT:    (CXComment_FullComment
 // CHECK-NEXT:       (CXComment_Paragraph
@@ -928,27 +937,27 @@
 // CHECK-NEXT:         (CXComment_Text Text=[i])
 // CHECK-NEXT:         (CXComment_HTMLEndTag Name=[em])))]
 
-// CHECK: annotate-comments.cpp:395:7: ClassDecl=comment_to_xml_conversion_01:{{.*}} FullCommentAsXML=[<Class file="{{[^"]+}}annotate-comments.cpp" line="395" column="7"><Name>comment_to_xml_conversion_01</Name><USR>c:@C@comment_to_xml_conversion_01</USR><Abstract><Para> Aaa.</Para></Abstract></Class>]
-// CHECK: annotate-comments.cpp:397:3: CXXConstructor=comment_to_xml_conversion_01:{{.*}} FullCommentAsXML=[<Function isInstanceMethod="1" file="{{[^"]+}}annotate-comments.cpp" line="397" column="3"><Name>comment_to_xml_conversion_01</Name><USR>c:@C@comment_to_xml_conversion_01@F@comment_to_xml_conversion_01#I#</USR><Parameters><Parameter><Name>aaa</Name><Index>0</Index><Direction isExplicit="0">in</Direction><Discussion><Para> Blah blah.</Para></Discussion></Parameter></Parameters></Function>]
-// CHECK: annotate-comments.cpp:400:3: CXXDestructor=~comment_to_xml_conversion_01:{{.*}} FullCommentAsXML=[<Function isInstanceMethod="1" file="{{[^"]+}}annotate-comments.cpp" line="400" column="3"><Name>~comment_to_xml_conversion_01</Name><USR>c:@C@comment_to_xml_conversion_01@F@~comment_to_xml_conversion_01#</USR><Abstract><Para> Aaa.</Para></Abstract></Function>]
-// CHECK: annotate-comments.cpp:403:7: CXXMethod=comment_to_xml_conversion_02:{{.*}} FullCommentAsXML=[<Function isInstanceMethod="1" file="{{[^"]+}}annotate-comments.cpp" line="403" column="7"><Name>comment_to_xml_conversion_02</Name><USR>c:@C@comment_to_xml_conversion_01@F@comment_to_xml_conversion_02#I#</USR><Parameters><Parameter><Name>aaa</Name><Index>0</Index><Direction isExplicit="0">in</Direction><Discussion><Para> Blah blah.</Para></Discussion></Parameter></Parameters></Function>]
-// CHECK: annotate-comments.cpp:406:14: CXXMethod=comment_to_xml_conversion_03:{{.*}} FullCommentAsXML=[<Function isClassMethod="1" file="{{[^"]+}}annotate-comments.cpp" line="406" column="14"><Name>comment_to_xml_conversion_03</Name><USR>c:@C@comment_to_xml_conversion_01@F@comment_to_xml_conversion_03#I#S</USR><Parameters><Parameter><Name>aaa</Name><Index>0</Index><Direction isExplicit="0">in</Direction><Discussion><Para> Blah blah.</Para></Discussion></Parameter></Parameters></Function>]
-// CHECK: annotate-comments.cpp:409:7: FieldDecl=comment_to_xml_conversion_04:{{.*}} FullCommentAsXML=[<Variable file="{{[^"]+}}annotate-comments.cpp" line="409" column="7"><Name>comment_to_xml_conversion_04</Name><USR>c:@C@comment_to_xml_conversion_01@FI@comment_to_xml_conversion_04</USR><Abstract><Para> Aaa.</Para></Abstract></Variable>]
-// CHECK: annotate-comments.cpp:412:14: VarDecl=comment_to_xml_conversion_05:{{.*}} FullCommentAsXML=[<Variable file="{{[^"]+}}annotate-comments.cpp" line="412" column="14"><Name>comment_to_xml_conversion_05</Name><USR>c:@C@comment_to_xml_conversion_01@comment_to_xml_conversion_05</USR><Abstract><Para> Aaa.</Para></Abstract></Variable>]
-// CHECK: annotate-comments.cpp:415:8: CXXMethod=operator():{{.*}} FullCommentAsXML=[<Function isInstanceMethod="1" file="{{[^"]+}}annotate-comments.cpp" line="415" column="8"><Name>operator()</Name><USR>c:@C@comment_to_xml_conversion_01@F@operator()#I#</USR><Parameters><Parameter><Name>aaa</Name><Index>0</Index><Direction isExplicit="0">in</Direction><Discussion><Para> Blah blah.</Para></Discussion></Parameter></Parameters></Function>]
-// CHECK: annotate-comments.cpp:418:3: CXXConversion=operator _Bool:{{.*}} FullCommentAsXML=[<Function isInstanceMethod="1" file="{{[^"]+}}annotate-comments.cpp" line="418" column="3"><Name>operator _Bool</Name><USR>c:@C@comment_to_xml_conversion_01@F@operator _Bool#</USR><Abstract><Para> Aaa.</Para></Abstract></Function>]
-// CHECK: annotate-comments.cpp:421:15: TypedefDecl=comment_to_xml_conversion_06:{{.*}} FullCommentAsXML=[<Typedef file="{{[^"]+}}annotate-comments.cpp" line="421" column="15"><Name>comment_to_xml_conversion_06</Name><USR>c:annotate-comments.cpp@8453@C@comment_to_xml_conversion_01@T@comment_to_xml_conversion_06</USR><Abstract><Para> Aaa.</Para></Abstract></Typedef>]
-// CHECK: annotate-comments.cpp:424:9: TypeAliasDecl=comment_to_xml_conversion_07:{{.*}} FullCommentAsXML=[<Typedef file="{{[^"]+}}annotate-comments.cpp" line="424" column="9"><Name>comment_to_xml_conversion_07</Name><USR>c:@C@comment_to_xml_conversion_01@comment_to_xml_conversion_07</USR><Abstract><Para> Aaa.</Para></Abstract></Typedef>]
-// CHECK: annotate-comments.cpp:431:3: UnexposedDecl=comment_to_xml_conversion_09:{{.*}} FullCommentAsXML=[<Typedef file="{{[^"]+}}annotate-comments.cpp" line="431" column="3"><Name>comment_to_xml_conversion_09</Name><USR>c:@C@comment_to_xml_conversion_01@comment_to_xml_conversion_09</USR><Abstract><Para> Aaa.</Para></Abstract></Typedef>]
-// CHECK: annotate-comments.cpp:436:6: FunctionTemplate=comment_to_xml_conversion_10:{{.*}} FullCommentAsXML=[<Function templateKind="template" file="{{[^"]+}}annotate-comments.cpp" line="436" column="6"><Name>comment_to_xml_conversion_10</Name><USR>c:@FT@&gt;2#T#Tcomment_to_xml_conversion_10#t0.0#t0.1#</USR><Abstract><Para> Aaa.</Para></Abstract></Function>]
-// CHECK: annotate-comments.cpp:440:6: FunctionDecl=comment_to_xml_conversion_10:{{.*}} FullCommentAsXML=[<Function templateKind="specialization" file="{{[^"]+}}annotate-comments.cpp" line="440" column="6"><Name>comment_to_xml_conversion_10</Name><USR>c:@F@comment_to_xml_conversion_10&lt;#I#I&gt;#I#I#</USR><Abstract><Para> Aaa.</Para></Abstract></Function>]
-// CHECK: annotate-comments.cpp:444:7: ClassTemplate=comment_to_xml_conversion_11:{{.*}} FullCommentAsXML=[<Class templateKind="template" file="{{[^"]+}}annotate-comments.cpp" line="444" column="7"><Name>comment_to_xml_conversion_11</Name><USR>c:@CT&gt;2#T#T@comment_to_xml_conversion_11</USR><Abstract><Para> Aaa.</Para></Abstract></Class>]
-// CHECK: annotate-comments.cpp:448:7: ClassTemplatePartialSpecialization=comment_to_xml_conversion_11:{{.*}} FullCommentAsXML=[<Class templateKind="partialSpecialization" file="{{[^"]+}}annotate-comments.cpp" line="448" column="7"><Name>comment_to_xml_conversion_11</Name><USR>c:@CP&gt;1#T@comment_to_xml_conversion_11&gt;#t0.0#I</USR><Abstract><Para> Aaa.</Para></Abstract></Class>]
-// CHECK: annotate-comments.cpp:452:7: ClassDecl=comment_to_xml_conversion_11:{{.*}} FullCommentAsXML=[<Class templateKind="specialization" file="{{[^"]+}}annotate-comments.cpp" line="452" column="7"><Name>comment_to_xml_conversion_11</Name><USR>c:@C@comment_to_xml_conversion_11&gt;#I#I</USR><Abstract><Para> Aaa.</Para></Abstract></Class>]
-// CHECK: annotate-comments.cpp:455:5: VarDecl=comment_to_xml_conversion_12:{{.*}} FullCommentAsXML=[<Variable file="{{[^"]+}}annotate-comments.cpp" line="455" column="5"><Name>comment_to_xml_conversion_12</Name><USR>c:@comment_to_xml_conversion_12</USR><Abstract><Para> Aaa.</Para></Abstract></Variable>]
-// CHECK: annotate-comments.cpp:458:11: Namespace=comment_to_xml_conversion_13:{{.*}} FullCommentAsXML=[<Namespace file="{{[^"]+}}annotate-comments.cpp" line="458" column="11"><Name>comment_to_xml_conversion_13</Name><USR>c:@N@comment_to_xml_conversion_13</USR><Abstract><Para> Aaa.</Para></Abstract></Namespace>]
-// CHECK: annotate-comments.cpp:460:13: Namespace=comment_to_xml_conversion_14:{{.*}} FullCommentAsXML=[<Namespace file="{{[^"]+}}annotate-comments.cpp" line="460" column="13"><Name>comment_to_xml_conversion_14</Name><USR>c:@N@comment_to_xml_conversion_13@N@comment_to_xml_conversion_14</USR><Abstract><Para> Aaa.</Para></Abstract></Namespace>]
-// CHECK: annotate-comments.cpp:465:6: EnumDecl=comment_to_xml_conversion_15:{{.*}} FullCommentAsXML=[<Enum file="{{[^"]+}}annotate-comments.cpp" line="465" column="6"><Name>comment_to_xml_conversion_15</Name><USR>c:@E@comment_to_xml_conversion_15</USR><Abstract><Para> Aaa.</Para></Abstract></Enum>]
-// CHECK: annotate-comments.cpp:467:3: EnumConstantDecl=comment_to_xml_conversion_16:{{.*}} FullCommentAsXML=[<Variable file="{{[^"]+}}annotate-comments.cpp" line="467" column="3"><Name>comment_to_xml_conversion_16</Name><USR>c:@E@comment_to_xml_conversion_15@comment_to_xml_conversion_16</USR><Abstract><Para> Aaa.</Para></Abstract></Variable>]
-// CHECK: annotate-comments.cpp:471:12: EnumDecl=comment_to_xml_conversion_17:{{.*}} FullCommentAsXML=[<Enum file="{{[^"]+}}annotate-comments.cpp" line="471" column="12"><Name>comment_to_xml_conversion_17</Name><USR>c:@E@comment_to_xml_conversion_17</USR><Abstract><Para> Aaa.</Para></Abstract></Enum>]
-// CHECK: annotate-comments.cpp:473:3: EnumConstantDecl=comment_to_xml_conversion_18:{{.*}} FullCommentAsXML=[<Variable file="{{[^"]+}}annotate-comments.cpp" line="473" column="3"><Name>comment_to_xml_conversion_18</Name><USR>c:@E@comment_to_xml_conversion_17@comment_to_xml_conversion_18</USR><Abstract><Para> Aaa.</Para></Abstract></Variable>]
+// CHECK: annotate-comments.cpp:398:7: ClassDecl=comment_to_xml_conversion_01:{{.*}} FullCommentAsXML=[<Class file="{{[^"]+}}annotate-comments.cpp" line="398" column="7"><Name>comment_to_xml_conversion_01</Name><USR>c:@C@comment_to_xml_conversion_01</USR><Abstract><Para> Aaa.</Para></Abstract></Class>]
+// CHECK: annotate-comments.cpp:400:3: CXXConstructor=comment_to_xml_conversion_01:{{.*}} FullCommentAsXML=[<Function isInstanceMethod="1" file="{{[^"]+}}annotate-comments.cpp" line="400" column="3"><Name>comment_to_xml_conversion_01</Name><USR>c:@C@comment_to_xml_conversion_01@F@comment_to_xml_conversion_01#I#</USR><Parameters><Parameter><Name>aaa</Name><Index>0</Index><Direction isExplicit="0">in</Direction><Discussion><Para> Blah blah.</Para></Discussion></Parameter></Parameters></Function>]
+// CHECK: annotate-comments.cpp:403:3: CXXDestructor=~comment_to_xml_conversion_01:{{.*}} FullCommentAsXML=[<Function isInstanceMethod="1" file="{{[^"]+}}annotate-comments.cpp" line="403" column="3"><Name>~comment_to_xml_conversion_01</Name><USR>c:@C@comment_to_xml_conversion_01@F@~comment_to_xml_conversion_01#</USR><Abstract><Para> Aaa.</Para></Abstract></Function>]
+// CHECK: annotate-comments.cpp:406:7: CXXMethod=comment_to_xml_conversion_02:{{.*}} FullCommentAsXML=[<Function isInstanceMethod="1" file="{{[^"]+}}annotate-comments.cpp" line="406" column="7"><Name>comment_to_xml_conversion_02</Name><USR>c:@C@comment_to_xml_conversion_01@F@comment_to_xml_conversion_02#I#</USR><Parameters><Parameter><Name>aaa</Name><Index>0</Index><Direction isExplicit="0">in</Direction><Discussion><Para> Blah blah.</Para></Discussion></Parameter></Parameters></Function>]
+// CHECK: annotate-comments.cpp:409:14: CXXMethod=comment_to_xml_conversion_03:{{.*}} FullCommentAsXML=[<Function isClassMethod="1" file="{{[^"]+}}annotate-comments.cpp" line="409" column="14"><Name>comment_to_xml_conversion_03</Name><USR>c:@C@comment_to_xml_conversion_01@F@comment_to_xml_conversion_03#I#S</USR><Parameters><Parameter><Name>aaa</Name><Index>0</Index><Direction isExplicit="0">in</Direction><Discussion><Para> Blah blah.</Para></Discussion></Parameter></Parameters></Function>]
+// CHECK: annotate-comments.cpp:412:7: FieldDecl=comment_to_xml_conversion_04:{{.*}} FullCommentAsXML=[<Variable file="{{[^"]+}}annotate-comments.cpp" line="412" column="7"><Name>comment_to_xml_conversion_04</Name><USR>c:@C@comment_to_xml_conversion_01@FI@comment_to_xml_conversion_04</USR><Abstract><Para> Aaa.</Para></Abstract></Variable>]
+// CHECK: annotate-comments.cpp:415:14: VarDecl=comment_to_xml_conversion_05:{{.*}} FullCommentAsXML=[<Variable file="{{[^"]+}}annotate-comments.cpp" line="415" column="14"><Name>comment_to_xml_conversion_05</Name><USR>c:@C@comment_to_xml_conversion_01@comment_to_xml_conversion_05</USR><Abstract><Para> Aaa.</Para></Abstract></Variable>]
+// CHECK: annotate-comments.cpp:418:8: CXXMethod=operator():{{.*}} FullCommentAsXML=[<Function isInstanceMethod="1" file="{{[^"]+}}annotate-comments.cpp" line="418" column="8"><Name>operator()</Name><USR>c:@C@comment_to_xml_conversion_01@F@operator()#I#</USR><Parameters><Parameter><Name>aaa</Name><Index>0</Index><Direction isExplicit="0">in</Direction><Discussion><Para> Blah blah.</Para></Discussion></Parameter></Parameters></Function>]
+// CHECK: annotate-comments.cpp:421:3: CXXConversion=operator _Bool:{{.*}} FullCommentAsXML=[<Function isInstanceMethod="1" file="{{[^"]+}}annotate-comments.cpp" line="421" column="3"><Name>operator _Bool</Name><USR>c:@C@comment_to_xml_conversion_01@F@operator _Bool#</USR><Abstract><Para> Aaa.</Para></Abstract></Function>]
+// CHECK: annotate-comments.cpp:424:15: TypedefDecl=comment_to_xml_conversion_06:{{.*}} FullCommentAsXML=[<Typedef file="{{[^"]+}}annotate-comments.cpp" line="424" column="15"><Name>comment_to_xml_conversion_06</Name><USR>c:annotate-comments.cpp@8505@C@comment_to_xml_conversion_01@T@comment_to_xml_conversion_06</USR><Abstract><Para> Aaa.</Para></Abstract></Typedef>]
+// CHECK: annotate-comments.cpp:427:9: TypeAliasDecl=comment_to_xml_conversion_07:{{.*}} FullCommentAsXML=[<Typedef file="{{[^"]+}}annotate-comments.cpp" line="427" column="9"><Name>comment_to_xml_conversion_07</Name><USR>c:@C@comment_to_xml_conversion_01@comment_to_xml_conversion_07</USR><Abstract><Para> Aaa.</Para></Abstract></Typedef>]
+// CHECK: annotate-comments.cpp:434:3: UnexposedDecl=comment_to_xml_conversion_09:{{.*}} FullCommentAsXML=[<Typedef file="{{[^"]+}}annotate-comments.cpp" line="434" column="3"><Name>comment_to_xml_conversion_09</Name><USR>c:@C@comment_to_xml_conversion_01@comment_to_xml_conversion_09</USR><Abstract><Para> Aaa.</Para></Abstract></Typedef>]
+// CHECK: annotate-comments.cpp:439:6: FunctionTemplate=comment_to_xml_conversion_10:{{.*}} FullCommentAsXML=[<Function templateKind="template" file="{{[^"]+}}annotate-comments.cpp" line="439" column="6"><Name>comment_to_xml_conversion_10</Name><USR>c:@FT@&gt;2#T#Tcomment_to_xml_conversion_10#t0.0#t0.1#</USR><Abstract><Para> Aaa.</Para></Abstract></Function>]
+// CHECK: annotate-comments.cpp:443:6: FunctionDecl=comment_to_xml_conversion_10:{{.*}} FullCommentAsXML=[<Function templateKind="specialization" file="{{[^"]+}}annotate-comments.cpp" line="443" column="6"><Name>comment_to_xml_conversion_10</Name><USR>c:@F@comment_to_xml_conversion_10&lt;#I#I&gt;#I#I#</USR><Abstract><Para> Aaa.</Para></Abstract></Function>]
+// CHECK: annotate-comments.cpp:447:7: ClassTemplate=comment_to_xml_conversion_11:{{.*}} FullCommentAsXML=[<Class templateKind="template" file="{{[^"]+}}annotate-comments.cpp" line="447" column="7"><Name>comment_to_xml_conversion_11</Name><USR>c:@CT&gt;2#T#T@comment_to_xml_conversion_11</USR><Abstract><Para> Aaa.</Para></Abstract></Class>]
+// CHECK: annotate-comments.cpp:451:7: ClassTemplatePartialSpecialization=comment_to_xml_conversion_11:{{.*}} FullCommentAsXML=[<Class templateKind="partialSpecialization" file="{{[^"]+}}annotate-comments.cpp" line="451" column="7"><Name>comment_to_xml_conversion_11</Name><USR>c:@CP&gt;1#T@comment_to_xml_conversion_11&gt;#t0.0#I</USR><Abstract><Para> Aaa.</Para></Abstract></Class>]
+// CHECK: annotate-comments.cpp:455:7: ClassDecl=comment_to_xml_conversion_11:{{.*}} FullCommentAsXML=[<Class templateKind="specialization" file="{{[^"]+}}annotate-comments.cpp" line="455" column="7"><Name>comment_to_xml_conversion_11</Name><USR>c:@C@comment_to_xml_conversion_11&gt;#I#I</USR><Abstract><Para> Aaa.</Para></Abstract></Class>]
+// CHECK: annotate-comments.cpp:458:5: VarDecl=comment_to_xml_conversion_12:{{.*}} FullCommentAsXML=[<Variable file="{{[^"]+}}annotate-comments.cpp" line="458" column="5"><Name>comment_to_xml_conversion_12</Name><USR>c:@comment_to_xml_conversion_12</USR><Abstract><Para> Aaa.</Para></Abstract></Variable>]
+// CHECK: annotate-comments.cpp:461:11: Namespace=comment_to_xml_conversion_13:{{.*}} FullCommentAsXML=[<Namespace file="{{[^"]+}}annotate-comments.cpp" line="461" column="11"><Name>comment_to_xml_conversion_13</Name><USR>c:@N@comment_to_xml_conversion_13</USR><Abstract><Para> Aaa.</Para></Abstract></Namespace>]
+// CHECK: annotate-comments.cpp:463:13: Namespace=comment_to_xml_conversion_14:{{.*}} FullCommentAsXML=[<Namespace file="{{[^"]+}}annotate-comments.cpp" line="463" column="13"><Name>comment_to_xml_conversion_14</Name><USR>c:@N@comment_to_xml_conversion_13@N@comment_to_xml_conversion_14</USR><Abstract><Para> Aaa.</Para></Abstract></Namespace>]
+// CHECK: annotate-comments.cpp:468:6: EnumDecl=comment_to_xml_conversion_15:{{.*}} FullCommentAsXML=[<Enum file="{{[^"]+}}annotate-comments.cpp" line="468" column="6"><Name>comment_to_xml_conversion_15</Name><USR>c:@E@comment_to_xml_conversion_15</USR><Abstract><Para> Aaa.</Para></Abstract></Enum>]
+// CHECK: annotate-comments.cpp:470:3: EnumConstantDecl=comment_to_xml_conversion_16:{{.*}} FullCommentAsXML=[<Variable file="{{[^"]+}}annotate-comments.cpp" line="470" column="3"><Name>comment_to_xml_conversion_16</Name><USR>c:@E@comment_to_xml_conversion_15@comment_to_xml_conversion_16</USR><Abstract><Para> Aaa.</Para></Abstract></Variable>]
+// CHECK: annotate-comments.cpp:474:12: EnumDecl=comment_to_xml_conversion_17:{{.*}} FullCommentAsXML=[<Enum file="{{[^"]+}}annotate-comments.cpp" line="474" column="12"><Name>comment_to_xml_conversion_17</Name><USR>c:@E@comment_to_xml_conversion_17</USR><Abstract><Para> Aaa.</Para></Abstract></Enum>]
+// CHECK: annotate-comments.cpp:476:3: EnumConstantDecl=comment_to_xml_conversion_18:{{.*}} FullCommentAsXML=[<Variable file="{{[^"]+}}annotate-comments.cpp" line="476" column="3"><Name>comment_to_xml_conversion_18</Name><USR>c:@E@comment_to_xml_conversion_17@comment_to_xml_conversion_18</USR><Abstract><Para> Aaa.</Para></Abstract></Variable>]
diff --git a/tools/c-index-test/c-index-test.c b/tools/c-index-test/c-index-test.c
index 511f1a9..85dcddd 100644
--- a/tools/c-index-test/c-index-test.c
+++ b/tools/c-index-test/c-index-test.c
@@ -512,8 +512,7 @@
 #endif
 }
 
-static void PrintCursorComments(CXTranslationUnit TU,
-                                CXCursor Cursor,
+static void PrintCursorComments(CXCursor Cursor,
                                 CommentXMLValidationData *ValidationData) {
   {
     CXString RawComment;
@@ -543,7 +542,7 @@
                                         clang_FullComment_getAsHTML(Comment));
       {
         CXString XML;
-        XML = clang_FullComment_getAsXML(TU, Comment);
+        XML = clang_FullComment_getAsXML(Comment);
         PrintCXStringWithPrefix("FullCommentAsXML", XML);
         ValidateCommentXML(clang_getCString(XML), ValidationData);
         clang_disposeString(XML);
@@ -781,7 +780,7 @@
         PrintRange(RefNameRange, "RefName");
     }
 
-    PrintCursorComments(TU, Cursor, ValidationData);
+    PrintCursorComments(Cursor, ValidationData);
   }
 }
 
diff --git a/tools/libclang/CIndex.cpp b/tools/libclang/CIndex.cpp
index ae488ab..6cf40fd 100644
--- a/tools/libclang/CIndex.cpp
+++ b/tools/libclang/CIndex.cpp
@@ -5800,13 +5800,13 @@
 
 CXComment clang_Cursor_getParsedComment(CXCursor C) {
   if (!clang_isDeclaration(C.kind))
-    return cxcomment::createCXComment(NULL);
+    return cxcomment::createCXComment(NULL, NULL);
 
   const Decl *D = getCursorDecl(C);
   const ASTContext &Context = getCursorContext(C);
   const comments::FullComment *FC = Context.getCommentForDecl(D);
 
-  return cxcomment::createCXComment(FC);
+  return cxcomment::createCXComment(FC, getCursorTU(C));
 }
 
 } // end: extern "C"
diff --git a/tools/libclang/CXComment.cpp b/tools/libclang/CXComment.cpp
index c5c9ca8..4e26a9e 100644
--- a/tools/libclang/CXComment.cpp
+++ b/tools/libclang/CXComment.cpp
@@ -15,12 +15,10 @@
 #include "CXString.h"
 #include "CXComment.h"
 #include "CXCursor.h"
-#include "CXTranslationUnit.h"
 
 #include "clang/AST/CommentVisitor.h"
 #include "clang/AST/CommentCommandTraits.h"
 #include "clang/AST/Decl.h"
-#include "clang/Frontend/ASTUnit.h"
 
 #include "llvm/ADT/StringSwitch.h"
 #include "llvm/Support/ErrorHandling.h"
@@ -94,9 +92,9 @@
 CXComment clang_Comment_getChild(CXComment CXC, unsigned ChildIdx) {
   const Comment *C = getASTNode(CXC);
   if (!C || ChildIdx >= C->child_count())
-    return createCXComment(NULL);
+    return createCXComment(NULL, NULL);
 
-  return createCXComment(*(C->child_begin() + ChildIdx));
+  return createCXComment(*(C->child_begin() + ChildIdx), CXC.TranslationUnit);
 }
 
 unsigned clang_Comment_isWhitespace(CXComment CXC) {
@@ -134,7 +132,8 @@
   if (!ICC)
     return createCXString((const char *) 0);
 
-  return createCXString(ICC->getCommandName(), /*DupString=*/ false);
+  const CommandTraits &Traits = getCommandTraits(CXC);
+  return createCXString(ICC->getCommandName(Traits), /*DupString=*/ false);
 }
 
 enum CXCommentInlineCommandRenderKind
@@ -221,7 +220,8 @@
   if (!BCC)
     return createCXString((const char *) 0);
 
-  return createCXString(BCC->getCommandName(), /*DupString=*/ false);
+  const CommandTraits &Traits = getCommandTraits(CXC);
+  return createCXString(BCC->getCommandName(Traits), /*DupString=*/ false);
 }
 
 unsigned clang_BlockCommandComment_getNumArgs(CXComment CXC) {
@@ -244,9 +244,9 @@
 CXComment clang_BlockCommandComment_getParagraph(CXComment CXC) {
   const BlockCommandComment *BCC = getASTNodeAs<BlockCommandComment>(CXC);
   if (!BCC)
-    return createCXComment(NULL);
+    return createCXComment(NULL, NULL);
 
-  return createCXComment(BCC->getParagraph());
+  return createCXComment(BCC->getParagraph(), CXC.TranslationUnit);
 }
 
 CXString clang_ParamCommandComment_getParamName(CXComment CXC) {
@@ -405,7 +405,8 @@
 /// Separate parts of a FullComment.
 struct FullCommentParts {
   /// Take a full comment apart and initialize members accordingly.
-  FullCommentParts(const FullComment *C);
+  FullCommentParts(const FullComment *C,
+                   const CommandTraits &Traits);
 
   const BlockContentComment *Brief;
   const ParagraphComment *FirstParagraph;
@@ -415,9 +416,9 @@
   SmallVector<const BlockContentComment *, 8> MiscBlocks;
 };
 
-FullCommentParts::FullCommentParts(const FullComment *C) :
+FullCommentParts::FullCommentParts(const FullComment *C,
+                                   const CommandTraits &Traits) :
     Brief(NULL), FirstParagraph(NULL), Returns(NULL) {
-  const CommandTraits Traits;
   for (Comment::child_iterator I = C->child_begin(), E = C->child_end();
        I != E; ++I) {
     const Comment *Child = *I;
@@ -440,12 +441,12 @@
 
     case Comment::BlockCommandCommentKind: {
       const BlockCommandComment *BCC = cast<BlockCommandComment>(Child);
-      StringRef CommandName = BCC->getCommandName();
-      if (!Brief && Traits.isBriefCommand(CommandName)) {
+      const CommandInfo *Info = Traits.getCommandInfo(BCC->getCommandID());
+      if (!Brief && Info->IsBriefCommand) {
         Brief = BCC;
         break;
       }
-      if (!Returns && Traits.isReturnsCommand(CommandName)) {
+      if (!Returns && Info->IsReturnsCommand) {
         Returns = BCC;
         break;
       }
@@ -483,7 +484,8 @@
 
     case Comment::VerbatimLineCommentKind: {
       const VerbatimLineComment *VLC = cast<VerbatimLineComment>(Child);
-      if (!Traits.isDeclarationCommand(VLC->getCommandName()))
+      const CommandInfo *Info = Traits.getCommandInfo(VLC->getCommandID());
+      if (!Info->IsDeclarationCommand)
         MiscBlocks.push_back(VLC);
       break;
     }
@@ -533,7 +535,10 @@
     public ConstCommentVisitor<CommentASTToHTMLConverter> {
 public:
   /// \param Str accumulator for HTML.
-  CommentASTToHTMLConverter(SmallVectorImpl<char> &Str) : Result(Str) { }
+  CommentASTToHTMLConverter(SmallVectorImpl<char> &Str,
+                            const CommandTraits &Traits) :
+      Result(Str), Traits(Traits)
+  { }
 
   // Inline content.
   void visitTextComment(const TextComment *C);
@@ -561,10 +566,10 @@
   void appendToResultWithHTMLEscaping(StringRef S);
 
 private:
-  const CommandTraits Traits;
-
   /// Output stream for HTML.
   llvm::raw_svector_ostream Result;
+
+  const CommandTraits &Traits;
 };
 } // end unnamed namespace
 
@@ -637,14 +642,14 @@
 
 void CommentASTToHTMLConverter::visitBlockCommandComment(
                                   const BlockCommandComment *C) {
-  StringRef CommandName = C->getCommandName();
-  if (Traits.isBriefCommand(CommandName)) {
+  const CommandInfo *Info = Traits.getCommandInfo(C->getCommandID());
+  if (Info->IsBriefCommand) {
     Result << "<p class=\"para-brief\">";
     visitNonStandaloneParagraphComment(C->getParagraph());
     Result << "</p>";
     return;
   }
-  if (Traits.isReturnsCommand(CommandName)) {
+  if (Info->IsReturnsCommand) {
     Result << "<p class=\"para-returns\">"
               "<span class=\"word-returns\">Returns</span> ";
     visitNonStandaloneParagraphComment(C->getParagraph());
@@ -735,7 +740,7 @@
 }
 
 void CommentASTToHTMLConverter::visitFullComment(const FullComment *C) {
-  FullCommentParts Parts(C);
+  FullCommentParts Parts(C, Traits);
 
   bool FirstParagraphIsBrief = false;
   if (Parts.Brief)
@@ -822,7 +827,7 @@
     return createCXString((const char *) 0);
 
   SmallString<128> HTML;
-  CommentASTToHTMLConverter Converter(HTML);
+  CommentASTToHTMLConverter Converter(HTML, getCommandTraits(CXC));
   Converter.visit(HTC);
   return createCXString(HTML.str(), /* DupString = */ true);
 }
@@ -833,7 +838,7 @@
     return createCXString((const char *) 0);
 
   SmallString<1024> HTML;
-  CommentASTToHTMLConverter Converter(HTML);
+  CommentASTToHTMLConverter Converter(HTML, getCommandTraits(CXC));
   Converter.visit(FC);
   return createCXString(HTML.str(), /* DupString = */ true);
 }
@@ -845,9 +850,10 @@
     public ConstCommentVisitor<CommentASTToXMLConverter> {
 public:
   /// \param Str accumulator for XML.
-  CommentASTToXMLConverter(const SourceManager &SM,
-                           SmallVectorImpl<char> &Str) :
-    SM(SM), Result(Str) { }
+  CommentASTToXMLConverter(SmallVectorImpl<char> &Str,
+                           const CommandTraits &Traits,
+                           const SourceManager &SM) :
+      Result(Str), Traits(Traits), SM(SM) { }
 
   // Inline content.
   void visitTextComment(const TextComment *C);
@@ -870,10 +876,11 @@
   void appendToResultWithXMLEscaping(StringRef S);
 
 private:
-  const SourceManager &SM;
-
   /// Output stream for XML.
   llvm::raw_svector_ostream Result;
+
+  const CommandTraits &Traits;
+  const SourceManager &SM;
 };
 } // end unnamed namespace
 
@@ -991,7 +998,7 @@
   if (NumLines == 0)
     return;
 
-  Result << llvm::StringSwitch<const char *>(C->getCommandName())
+  Result << llvm::StringSwitch<const char *>(C->getCommandName(Traits))
       .Case("code", "<Verbatim xml:space=\"preserve\" kind=\"code\">")
       .Default("<Verbatim xml:space=\"preserve\" kind=\"verbatim\">");
   for (unsigned i = 0; i != NumLines; ++i) {
@@ -1015,7 +1022,7 @@
 }
 
 void CommentASTToXMLConverter::visitFullComment(const FullComment *C) {
-  FullCommentParts Parts(C);
+  FullCommentParts Parts(C, Traits);
 
   const DeclInfo *DI = C->getDeclInfo();
   StringRef RootEndTag;
@@ -1213,15 +1220,16 @@
 
 extern "C" {
 
-CXString clang_FullComment_getAsXML(CXTranslationUnit TU, CXComment CXC) {
+CXString clang_FullComment_getAsXML(CXComment CXC) {
   const FullComment *FC = getASTNodeAs<FullComment>(CXC);
   if (!FC)
     return createCXString((const char *) 0);
 
+  CXTranslationUnit TU = CXC.TranslationUnit;
   SourceManager &SM = static_cast<ASTUnit *>(TU->TUData)->getSourceManager();
 
   SmallString<1024> XML;
-  CommentASTToXMLConverter Converter(SM, XML);
+  CommentASTToXMLConverter Converter(XML, getCommandTraits(CXC), SM);
   Converter.visit(FC);
   return createCXString(XML.str(), /* DupString = */ true);
 }
diff --git a/tools/libclang/CXComment.h b/tools/libclang/CXComment.h
index 753877e..5134317 100644
--- a/tools/libclang/CXComment.h
+++ b/tools/libclang/CXComment.h
@@ -15,20 +15,29 @@
 #define LLVM_CLANG_CXCOMMENT_H
 
 #include "clang-c/Index.h"
+#include "CXTranslationUnit.h"
 
 #include "clang/AST/Comment.h"
+#include "clang/AST/ASTContext.h"
+#include "clang/Frontend/ASTUnit.h"
 
 namespace clang {
+namespace comments {
+  class CommandTraits;
+}
+
 namespace cxcomment {
 
-inline CXComment createCXComment(const comments::Comment *C) {
+inline CXComment createCXComment(const comments::Comment *C,
+                                 CXTranslationUnit TU) {
   CXComment Result;
-  Result.Data = C;
+  Result.ASTNode = C;
+  Result.TranslationUnit = TU;
   return Result;
 }
 
 inline const comments::Comment *getASTNode(CXComment CXC) {
-  return static_cast<const comments::Comment *>(CXC.Data);
+  return static_cast<const comments::Comment *>(CXC.ASTNode);
 }
 
 template<typename T>
@@ -40,6 +49,14 @@
   return dyn_cast<T>(C);
 }
 
+inline ASTContext &getASTContext(CXComment CXC) {
+  return static_cast<ASTUnit *>(CXC.TranslationUnit->TUData)->getASTContext();
+}
+
+inline comments::CommandTraits &getCommandTraits(CXComment CXC) {
+  return getASTContext(CXC).getCommentCommandTraits();
+}
+
 } // end namespace cxcomment
 } // end namespace clang
 
diff --git a/unittests/AST/CommentLexer.cpp b/unittests/AST/CommentLexer.cpp
index b7bc078..2ec741b 100644
--- a/unittests/AST/CommentLexer.cpp
+++ b/unittests/AST/CommentLexer.cpp
@@ -30,7 +30,8 @@
     : FileMgr(FileMgrOpts),
       DiagID(new DiagnosticIDs()),
       Diags(DiagID, new IgnoringDiagConsumer()),
-      SourceMgr(Diags, FileMgr) {
+      SourceMgr(Diags, FileMgr),
+      Traits(Allocator) {
   }
 
   FileSystemOptions FileMgrOpts;
@@ -39,8 +40,21 @@
   DiagnosticsEngine Diags;
   SourceManager SourceMgr;
   llvm::BumpPtrAllocator Allocator;
+  CommandTraits Traits;
 
   void lexString(const char *Source, std::vector<Token> &Toks);
+
+  StringRef getCommandName(const Token &Tok) {
+    return Traits.getCommandInfo(Tok.getCommandID())->Name;
+  }
+
+  StringRef getVerbatimBlockName(const Token &Tok) {
+    return Traits.getCommandInfo(Tok.getVerbatimBlockID())->Name;
+  }
+
+  StringRef getVerbatimLineName(const Token &Tok) {
+    return Traits.getCommandInfo(Tok.getVerbatimLineID())->Name;
+  }
 };
 
 void CommentLexerTest::lexString(const char *Source,
@@ -49,9 +63,7 @@
   FileID File = SourceMgr.createFileIDForMemBuffer(Buf);
   SourceLocation Begin = SourceMgr.getLocForStartOfFile(File);
 
-  comments::CommandTraits Traits;
-  comments::Lexer L(Allocator, Traits, Begin,
-                    Source, Source + strlen(Source));
+  Lexer L(Allocator, Traits, Begin, Source, Source + strlen(Source));
 
   while (1) {
     Token Tok;
@@ -322,7 +334,7 @@
   ASSERT_EQ(StringRef(" "),     Toks[0].getText());
 
   ASSERT_EQ(tok::command,       Toks[1].getKind());
-  ASSERT_EQ(StringRef("brief"), Toks[1].getCommandName());
+  ASSERT_EQ(StringRef("brief"), getCommandName(Toks[1]));
 
   ASSERT_EQ(tok::text,          Toks[2].getKind());
   ASSERT_EQ(StringRef(" Aaa."), Toks[2].getText());
@@ -331,6 +343,38 @@
 }
 
 TEST_F(CommentLexerTest, DoxygenCommand6) {
+  const char *Source = "/// \\em\\em \\em\t\\em\n";
+  std::vector<Token> Toks;
+
+  lexString(Source, Toks);
+
+  ASSERT_EQ(8U, Toks.size());
+
+  ASSERT_EQ(tok::text,       Toks[0].getKind());
+  ASSERT_EQ(StringRef(" "),  Toks[0].getText());
+
+  ASSERT_EQ(tok::command,    Toks[1].getKind());
+  ASSERT_EQ(StringRef("em"), getCommandName(Toks[1]));
+
+  ASSERT_EQ(tok::command,    Toks[2].getKind());
+  ASSERT_EQ(StringRef("em"), getCommandName(Toks[2]));
+
+  ASSERT_EQ(tok::text,       Toks[3].getKind());
+  ASSERT_EQ(StringRef(" "),  Toks[3].getText());
+
+  ASSERT_EQ(tok::command,    Toks[4].getKind());
+  ASSERT_EQ(StringRef("em"), getCommandName(Toks[4]));
+
+  ASSERT_EQ(tok::text,       Toks[5].getKind());
+  ASSERT_EQ(StringRef("\t"), Toks[5].getText());
+
+  ASSERT_EQ(tok::command,    Toks[6].getKind());
+  ASSERT_EQ(StringRef("em"), getCommandName(Toks[6]));
+
+  ASSERT_EQ(tok::newline,    Toks[7].getKind());
+}
+
+TEST_F(CommentLexerTest, DoxygenCommand7) {
   const char *Source = "/// \\aaa\\bbb \\ccc\t\\ddd\n";
   std::vector<Token> Toks;
 
@@ -341,28 +385,28 @@
   ASSERT_EQ(tok::text,        Toks[0].getKind());
   ASSERT_EQ(StringRef(" "),   Toks[0].getText());
 
-  ASSERT_EQ(tok::command,     Toks[1].getKind());
-  ASSERT_EQ(StringRef("aaa"), Toks[1].getCommandName());
+  ASSERT_EQ(tok::unknown_command, Toks[1].getKind());
+  ASSERT_EQ(StringRef("aaa"), Toks[1].getUnknownCommandName());
 
-  ASSERT_EQ(tok::command,     Toks[2].getKind());
-  ASSERT_EQ(StringRef("bbb"), Toks[2].getCommandName());
+  ASSERT_EQ(tok::unknown_command, Toks[2].getKind());
+  ASSERT_EQ(StringRef("bbb"), Toks[2].getUnknownCommandName());
 
   ASSERT_EQ(tok::text,        Toks[3].getKind());
   ASSERT_EQ(StringRef(" "),   Toks[3].getText());
 
-  ASSERT_EQ(tok::command,     Toks[4].getKind());
-  ASSERT_EQ(StringRef("ccc"), Toks[4].getCommandName());
+  ASSERT_EQ(tok::unknown_command, Toks[4].getKind());
+  ASSERT_EQ(StringRef("ccc"), Toks[4].getUnknownCommandName());
 
   ASSERT_EQ(tok::text,        Toks[5].getKind());
   ASSERT_EQ(StringRef("\t"),  Toks[5].getText());
 
-  ASSERT_EQ(tok::command,     Toks[6].getKind());
-  ASSERT_EQ(StringRef("ddd"), Toks[6].getCommandName());
+  ASSERT_EQ(tok::unknown_command, Toks[6].getKind());
+  ASSERT_EQ(StringRef("ddd"), Toks[6].getUnknownCommandName());
 
   ASSERT_EQ(tok::newline,     Toks[7].getKind());
 }
 
-TEST_F(CommentLexerTest, DoxygenCommand7) {
+TEST_F(CommentLexerTest, DoxygenCommand8) {
   const char *Source = "// \\c\n";
   std::vector<Token> Toks;
 
@@ -374,7 +418,7 @@
   ASSERT_EQ(StringRef(" "), Toks[0].getText());
 
   ASSERT_EQ(tok::command,   Toks[1].getKind());
-  ASSERT_EQ(StringRef("c"), Toks[1].getCommandName());
+  ASSERT_EQ(StringRef("c"), getCommandName(Toks[1]));
 
   ASSERT_EQ(tok::newline,   Toks[2].getKind());
 }
@@ -397,10 +441,10 @@
     ASSERT_EQ(StringRef(" "),            Toks[0].getText());
 
     ASSERT_EQ(tok::verbatim_block_begin, Toks[1].getKind());
-    ASSERT_EQ(StringRef("verbatim"),     Toks[1].getVerbatimBlockName());
+    ASSERT_EQ(StringRef("verbatim"),     getVerbatimBlockName(Toks[1]));
 
     ASSERT_EQ(tok::verbatim_block_end,   Toks[2].getKind());
-    ASSERT_EQ(StringRef("endverbatim"),  Toks[2].getVerbatimBlockName());
+    ASSERT_EQ(StringRef("endverbatim"),  getVerbatimBlockName(Toks[2]));
 
     ASSERT_EQ(tok::newline,              Toks[3].getKind());
     ASSERT_EQ(tok::newline,              Toks[4].getKind());
@@ -421,7 +465,7 @@
   ASSERT_EQ(StringRef(" "),            Toks[0].getText());
 
   ASSERT_EQ(tok::verbatim_block_begin, Toks[1].getKind());
-  ASSERT_EQ(StringRef("verbatim"),     Toks[1].getVerbatimBlockName());
+  ASSERT_EQ(StringRef("verbatim"),     getVerbatimBlockName(Toks[1]));
 
   ASSERT_EQ(tok::newline,              Toks[2].getKind());
 }
@@ -440,7 +484,7 @@
   ASSERT_EQ(StringRef(" "),            Toks[0].getText());
 
   ASSERT_EQ(tok::verbatim_block_begin, Toks[1].getKind());
-  ASSERT_EQ(StringRef("verbatim"),     Toks[1].getVerbatimBlockName());
+  ASSERT_EQ(StringRef("verbatim"),     getVerbatimBlockName(Toks[1]));
 
   ASSERT_EQ(tok::newline,              Toks[2].getKind());
   ASSERT_EQ(tok::newline,              Toks[3].getKind());
@@ -464,13 +508,13 @@
     ASSERT_EQ(StringRef(" Meow "),       Toks[0].getText());
 
     ASSERT_EQ(tok::verbatim_block_begin, Toks[1].getKind());
-    ASSERT_EQ(StringRef("verbatim"),     Toks[1].getVerbatimBlockName());
+    ASSERT_EQ(StringRef("verbatim"),     getVerbatimBlockName(Toks[1]));
 
     ASSERT_EQ(tok::verbatim_block_line,  Toks[2].getKind());
     ASSERT_EQ(StringRef(" aaa "),        Toks[2].getVerbatimBlockText());
 
     ASSERT_EQ(tok::verbatim_block_end,   Toks[3].getKind());
-    ASSERT_EQ(StringRef("endverbatim"),  Toks[3].getVerbatimBlockName());
+    ASSERT_EQ(StringRef("endverbatim"),  getVerbatimBlockName(Toks[3]));
 
     ASSERT_EQ(tok::newline,              Toks[4].getKind());
     ASSERT_EQ(tok::newline,              Toks[5].getKind());
@@ -495,7 +539,7 @@
     ASSERT_EQ(StringRef(" Meow "),       Toks[0].getText());
 
     ASSERT_EQ(tok::verbatim_block_begin, Toks[1].getKind());
-    ASSERT_EQ(StringRef("verbatim"),     Toks[1].getVerbatimBlockName());
+    ASSERT_EQ(StringRef("verbatim"),     getVerbatimBlockName(Toks[1]));
 
     ASSERT_EQ(tok::verbatim_block_line,  Toks[2].getKind());
     ASSERT_EQ(StringRef(" aaa "),        Toks[2].getVerbatimBlockText());
@@ -523,7 +567,7 @@
   ASSERT_EQ(StringRef(" "),            Toks[0].getText());
 
   ASSERT_EQ(tok::verbatim_block_begin, Toks[1].getKind());
-  ASSERT_EQ(StringRef("verbatim"),     Toks[1].getVerbatimBlockName());
+  ASSERT_EQ(StringRef("verbatim"),     getVerbatimBlockName(Toks[1]));
 
   ASSERT_EQ(tok::newline,              Toks[2].getKind());
 
@@ -540,7 +584,7 @@
   ASSERT_EQ(tok::newline,              Toks[7].getKind());
 
   ASSERT_EQ(tok::verbatim_block_end,   Toks[8].getKind());
-  ASSERT_EQ(StringRef("endverbatim"),  Toks[8].getVerbatimBlockName());
+  ASSERT_EQ(StringRef("endverbatim"),  getVerbatimBlockName(Toks[8]));
 
   ASSERT_EQ(tok::newline,              Toks[9].getKind());
 }
@@ -564,7 +608,7 @@
   ASSERT_EQ(StringRef(" "),            Toks[0].getText());
 
   ASSERT_EQ(tok::verbatim_block_begin, Toks[1].getKind());
-  ASSERT_EQ(StringRef("verbatim"),     Toks[1].getVerbatimBlockName());
+  ASSERT_EQ(StringRef("verbatim"),     getVerbatimBlockName(Toks[1]));
 
   ASSERT_EQ(tok::verbatim_block_line,  Toks[2].getKind());
   ASSERT_EQ(StringRef(" Aaa"),         Toks[2].getVerbatimBlockText());
@@ -576,7 +620,7 @@
   ASSERT_EQ(StringRef(" Bbb"),         Toks[4].getVerbatimBlockText());
 
   ASSERT_EQ(tok::verbatim_block_end,   Toks[5].getKind());
-  ASSERT_EQ(StringRef("endverbatim"),  Toks[5].getVerbatimBlockName());
+  ASSERT_EQ(StringRef("endverbatim"),  getVerbatimBlockName(Toks[5]));
 
   ASSERT_EQ(tok::newline,              Toks[6].getKind());
 
@@ -605,7 +649,7 @@
   ASSERT_EQ(StringRef(" Meow "),       Toks[0].getText());
 
   ASSERT_EQ(tok::verbatim_block_begin, Toks[1].getKind());
-  ASSERT_EQ(StringRef("verbatim"),     Toks[1].getVerbatimBlockName());
+  ASSERT_EQ(StringRef("verbatim"),     getVerbatimBlockName(Toks[1]));
 
   ASSERT_EQ(tok::verbatim_block_line,  Toks[2].getKind());
   ASSERT_EQ(StringRef(" aaa\\$\\@"),   Toks[2].getVerbatimBlockText());
@@ -620,19 +664,19 @@
   ASSERT_EQ(StringRef("ddd "),         Toks[5].getVerbatimBlockText());
 
   ASSERT_EQ(tok::verbatim_block_end,   Toks[6].getKind());
-  ASSERT_EQ(StringRef("endverbatim"),  Toks[6].getVerbatimBlockName());
+  ASSERT_EQ(StringRef("endverbatim"),  getVerbatimBlockName(Toks[6]));
 
   ASSERT_EQ(tok::text,                 Toks[7].getKind());
   ASSERT_EQ(StringRef(" Blah "),       Toks[7].getText());
 
   ASSERT_EQ(tok::verbatim_block_begin, Toks[8].getKind());
-  ASSERT_EQ(StringRef("verbatim"),     Toks[8].getVerbatimBlockName());
+  ASSERT_EQ(StringRef("verbatim"),     getVerbatimBlockName(Toks[8]));
 
   ASSERT_EQ(tok::verbatim_block_line,  Toks[9].getKind());
   ASSERT_EQ(StringRef(" eee"),         Toks[9].getVerbatimBlockText());
 
   ASSERT_EQ(tok::verbatim_block_end,   Toks[10].getKind());
-  ASSERT_EQ(StringRef("endverbatim"),  Toks[10].getVerbatimBlockName());
+  ASSERT_EQ(StringRef("endverbatim"),  getVerbatimBlockName(Toks[10]));
 
   ASSERT_EQ(tok::text,                 Toks[11].getKind());
   ASSERT_EQ(StringRef(" BlahBlah"),    Toks[11].getText());
@@ -655,37 +699,37 @@
   ASSERT_EQ(StringRef(" "),            Toks[0].getText());
 
   ASSERT_EQ(tok::verbatim_block_begin, Toks[1].getKind());
-  ASSERT_EQ(StringRef("f$"),           Toks[1].getVerbatimBlockName());
+  ASSERT_EQ(StringRef("f$"),           getVerbatimBlockName(Toks[1]));
 
   ASSERT_EQ(tok::verbatim_block_line,  Toks[2].getKind());
   ASSERT_EQ(StringRef(" Aaa "),        Toks[2].getVerbatimBlockText());
 
   ASSERT_EQ(tok::verbatim_block_end,   Toks[3].getKind());
-  ASSERT_EQ(StringRef("f$"),           Toks[3].getVerbatimBlockName());
+  ASSERT_EQ(StringRef("f$"),           getVerbatimBlockName(Toks[3]));
 
   ASSERT_EQ(tok::text,                 Toks[4].getKind());
   ASSERT_EQ(StringRef(" "),            Toks[4].getText());
 
   ASSERT_EQ(tok::verbatim_block_begin, Toks[5].getKind());
-  ASSERT_EQ(StringRef("f["),           Toks[5].getVerbatimBlockName());
+  ASSERT_EQ(StringRef("f["),           getVerbatimBlockName(Toks[5]));
 
   ASSERT_EQ(tok::verbatim_block_line,  Toks[6].getKind());
   ASSERT_EQ(StringRef(" Bbb "),        Toks[6].getVerbatimBlockText());
 
   ASSERT_EQ(tok::verbatim_block_end,   Toks[7].getKind());
-  ASSERT_EQ(StringRef("f]"),           Toks[7].getVerbatimBlockName());
+  ASSERT_EQ(StringRef("f]"),           getVerbatimBlockName(Toks[7]));
 
   ASSERT_EQ(tok::text,                 Toks[8].getKind());
   ASSERT_EQ(StringRef(" "),            Toks[8].getText());
 
   ASSERT_EQ(tok::verbatim_block_begin, Toks[9].getKind());
-  ASSERT_EQ(StringRef("f{"),           Toks[9].getVerbatimBlockName());
+  ASSERT_EQ(StringRef("f{"),           getVerbatimBlockName(Toks[9]));
 
   ASSERT_EQ(tok::verbatim_block_line,  Toks[10].getKind());
   ASSERT_EQ(StringRef(" Ccc "),        Toks[10].getVerbatimBlockText());
 
   ASSERT_EQ(tok::verbatim_block_end,   Toks[11].getKind());
-  ASSERT_EQ(StringRef("f}"),           Toks[11].getVerbatimBlockName());
+  ASSERT_EQ(StringRef("f}"),           getVerbatimBlockName(Toks[11]));
 
   ASSERT_EQ(tok::newline,              Toks[12].getKind());
 }
@@ -708,7 +752,7 @@
     ASSERT_EQ(StringRef(" "),          Toks[0].getText());
 
     ASSERT_EQ(tok::verbatim_line_name, Toks[1].getKind());
-    ASSERT_EQ(StringRef("fn"),         Toks[1].getVerbatimLineName());
+    ASSERT_EQ(StringRef("fn"),         getVerbatimLineName(Toks[1]));
 
     ASSERT_EQ(tok::newline,            Toks[2].getKind());
     ASSERT_EQ(tok::newline,            Toks[3].getKind());
@@ -733,7 +777,7 @@
     ASSERT_EQ(StringRef(" "),          Toks[0].getText());
 
     ASSERT_EQ(tok::verbatim_line_name, Toks[1].getKind());
-    ASSERT_EQ(StringRef("fn"),         Toks[1].getVerbatimLineName());
+    ASSERT_EQ(StringRef("fn"),         getVerbatimLineName(Toks[1]));
 
     ASSERT_EQ(tok::verbatim_line_text, Toks[2].getKind());
     ASSERT_EQ(StringRef(" void *foo(const char *zzz = \"\\$\");"),
@@ -761,7 +805,7 @@
   ASSERT_EQ(StringRef(" "),          Toks[0].getText());
 
   ASSERT_EQ(tok::verbatim_line_name, Toks[1].getKind());
-  ASSERT_EQ(StringRef("fn"),         Toks[1].getVerbatimLineName());
+  ASSERT_EQ(StringRef("fn"),         getVerbatimLineName(Toks[1]));
 
   ASSERT_EQ(tok::verbatim_line_text, Toks[2].getKind());
   ASSERT_EQ(StringRef(" void *foo(const char *zzz = \"\\$\");"),
diff --git a/unittests/AST/CommentParser.cpp b/unittests/AST/CommentParser.cpp
index f04cf19..0ed8771 100644
--- a/unittests/AST/CommentParser.cpp
+++ b/unittests/AST/CommentParser.cpp
@@ -37,7 +37,8 @@
     : FileMgr(FileMgrOpts),
       DiagID(new DiagnosticIDs()),
       Diags(DiagID, new IgnoringDiagConsumer()),
-      SourceMgr(Diags, FileMgr) {
+      SourceMgr(Diags, FileMgr),
+      Traits(Allocator) {
   }
 
   FileSystemOptions FileMgrOpts;
@@ -46,6 +47,7 @@
   DiagnosticsEngine Diags;
   SourceManager SourceMgr;
   llvm::BumpPtrAllocator Allocator;
+  CommandTraits Traits;
 
   FullComment *parseString(const char *Source);
 };
@@ -55,17 +57,15 @@
   FileID File = SourceMgr.createFileIDForMemBuffer(Buf);
   SourceLocation Begin = SourceMgr.getLocForStartOfFile(File);
 
-  comments::CommandTraits Traits;
-  comments::Lexer L(Allocator, Traits, Begin,
-                    Source, Source + strlen(Source));
+  Lexer L(Allocator, Traits, Begin, Source, Source + strlen(Source));
 
-  comments::Sema S(Allocator, SourceMgr, Diags, Traits);
-  comments::Parser P(L, S, Allocator, SourceMgr, Diags, Traits);
-  comments::FullComment *FC = P.parseFullComment();
+  Sema S(Allocator, SourceMgr, Diags, Traits);
+  Parser P(L, S, Allocator, SourceMgr, Diags, Traits);
+  FullComment *FC = P.parseFullComment();
 
   if (DEBUG) {
     llvm::errs() << "=== Source:\n" << Source << "\n=== AST:\n";
-    FC->dump(SourceMgr);
+    FC->dump(llvm::errs(), &Traits, &SourceMgr);
   }
 
   Token Tok;
@@ -157,6 +157,7 @@
 }
 
 ::testing::AssertionResult HasBlockCommandAt(const Comment *C,
+                                             const CommandTraits &Traits,
                                              size_t Idx,
                                              BlockCommandComment *&BCC,
                                              StringRef Name,
@@ -165,7 +166,7 @@
   if (!AR)
     return AR;
 
-  StringRef ActualName = BCC->getCommandName();
+  StringRef ActualName = BCC->getCommandName(Traits);
   if (ActualName != Name)
     return ::testing::AssertionFailure()
         << "BlockCommandComment has name \"" << ActualName.str() << "\", "
@@ -178,6 +179,7 @@
 
 ::testing::AssertionResult HasParamCommandAt(
                               const Comment *C,
+                              const CommandTraits &Traits,
                               size_t Idx,
                               ParamCommandComment *&PCC,
                               StringRef CommandName,
@@ -189,7 +191,7 @@
   if (!AR)
     return AR;
 
-  StringRef ActualCommandName = PCC->getCommandName();
+  StringRef ActualCommandName = PCC->getCommandName(Traits);
   if (ActualCommandName != CommandName)
     return ::testing::AssertionFailure()
         << "ParamCommandComment has name \"" << ActualCommandName.str() << "\", "
@@ -225,6 +227,7 @@
 
 ::testing::AssertionResult HasTParamCommandAt(
                               const Comment *C,
+                              const CommandTraits &Traits,
                               size_t Idx,
                               TParamCommandComment *&TPCC,
                               StringRef CommandName,
@@ -234,7 +237,7 @@
   if (!AR)
     return AR;
 
-  StringRef ActualCommandName = TPCC->getCommandName();
+  StringRef ActualCommandName = TPCC->getCommandName(Traits);
   if (ActualCommandName != CommandName)
     return ::testing::AssertionFailure()
         << "TParamCommandComment has name \"" << ActualCommandName.str() << "\", "
@@ -257,6 +260,7 @@
 }
 
 ::testing::AssertionResult HasInlineCommandAt(const Comment *C,
+                                              const CommandTraits &Traits,
                                               size_t Idx,
                                               InlineCommandComment *&ICC,
                                               StringRef Name) {
@@ -264,7 +268,7 @@
   if (!AR)
     return AR;
 
-  StringRef ActualName = ICC->getCommandName();
+  StringRef ActualName = ICC->getCommandName(Traits);
   if (ActualName != Name)
     return ::testing::AssertionFailure()
         << "InlineCommandComment has name \"" << ActualName.str() << "\", "
@@ -276,11 +280,12 @@
 struct NoArgs {};
 
 ::testing::AssertionResult HasInlineCommandAt(const Comment *C,
+                                              const CommandTraits &Traits,
                                               size_t Idx,
                                               InlineCommandComment *&ICC,
                                               StringRef Name,
                                               NoArgs) {
-  ::testing::AssertionResult AR = HasInlineCommandAt(C, Idx, ICC, Name);
+  ::testing::AssertionResult AR = HasInlineCommandAt(C, Traits, Idx, ICC, Name);
   if (!AR)
     return AR;
 
@@ -293,11 +298,12 @@
 }
 
 ::testing::AssertionResult HasInlineCommandAt(const Comment *C,
+                                              const CommandTraits &Traits,
                                               size_t Idx,
                                               InlineCommandComment *&ICC,
                                               StringRef Name,
                                               StringRef Arg) {
-  ::testing::AssertionResult AR = HasInlineCommandAt(C, Idx, ICC, Name);
+  ::testing::AssertionResult AR = HasInlineCommandAt(C, Traits, Idx, ICC, Name);
   if (!AR)
     return AR;
 
@@ -452,6 +458,7 @@
 }
 
 ::testing::AssertionResult HasVerbatimBlockAt(const Comment *C,
+                                              const CommandTraits &Traits,
                                               size_t Idx,
                                               VerbatimBlockComment *&VBC,
                                               StringRef Name,
@@ -460,7 +467,7 @@
   if (!AR)
     return AR;
 
-  StringRef ActualName = VBC->getCommandName();
+  StringRef ActualName = VBC->getCommandName(Traits);
   if (ActualName != Name)
     return ::testing::AssertionFailure()
         << "VerbatimBlockComment has name \"" << ActualName.str() << "\", "
@@ -480,12 +487,13 @@
 struct Lines {};
 
 ::testing::AssertionResult HasVerbatimBlockAt(const Comment *C,
+                                              const CommandTraits &Traits,
                                               size_t Idx,
                                               VerbatimBlockComment *&VBC,
                                               StringRef Name,
                                               StringRef CloseName,
                                               NoLines) {
-  ::testing::AssertionResult AR = HasVerbatimBlockAt(C, Idx, VBC, Name,
+  ::testing::AssertionResult AR = HasVerbatimBlockAt(C, Traits, Idx, VBC, Name,
                                                      CloseName);
   if (!AR)
     return AR;
@@ -499,13 +507,14 @@
 }
 
 ::testing::AssertionResult HasVerbatimBlockAt(const Comment *C,
+                                              const CommandTraits &Traits,
                                               size_t Idx,
                                               VerbatimBlockComment *&VBC,
                                               StringRef Name,
                                               StringRef CloseName,
                                               Lines,
                                               StringRef Line0) {
-  ::testing::AssertionResult AR = HasVerbatimBlockAt(C, Idx, VBC, Name,
+  ::testing::AssertionResult AR = HasVerbatimBlockAt(C, Traits, Idx, VBC, Name,
                                                      CloseName);
   if (!AR)
     return AR;
@@ -525,6 +534,7 @@
 }
 
 ::testing::AssertionResult HasVerbatimBlockAt(const Comment *C,
+                                              const CommandTraits &Traits,
                                               size_t Idx,
                                               VerbatimBlockComment *&VBC,
                                               StringRef Name,
@@ -532,7 +542,7 @@
                                               Lines,
                                               StringRef Line0,
                                               StringRef Line1) {
-  ::testing::AssertionResult AR = HasVerbatimBlockAt(C, Idx, VBC, Name,
+  ::testing::AssertionResult AR = HasVerbatimBlockAt(C, Traits, Idx, VBC, Name,
                                                      CloseName);
   if (!AR)
     return AR;
@@ -558,6 +568,7 @@
 }
 
 ::testing::AssertionResult HasVerbatimLineAt(const Comment *C,
+                                             const CommandTraits &Traits,
                                              size_t Idx,
                                              VerbatimLineComment *&VLC,
                                              StringRef Name,
@@ -566,7 +577,7 @@
   if (!AR)
     return AR;
 
-  StringRef ActualName = VLC->getCommandName();
+  StringRef ActualName = VLC->getCommandName(Traits);
   if (ActualName != Name)
     return ::testing::AssertionFailure()
         << "VerbatimLineComment has name \"" << ActualName.str() << "\", "
@@ -651,7 +662,7 @@
   {
     BlockCommandComment *BCC;
     ParagraphComment *PC;
-    ASSERT_TRUE(HasBlockCommandAt(FC, 1, BCC, "brief", PC));
+    ASSERT_TRUE(HasBlockCommandAt(FC, Traits, 1, BCC, "brief", PC));
 
     ASSERT_TRUE(HasParagraphCommentAt(BCC, 0, " Aaa"));
   }
@@ -668,14 +679,14 @@
   {
     BlockCommandComment *BCC;
     ParagraphComment *PC;
-    ASSERT_TRUE(HasBlockCommandAt(FC, 1, BCC, "brief", PC));
+    ASSERT_TRUE(HasBlockCommandAt(FC, Traits, 1, BCC, "brief", PC));
 
     ASSERT_TRUE(HasParagraphCommentAt(BCC, 0, " "));
   }
   {
     BlockCommandComment *BCC;
     ParagraphComment *PC;
-    ASSERT_TRUE(HasBlockCommandAt(FC, 2, BCC, "author", PC));
+    ASSERT_TRUE(HasBlockCommandAt(FC, Traits, 2, BCC, "author", PC));
 
     ASSERT_TRUE(GetChildAt(BCC, 0, PC));
       ASSERT_TRUE(HasChildCount(PC, 0));
@@ -695,7 +706,7 @@
   {
     BlockCommandComment *BCC;
     ParagraphComment *PC;
-    ASSERT_TRUE(HasBlockCommandAt(FC, 1, BCC, "brief", PC));
+    ASSERT_TRUE(HasBlockCommandAt(FC, Traits, 1, BCC, "brief", PC));
 
     ASSERT_TRUE(GetChildAt(BCC, 0, PC));
       ASSERT_TRUE(HasChildCount(PC, 2));
@@ -705,7 +716,7 @@
   {
     BlockCommandComment *BCC;
     ParagraphComment *PC;
-    ASSERT_TRUE(HasBlockCommandAt(FC, 2, BCC, "author", PC));
+    ASSERT_TRUE(HasBlockCommandAt(FC, Traits, 2, BCC, "author", PC));
 
     ASSERT_TRUE(HasParagraphCommentAt(BCC, 0, " Ccc"));
   }
@@ -721,7 +732,7 @@
   {
     ParamCommandComment *PCC;
     ParagraphComment *PC;
-    ASSERT_TRUE(HasParamCommandAt(FC, 1, PCC, "param",
+    ASSERT_TRUE(HasParamCommandAt(FC, Traits, 1, PCC, "param",
                                   ParamCommandComment::In,
                                   /* IsDirectionExplicit = */ false,
                                   "aaa", PC));
@@ -740,7 +751,7 @@
   {
     ParamCommandComment *PCC;
     ParagraphComment *PC;
-    ASSERT_TRUE(HasParamCommandAt(FC, 1, PCC, "param",
+    ASSERT_TRUE(HasParamCommandAt(FC, Traits, 1, PCC, "param",
                                   ParamCommandComment::In,
                                   /* IsDirectionExplicit = */ false,
                                   "", PC));
@@ -750,7 +761,7 @@
   {
     BlockCommandComment *BCC;
     ParagraphComment *PC;
-    ASSERT_TRUE(HasBlockCommandAt(FC, 2, BCC, "brief", PC));
+    ASSERT_TRUE(HasBlockCommandAt(FC, Traits, 2, BCC, "brief", PC));
     ASSERT_TRUE(HasChildCount(PC, 0));
   }
 }
@@ -774,7 +785,7 @@
     {
       ParamCommandComment *PCC;
       ParagraphComment *PC;
-      ASSERT_TRUE(HasParamCommandAt(FC, 1, PCC, "param",
+      ASSERT_TRUE(HasParamCommandAt(FC, Traits, 1, PCC, "param",
                                     ParamCommandComment::In,
                                     /* IsDirectionExplicit = */ false,
                                     "aaa", PC));
@@ -804,7 +815,7 @@
     {
       ParamCommandComment *PCC;
       ParagraphComment *PC;
-      ASSERT_TRUE(HasParamCommandAt(FC, 1, PCC, "param",
+      ASSERT_TRUE(HasParamCommandAt(FC, Traits, 1, PCC, "param",
                                     ParamCommandComment::In,
                                     /* IsDirectionExplicit = */ true,
                                     "aaa", PC));
@@ -834,7 +845,7 @@
     {
       ParamCommandComment *PCC;
       ParagraphComment *PC;
-      ASSERT_TRUE(HasParamCommandAt(FC, 1, PCC, "param",
+      ASSERT_TRUE(HasParamCommandAt(FC, Traits, 1, PCC, "param",
                                     ParamCommandComment::Out,
                                     /* IsDirectionExplicit = */ true,
                                     "aaa", PC));
@@ -865,7 +876,7 @@
     {
       ParamCommandComment *PCC;
       ParagraphComment *PC;
-      ASSERT_TRUE(HasParamCommandAt(FC, 1, PCC, "param",
+      ASSERT_TRUE(HasParamCommandAt(FC, Traits, 1, PCC, "param",
                                     ParamCommandComment::InOut,
                                     /* IsDirectionExplicit = */ true,
                                     "aaa", PC));
@@ -886,7 +897,7 @@
   {
     ParamCommandComment *PCC;
     ParagraphComment *PC;
-    ASSERT_TRUE(HasParamCommandAt(FC, 1, PCC, "param",
+    ASSERT_TRUE(HasParamCommandAt(FC, Traits, 1, PCC, "param",
                                   ParamCommandComment::In,
                                   /* IsDirectionExplicit = */ false,
                                   "aaa", PC));
@@ -920,7 +931,7 @@
     {
       TParamCommandComment *TPCC;
       ParagraphComment *PC;
-      ASSERT_TRUE(HasTParamCommandAt(FC, 1, TPCC, "tparam",
+      ASSERT_TRUE(HasTParamCommandAt(FC, Traits, 1, TPCC, "tparam",
                                      "aaa", PC));
       ASSERT_TRUE(HasChildCount(TPCC, 1));
       ASSERT_TRUE(HasParagraphCommentAt(TPCC, 0, " Bbb"));
@@ -938,14 +949,14 @@
   {
     TParamCommandComment *TPCC;
     ParagraphComment *PC;
-    ASSERT_TRUE(HasTParamCommandAt(FC, 1, TPCC, "tparam", "", PC));
+    ASSERT_TRUE(HasTParamCommandAt(FC, Traits, 1, TPCC, "tparam", "", PC));
     ASSERT_TRUE(HasChildCount(TPCC, 1));
     ASSERT_TRUE(HasChildCount(PC, 0));
   }
   {
     BlockCommandComment *BCC;
     ParagraphComment *PC;
-    ASSERT_TRUE(HasBlockCommandAt(FC, 2, BCC, "brief", PC));
+    ASSERT_TRUE(HasBlockCommandAt(FC, Traits, 2, BCC, "brief", PC));
     ASSERT_TRUE(HasChildCount(PC, 0));
   }
 }
@@ -964,7 +975,7 @@
 
     ASSERT_TRUE(HasChildCount(PC, 2));
       ASSERT_TRUE(HasTextAt(PC, 0, " "));
-      ASSERT_TRUE(HasInlineCommandAt(PC, 1, ICC, "c", NoArgs()));
+      ASSERT_TRUE(HasInlineCommandAt(PC, Traits, 1, ICC, "c", NoArgs()));
   }
 }
 
@@ -981,7 +992,7 @@
 
     ASSERT_TRUE(HasChildCount(PC, 3));
       ASSERT_TRUE(HasTextAt(PC, 0, " "));
-      ASSERT_TRUE(HasInlineCommandAt(PC, 1, ICC, "c", NoArgs()));
+      ASSERT_TRUE(HasInlineCommandAt(PC, Traits, 1, ICC, "c", NoArgs()));
       ASSERT_TRUE(HasTextAt(PC, 2, " "));
   }
 }
@@ -999,7 +1010,7 @@
 
     ASSERT_TRUE(HasChildCount(PC, 2));
       ASSERT_TRUE(HasTextAt(PC, 0, " "));
-      ASSERT_TRUE(HasInlineCommandAt(PC, 1, ICC, "c", "aaa"));
+      ASSERT_TRUE(HasInlineCommandAt(PC, Traits, 1, ICC, "c", "aaa"));
   }
 }
 
@@ -1016,7 +1027,7 @@
 
     ASSERT_TRUE(HasChildCount(PC, 3));
       ASSERT_TRUE(HasTextAt(PC, 0, " "));
-      ASSERT_TRUE(HasInlineCommandAt(PC, 1, ICC, "c", "aaa"));
+      ASSERT_TRUE(HasInlineCommandAt(PC, Traits, 1, ICC, "c", "aaa"));
       ASSERT_TRUE(HasTextAt(PC, 2, " bbb"));
   }
 }
@@ -1034,7 +1045,7 @@
 
     ASSERT_TRUE(HasChildCount(PC, 3));
       ASSERT_TRUE(HasTextAt(PC, 0, " "));
-      ASSERT_TRUE(HasInlineCommandAt(PC, 1, ICC, "unknown", NoArgs()));
+      ASSERT_TRUE(HasInlineCommandAt(PC, Traits, 1, ICC, "unknown", NoArgs()));
       ASSERT_TRUE(HasTextAt(PC, 2, " aaa"));
   }
 }
@@ -1188,7 +1199,8 @@
   ASSERT_TRUE(HasParagraphCommentAt(FC, 0, " "));
   {
     VerbatimBlockComment *VCC;
-    ASSERT_TRUE(HasVerbatimBlockAt(FC, 1, VCC, "verbatim", "endverbatim",
+    ASSERT_TRUE(HasVerbatimBlockAt(FC, Traits, 1, VCC,
+                                   "verbatim", "endverbatim",
                                    NoLines()));
   }
 }
@@ -1202,7 +1214,8 @@
   ASSERT_TRUE(HasParagraphCommentAt(FC, 0, " "));
   {
     VerbatimBlockComment *VBC;
-    ASSERT_TRUE(HasVerbatimBlockAt(FC, 1, VBC, "verbatim", "endverbatim",
+    ASSERT_TRUE(HasVerbatimBlockAt(FC, Traits, 1, VBC,
+                                   "verbatim", "endverbatim",
                                    Lines(), " Aaa "));
   }
 }
@@ -1216,7 +1229,7 @@
   ASSERT_TRUE(HasParagraphCommentAt(FC, 0, " "));
   {
     VerbatimBlockComment *VBC;
-    ASSERT_TRUE(HasVerbatimBlockAt(FC, 1, VBC, "verbatim", "",
+    ASSERT_TRUE(HasVerbatimBlockAt(FC, Traits, 1, VBC, "verbatim", "",
                                    Lines(), " Aaa"));
   }
 }
@@ -1231,7 +1244,8 @@
 
   {
     VerbatimBlockComment *VBC;
-    ASSERT_TRUE(HasVerbatimBlockAt(FC, 0, VBC, "verbatim", "endverbatim",
+    ASSERT_TRUE(HasVerbatimBlockAt(FC, Traits, 0, VBC,
+                                   "verbatim", "endverbatim",
                                    NoLines()));
   }
 }
@@ -1253,7 +1267,8 @@
 
     {
       VerbatimBlockComment *VBC;
-      ASSERT_TRUE(HasVerbatimBlockAt(FC, 0, VBC, "verbatim", "endverbatim",
+      ASSERT_TRUE(HasVerbatimBlockAt(FC, Traits, 0, VBC,
+                                     "verbatim", "endverbatim",
                                      Lines(), " Aaa"));
     }
   }
@@ -1277,7 +1292,8 @@
     ASSERT_TRUE(HasParagraphCommentAt(FC, 0, " "));
     {
       VerbatimBlockComment *VBC;
-      ASSERT_TRUE(HasVerbatimBlockAt(FC, 1, VBC, "verbatim", "endverbatim",
+      ASSERT_TRUE(HasVerbatimBlockAt(FC, Traits, 1, VBC,
+                                     "verbatim", "endverbatim",
                                      Lines(), " Aaa"));
     }
   }
@@ -1303,7 +1319,8 @@
     ASSERT_TRUE(HasParagraphCommentAt(FC, 0, " "));
     {
       VerbatimBlockComment *VBC;
-      ASSERT_TRUE(HasVerbatimBlockAt(FC, 1, VBC, "verbatim", "endverbatim",
+      ASSERT_TRUE(HasVerbatimBlockAt(FC, Traits, 1, VBC,
+                                     "verbatim", "endverbatim",
                                      Lines(), " Aaa", " Bbb"));
     }
   }
@@ -1330,7 +1347,8 @@
     ASSERT_TRUE(HasParagraphCommentAt(FC, 0, " "));
     {
       VerbatimBlockComment *VBC;
-      ASSERT_TRUE(HasVerbatimBlockAt(FC, 1, VBC, "verbatim", "endverbatim"));
+      ASSERT_TRUE(HasVerbatimBlockAt(FC, Traits, 1, VBC,
+                                     "verbatim", "endverbatim"));
       ASSERT_EQ(3U, VBC->getNumLines());
       ASSERT_EQ(" Aaa", VBC->getText(0));
       ASSERT_EQ("",     VBC->getText(1));
@@ -1352,7 +1370,7 @@
     ASSERT_TRUE(HasParagraphCommentAt(FC, 0, " "));
     {
       VerbatimLineComment *VLC;
-      ASSERT_TRUE(HasVerbatimLineAt(FC, 1, VLC, "fn", ""));
+      ASSERT_TRUE(HasVerbatimLineAt(FC, Traits, 1, VLC, "fn", ""));
     }
   }
 }
@@ -1370,7 +1388,7 @@
     ASSERT_TRUE(HasParagraphCommentAt(FC, 0, " "));
     {
       VerbatimLineComment *VLC;
-      ASSERT_TRUE(HasVerbatimLineAt(FC, 1, VLC, "fn",
+      ASSERT_TRUE(HasVerbatimLineAt(FC, Traits, 1, VLC, "fn",
                   " void *foo(const char *zzz = \"\\$\");"));
     }
   }
diff --git a/utils/TableGen/CMakeLists.txt b/utils/TableGen/CMakeLists.txt
index ca2e469..d5748bc 100644
--- a/utils/TableGen/CMakeLists.txt
+++ b/utils/TableGen/CMakeLists.txt
@@ -5,6 +5,7 @@
 add_tablegen(clang-tblgen CLANG
   ClangASTNodesEmitter.cpp
   ClangAttrEmitter.cpp
+  ClangCommentCommandInfoEmitter.cpp
   ClangCommentHTMLTagsEmitter.cpp
   ClangDiagnosticsEmitter.cpp
   ClangSACheckersEmitter.cpp
diff --git a/utils/TableGen/ClangCommentCommandInfoEmitter.cpp b/utils/TableGen/ClangCommentCommandInfoEmitter.cpp
new file mode 100644
index 0000000..11b2f51
--- /dev/null
+++ b/utils/TableGen/ClangCommentCommandInfoEmitter.cpp
@@ -0,0 +1,70 @@
+//===--- ClangCommentCommandInfoEmitter.cpp - Generate command lists -----====//
+//
+//                     The LLVM Compiler Infrastructure
+//
+// This file is distributed under the University of Illinois Open Source
+// License. See LICENSE.TXT for details.
+//
+//===----------------------------------------------------------------------===//
+//
+// This tablegen backend emits command lists and efficient matchers command
+// names that are used in documentation comments.
+//
+//===----------------------------------------------------------------------===//
+
+#include "llvm/TableGen/Record.h"
+#include "llvm/TableGen/StringMatcher.h"
+#include <vector>
+
+using namespace llvm;
+
+namespace clang {
+void EmitClangCommentCommandInfo(RecordKeeper &Records, raw_ostream &OS) {
+  OS << "// This file is generated by TableGen.  Do not edit.\n\n";
+
+  OS << "namespace {\n"
+        "const CommandInfo Commands[] = {\n";
+  std::vector<Record *> Tags = Records.getAllDerivedDefinitions("Command");
+  for (size_t i = 0, e = Tags.size(); i != e; ++i) {
+    Record &Tag = *Tags[i];
+    OS << "  { "
+       << "\"" << Tag.getValueAsString("Name") << "\", "
+       << "\"" << Tag.getValueAsString("EndCommandName") << "\", "
+       << i << ", "
+       << Tag.getValueAsInt("NumArgs") << ", "
+       << Tag.getValueAsBit("IsInlineCommand") << ", "
+       << Tag.getValueAsBit("IsBlockCommand") << ", "
+       << Tag.getValueAsBit("IsBriefCommand") << ", "
+       << Tag.getValueAsBit("IsReturnsCommand") << ", "
+       << Tag.getValueAsBit("IsParamCommand") << ", "
+       << Tag.getValueAsBit("IsTParamCommand") << ", "
+       << Tag.getValueAsBit("IsVerbatimBlockCommand") << ", "
+       << Tag.getValueAsBit("IsVerbatimBlockEndCommand") << ", "
+       << Tag.getValueAsBit("IsVerbatimLineCommand") << ", "
+       << Tag.getValueAsBit("IsDeclarationCommand") << ", "
+       << /* IsUnknownCommand = */ "0"
+       << " }";
+    if (i + 1 != e)
+      OS << ",";
+    OS << "\n";
+  }
+  OS << "};\n"
+        "} // unnamed namespace\n\n";
+
+  std::vector<StringMatcher::StringPair> Matches;
+  for (size_t i = 0, e = Tags.size(); i != e; ++i) {
+    Record &Tag = *Tags[i];
+    std::string Name = Tag.getValueAsString("Name");
+    std::string Return;
+    raw_string_ostream(Return) << "return &Commands[" << i << "];";
+    Matches.push_back(StringMatcher::StringPair(Name, Return));
+  }
+
+  OS << "const CommandInfo *CommandTraits::getBuiltinCommandInfo(\n"
+     << "                                         StringRef Name) {\n";
+  StringMatcher("Name", Matches, OS).Emit();
+  OS << "  return NULL;\n"
+     << "}\n\n";
+}
+} // end namespace clang
+
diff --git a/utils/TableGen/TableGen.cpp b/utils/TableGen/TableGen.cpp
index bb9b918..4f7789d 100644
--- a/utils/TableGen/TableGen.cpp
+++ b/utils/TableGen/TableGen.cpp
@@ -44,6 +44,7 @@
   GenClangSACheckers,
   GenClangCommentHTMLTags,
   GenClangCommentHTMLTagsProperties,
+  GenClangCommentCommandInfo,
   GenOptParserDefs, GenOptParserImpl,
   GenArmNeon,
   GenArmNeonSema,
@@ -105,6 +106,10 @@
                                "gen-clang-comment-html-tags-properties",
                                "Generate efficient matchers for HTML tag "
                                "properties"),
+                    clEnumValN(GenClangCommentCommandInfo,
+                               "gen-clang-comment-command-info",
+                               "Generate list of commands that are used in "
+                               "documentation comments"),
                     clEnumValN(GenArmNeon, "gen-arm-neon",
                                "Generate arm_neon.h for clang"),
                     clEnumValN(GenArmNeonSema, "gen-arm-neon-sema",
@@ -180,6 +185,9 @@
     case GenClangCommentHTMLTagsProperties:
       EmitClangCommentHTMLTagsProperties(Records, OS);
       break;
+    case GenClangCommentCommandInfo:
+      EmitClangCommentCommandInfo(Records, OS);
+      break;
     case GenOptParserDefs:
       EmitOptParser(Records, OS, true);
       break;
diff --git a/utils/TableGen/TableGenBackends.h b/utils/TableGen/TableGenBackends.h
index 74fc60e..838fc84 100644
--- a/utils/TableGen/TableGenBackends.h
+++ b/utils/TableGen/TableGenBackends.h
@@ -50,6 +50,8 @@
 void EmitClangCommentHTMLTags(RecordKeeper &Records, raw_ostream &OS);
 void EmitClangCommentHTMLTagsProperties(RecordKeeper &Records, raw_ostream &OS);
 
+void EmitClangCommentCommandInfo(RecordKeeper &Records, raw_ostream &OS);
+
 void EmitNeon(RecordKeeper &Records, raw_ostream &OS);
 void EmitNeonSema(RecordKeeper &Records, raw_ostream &OS);
 void EmitNeonTest(RecordKeeper &Records, raw_ostream &OS);