diff --git a/clang/include/clang/AST/CommentCommands.td b/clang/include/clang/AST/CommentCommands.td index e839031752cdd8..06b2fa9b5531c6 100644 --- a/clang/include/clang/AST/CommentCommands.td +++ b/clang/include/clang/AST/CommentCommands.td @@ -132,9 +132,9 @@ def Tparam : BlockCommand<"tparam"> { let IsTParamCommand = 1; } // HeaderDoc command for template parameter documentation. def Templatefield : BlockCommand<"templatefield"> { let IsTParamCommand = 1; } -def Throws : BlockCommand<"throws"> { let IsThrowsCommand = 1; } -def Throw : BlockCommand<"throw"> { let IsThrowsCommand = 1; } -def Exception : BlockCommand<"exception"> { let IsThrowsCommand = 1; } +def Throws : BlockCommand<"throws"> { let IsThrowsCommand = 1; let NumArgs = 1; } +def Throw : BlockCommand<"throw"> { let IsThrowsCommand = 1; let NumArgs = 1; } +def Exception : BlockCommand<"exception"> { let IsThrowsCommand = 1; let NumArgs = 1;} def Deprecated : BlockCommand<"deprecated"> { let IsEmptyParagraphAllowed = 1; diff --git a/clang/include/clang/AST/CommentParser.h b/clang/include/clang/AST/CommentParser.h index e11e818b1af0a1..a2d0e30835e2c4 100644 --- a/clang/include/clang/AST/CommentParser.h +++ b/clang/include/clang/AST/CommentParser.h @@ -100,6 +100,11 @@ class Parser { ArrayRef parseCommandArgs(TextTokenRetokenizer &Retokenizer, unsigned NumArgs); + /// Parse arguments for \throws command supported args are in form of class + /// or template. + ArrayRef + parseThrowCommandArgs(TextTokenRetokenizer &Retokenizer, unsigned NumArgs); + BlockCommandComment *parseBlockCommand(); InlineCommandComment *parseInlineCommand(); diff --git a/clang/lib/AST/CommentParser.cpp b/clang/lib/AST/CommentParser.cpp index 8adfd85d0160c3..5baf81a509fb60 100644 --- a/clang/lib/AST/CommentParser.cpp +++ b/clang/lib/AST/CommentParser.cpp @@ -89,6 +89,31 @@ class TextTokenRetokenizer { } } + /// Extract a template type + bool lexTemplate(SmallString<32> &WordText) { + unsigned BracketCount = 0; + while (!isEnd()) { + const char C = peek(); + WordText.push_back(C); + consumeChar(); + switch (C) { + case '<': { + BracketCount++; + break; + } + case '>': { + BracketCount--; + if (!BracketCount) + return true; + break; + } + default: + break; + } + } + return false; + } + /// Add a token. /// Returns true on success, false if there are no interesting tokens to /// fetch from lexer. @@ -149,6 +174,54 @@ class TextTokenRetokenizer { addToken(); } + /// Extract a type argument + bool lexType(Token &Tok) { + if (isEnd()) + return false; + + // Save current position in case we need to rollback because the type is + // empty. + Position SavedPos = Pos; + + // Consume any leading whitespace. + consumeWhitespace(); + SmallString<32> WordText; + const char *WordBegin = Pos.BufferPtr; + SourceLocation Loc = getSourceLocation(); + + while (!isEnd()) { + const char C = peek(); + // For non-whitespace characters we check if it's a template or otherwise + // continue reading the text into a word. + if (!isWhitespace(C)) { + if (C == '<') { + if (!lexTemplate(WordText)) + return false; + } else { + WordText.push_back(C); + consumeChar(); + } + } else { + consumeChar(); + break; + } + } + + const unsigned Length = WordText.size(); + if (Length == 0) { + Pos = SavedPos; + return false; + } + + char *TextPtr = Allocator.Allocate(Length + 1); + + memcpy(TextPtr, WordText.c_str(), Length + 1); + StringRef Text = StringRef(TextPtr, Length); + + formTokenWithChars(Tok, Loc, WordBegin, Length, Text); + return true; + } + /// Extract a word -- sequence of non-whitespace characters. bool lexWord(Token &Tok) { if (isEnd()) @@ -304,6 +377,23 @@ Parser::parseCommandArgs(TextTokenRetokenizer &Retokenizer, unsigned NumArgs) { return llvm::ArrayRef(Args, ParsedArgs); } +ArrayRef +Parser::parseThrowCommandArgs(TextTokenRetokenizer &Retokenizer, + unsigned NumArgs) { + auto *Args = new (Allocator.Allocate(NumArgs)) + Comment::Argument[NumArgs]; + unsigned ParsedArgs = 0; + Token Arg; + + while (ParsedArgs < NumArgs && Retokenizer.lexType(Arg)) { + Args[ParsedArgs] = Comment::Argument{ + SourceRange(Arg.getLocation(), Arg.getEndLocation()), Arg.getText()}; + ParsedArgs++; + } + + return llvm::ArrayRef(Args, ParsedArgs); +} + BlockCommandComment *Parser::parseBlockCommand() { assert(Tok.is(tok::backslash_command) || Tok.is(tok::at_command)); @@ -356,6 +446,9 @@ BlockCommandComment *Parser::parseBlockCommand() { parseParamCommandArgs(PC, Retokenizer); else if (TPC) parseTParamCommandArgs(TPC, Retokenizer); + else if (Info->IsThrowsCommand) + S.actOnBlockCommandArgs( + BC, parseThrowCommandArgs(Retokenizer, Info->NumArgs)); else S.actOnBlockCommandArgs(BC, parseCommandArgs(Retokenizer, Info->NumArgs)); diff --git a/clang/lib/Index/CommentToXML.cpp b/clang/lib/Index/CommentToXML.cpp index 3372fbba438317..cd7226e71171c2 100644 --- a/clang/lib/Index/CommentToXML.cpp +++ b/clang/lib/Index/CommentToXML.cpp @@ -546,7 +546,8 @@ class CommentASTToXMLConverter : void visitParagraphComment(const ParagraphComment *C); void appendParagraphCommentWithKind(const ParagraphComment *C, - StringRef Kind); + StringRef ParagraphKind, + StringRef PrependBodyText); void visitBlockCommandComment(const BlockCommandComment *C); void visitParamCommandComment(const ParamCommandComment *C); @@ -680,15 +681,15 @@ CommentASTToXMLConverter::visitHTMLEndTagComment(const HTMLEndTagComment *C) { Result << "></" << C->getTagName() << ">"; } -void -CommentASTToXMLConverter::visitParagraphComment(const ParagraphComment *C) { - appendParagraphCommentWithKind(C, StringRef()); +void CommentASTToXMLConverter::visitParagraphComment( + const ParagraphComment *C) { + appendParagraphCommentWithKind(C, StringRef(), StringRef()); } void CommentASTToXMLConverter::appendParagraphCommentWithKind( - const ParagraphComment *C, - StringRef ParagraphKind) { - if (C->isWhitespace()) + const ParagraphComment *C, StringRef ParagraphKind, + StringRef PrependBodyText) { + if (C->isWhitespace() && PrependBodyText.empty()) return; if (ParagraphKind.empty()) @@ -696,8 +697,11 @@ void CommentASTToXMLConverter::appendParagraphCommentWithKind( else Result << ""; - for (Comment::child_iterator I = C->child_begin(), E = C->child_end(); - I != E; ++I) { + if (!PrependBodyText.empty()) + Result << PrependBodyText << " "; + + for (Comment::child_iterator I = C->child_begin(), E = C->child_end(); I != E; + ++I) { visit(*I); } Result << ""; @@ -706,8 +710,15 @@ void CommentASTToXMLConverter::appendParagraphCommentWithKind( void CommentASTToXMLConverter::visitBlockCommandComment( const BlockCommandComment *C) { StringRef ParagraphKind; + StringRef ExceptionType; - switch (C->getCommandID()) { + const unsigned CommandID = C->getCommandID(); + const CommandInfo *Info = Traits.getCommandInfo(CommandID); + if (Info->IsThrowsCommand && C->getNumArgs() > 0) { + ExceptionType = C->getArgText(0); + } + + switch (CommandID) { case CommandTraits::KCI_attention: case CommandTraits::KCI_author: case CommandTraits::KCI_authors: @@ -732,7 +743,8 @@ void CommentASTToXMLConverter::visitBlockCommandComment( break; } - appendParagraphCommentWithKind(C->getParagraph(), ParagraphKind); + appendParagraphCommentWithKind(C->getParagraph(), ParagraphKind, + ExceptionType); } void CommentASTToXMLConverter::visitParamCommandComment( diff --git a/clang/test/Index/comment-to-html-xml-conversion.cpp b/clang/test/Index/comment-to-html-xml-conversion.cpp index d9eefb909653c7..e0a7cff5a9a3db 100644 --- a/clang/test/Index/comment-to-html-xml-conversion.cpp +++ b/clang/test/Index/comment-to-html-xml-conversion.cpp @@ -1046,82 +1046,101 @@ void comment_to_xml_conversion_todo_4(); /// Aaa. /// \throws Bbb. void comment_to_xml_conversion_exceptions_1(); -// CHECK: comment-to-html-xml-conversion.cpp:[[@LINE-1]]:6: FunctionDecl=comment_to_xml_conversion_exceptions_1:{{.*}} FullCommentAsXML=[comment_to_xml_conversion_exceptions_1c:@F@comment_to_xml_conversion_exceptions_1#void comment_to_xml_conversion_exceptions_1() Aaa. Bbb.] +// CHECK: comment-to-html-xml-conversion.cpp:[[@LINE-1]]:6: FunctionDecl=comment_to_xml_conversion_exceptions_1:{{.*}} FullCommentAsXML=[comment_to_xml_conversion_exceptions_1c:@F@comment_to_xml_conversion_exceptions_1#void comment_to_xml_conversion_exceptions_1() Aaa. Bbb. ] // CHECK-NEXT: CommentAST=[ // CHECK-NEXT: (CXComment_FullComment // CHECK-NEXT: (CXComment_Paragraph // CHECK-NEXT: (CXComment_Text Text=[ Aaa.] HasTrailingNewline) // CHECK-NEXT: (CXComment_Text Text=[ ] IsWhitespace)) -// CHECK-NEXT: (CXComment_BlockCommand CommandName=[throws] -// CHECK-NEXT: (CXComment_Paragraph -// CHECK-NEXT: (CXComment_Text Text=[ Bbb.]))))] +// CHECK-NEXT: (CXComment_BlockCommand CommandName=[throws] Arg[0]=Bbb. +// CHECK-NEXT: (CXComment_Paragraph IsWhitespace)))] /// Aaa. /// \throw Bbb. void comment_to_xml_conversion_exceptions_2(); -// CHECK: comment-to-html-xml-conversion.cpp:[[@LINE-1]]:6: FunctionDecl=comment_to_xml_conversion_exceptions_2:{{.*}} FullCommentAsXML=[comment_to_xml_conversion_exceptions_2c:@F@comment_to_xml_conversion_exceptions_2#void comment_to_xml_conversion_exceptions_2() Aaa. Bbb.] +// CHECK: comment-to-html-xml-conversion.cpp:[[@LINE-1]]:6: FunctionDecl=comment_to_xml_conversion_exceptions_2:{{.*}} FullCommentAsXML=[comment_to_xml_conversion_exceptions_2c:@F@comment_to_xml_conversion_exceptions_2#void comment_to_xml_conversion_exceptions_2() Aaa. Bbb. ] // CHECK-NEXT: CommentAST=[ // CHECK-NEXT: (CXComment_FullComment // CHECK-NEXT: (CXComment_Paragraph // CHECK-NEXT: (CXComment_Text Text=[ Aaa.] HasTrailingNewline) // CHECK-NEXT: (CXComment_Text Text=[ ] IsWhitespace)) -// CHECK-NEXT: (CXComment_BlockCommand CommandName=[throw] -// CHECK-NEXT: (CXComment_Paragraph -// CHECK-NEXT: (CXComment_Text Text=[ Bbb.]))))] +// CHECK-NEXT: (CXComment_BlockCommand CommandName=[throw] Arg[0]=Bbb. +// CHECK-NEXT: (CXComment_Paragraph IsWhitespace)))] /// Aaa. /// \exception Bbb. void comment_to_xml_conversion_exceptions_3(); -// CHECK: comment-to-html-xml-conversion.cpp:[[@LINE-1]]:6: FunctionDecl=comment_to_xml_conversion_exceptions_3:{{.*}} FullCommentAsXML=[comment_to_xml_conversion_exceptions_3c:@F@comment_to_xml_conversion_exceptions_3#void comment_to_xml_conversion_exceptions_3() Aaa. Bbb.] +// CHECK: comment-to-html-xml-conversion.cpp:[[@LINE-1]]:6: FunctionDecl=comment_to_xml_conversion_exceptions_3:{{.*}} FullCommentAsXML=[comment_to_xml_conversion_exceptions_3c:@F@comment_to_xml_conversion_exceptions_3#void comment_to_xml_conversion_exceptions_3() Aaa. Bbb. ] // CHECK-NEXT: CommentAST=[ // CHECK-NEXT: (CXComment_FullComment // CHECK-NEXT: (CXComment_Paragraph // CHECK-NEXT: (CXComment_Text Text=[ Aaa.] HasTrailingNewline) // CHECK-NEXT: (CXComment_Text Text=[ ] IsWhitespace)) -// CHECK-NEXT: (CXComment_BlockCommand CommandName=[exception] -// CHECK-NEXT: (CXComment_Paragraph -// CHECK-NEXT: (CXComment_Text Text=[ Bbb.]))))] +// CHECK-NEXT: (CXComment_BlockCommand CommandName=[exception] Arg[0]=Bbb. +// CHECK-NEXT: (CXComment_Paragraph IsWhitespace)))] /// Aaa. /// \throws Bbb. /// \throws Ccc. /// \throws Ddd. void comment_to_xml_conversion_exceptions_4(); -// CHECK: comment-to-html-xml-conversion.cpp:[[@LINE-1]]:6: FunctionDecl=comment_to_xml_conversion_exceptions_4:{{.*}} FullCommentAsXML=[comment_to_xml_conversion_exceptions_4c:@F@comment_to_xml_conversion_exceptions_4#void comment_to_xml_conversion_exceptions_4() Aaa. Bbb. Ccc. Ddd.] +// CHECK: comment-to-html-xml-conversion.cpp:[[@LINE-1]]:6: FunctionDecl=comment_to_xml_conversion_exceptions_4:{{.*}} FullCommentAsXML=[comment_to_xml_conversion_exceptions_4c:@F@comment_to_xml_conversion_exceptions_4#void comment_to_xml_conversion_exceptions_4() Aaa. Bbb. Ccc. Ddd. ] // CHECK-NEXT: CommentAST=[ // CHECK-NEXT: (CXComment_FullComment // CHECK-NEXT: (CXComment_Paragraph // CHECK-NEXT: (CXComment_Text Text=[ Aaa.] HasTrailingNewline) // CHECK-NEXT: (CXComment_Text Text=[ ] IsWhitespace)) -// CHECK-NEXT: (CXComment_BlockCommand CommandName=[throws] -// CHECK-NEXT: (CXComment_Paragraph -// CHECK-NEXT: (CXComment_Text Text=[ Bbb.] HasTrailingNewline) -// CHECK-NEXT: (CXComment_Text Text=[ ] IsWhitespace))) -// CHECK-NEXT: (CXComment_BlockCommand CommandName=[throws] -// CHECK-NEXT: (CXComment_Paragraph -// CHECK-NEXT: (CXComment_Text Text=[ Ccc.] HasTrailingNewline) -// CHECK-NEXT: (CXComment_Text Text=[ ] IsWhitespace))) -// CHECK-NEXT: (CXComment_BlockCommand CommandName=[throws] -// CHECK-NEXT: (CXComment_Paragraph -// CHECK-NEXT: (CXComment_Text Text=[ Ddd.]))))] +// CHECK-NEXT: (CXComment_BlockCommand CommandName=[throws] Arg[0]=Bbb. +// CHECK-NEXT: (CXComment_Paragraph IsWhitespace)) +// CHECK-NEXT: (CXComment_BlockCommand CommandName=[throws] Arg[0]=Ccc. +// CHECK-NEXT: (CXComment_Paragraph IsWhitespace)) +// CHECK-NEXT: (CXComment_BlockCommand CommandName=[throws] Arg[0]=Ddd. +// CHECK-NEXT: (CXComment_Paragraph IsWhitespace)))] /// Aaa. /// \throws Bbb. /// \throw Ccc. void comment_to_xml_conversion_exceptions_5(); -// CHECK: comment-to-html-xml-conversion.cpp:[[@LINE-1]]:6: FunctionDecl=comment_to_xml_conversion_exceptions_5:{{.*}} FullCommentAsXML=[comment_to_xml_conversion_exceptions_5c:@F@comment_to_xml_conversion_exceptions_5#void comment_to_xml_conversion_exceptions_5() Aaa. Bbb. Ccc.] +// CHECK: comment-to-html-xml-conversion.cpp:[[@LINE-1]]:6: FunctionDecl=comment_to_xml_conversion_exceptions_5:{{.*}} FullCommentAsXML=[comment_to_xml_conversion_exceptions_5c:@F@comment_to_xml_conversion_exceptions_5#void comment_to_xml_conversion_exceptions_5() Aaa. Bbb. Ccc. ] +// CHECK-NEXT: CommentAST=[ +// CHECK-NEXT: (CXComment_FullComment +// CHECK-NEXT: (CXComment_Paragraph +// CHECK-NEXT: (CXComment_Text Text=[ Aaa.] HasTrailingNewline) +// CHECK-NEXT: (CXComment_Text Text=[ ] IsWhitespace)) +// CHECK-NEXT: (CXComment_BlockCommand CommandName=[throws] Arg[0]=Bbb. +// CHECK-NEXT: (CXComment_Paragraph IsWhitespace)) +// CHECK-NEXT: (CXComment_BlockCommand CommandName=[throw] Arg[0]=Ccc. +// CHECK-NEXT: (CXComment_Paragraph IsWhitespace)))] + +/// Aaa. +/// \throws Bbb subsequent arg text +void comment_to_xml_conversion_exceptions_6(); +// CHECK: comment-to-html-xml-conversion.cpp:[[@LINE-1]]:6: FunctionDecl=comment_to_xml_conversion_exceptions_6:{{.*}} FullCommentAsXML=[comment_to_xml_conversion_exceptions_6c:@F@comment_to_xml_conversion_exceptions_6#void comment_to_xml_conversion_exceptions_6() Aaa. Bbb subsequent arg text] // CHECK-NEXT: CommentAST=[ // CHECK-NEXT: (CXComment_FullComment // CHECK-NEXT: (CXComment_Paragraph // CHECK-NEXT: (CXComment_Text Text=[ Aaa.] HasTrailingNewline) // CHECK-NEXT: (CXComment_Text Text=[ ] IsWhitespace)) -// CHECK-NEXT: (CXComment_BlockCommand CommandName=[throws] +// CHECK-NEXT: (CXComment_BlockCommand CommandName=[throws] Arg[0]=Bbb // CHECK-NEXT: (CXComment_Paragraph -// CHECK-NEXT: (CXComment_Text Text=[ Bbb.] HasTrailingNewline) +// CHECK-NEXT: (CXComment_Text Text=[subsequent arg text]))))] + +/// Aaa. +/// \throws Bbb subsequent arg text +/// \throw Ccc subsequent arg text +void comment_to_xml_conversion_exceptions_7(); +// CHECK: comment-to-html-xml-conversion.cpp:[[@LINE-1]]:6: FunctionDecl=comment_to_xml_conversion_exceptions_7:{{.*}} FullCommentAsXML=[comment_to_xml_conversion_exceptions_7c:@F@comment_to_xml_conversion_exceptions_7#void comment_to_xml_conversion_exceptions_7() Aaa. Bbb subsequent arg text Ccc subsequent arg text] +// CHECK-NEXT: CommentAST=[ +// CHECK-NEXT: (CXComment_FullComment +// CHECK-NEXT: (CXComment_Paragraph +// CHECK-NEXT: (CXComment_Text Text=[ Aaa.] HasTrailingNewline) +// CHECK-NEXT: (CXComment_Text Text=[ ] IsWhitespace)) +// CHECK-NEXT: (CXComment_BlockCommand CommandName=[throws] Arg[0]=Bbb +// CHECK-NEXT: (CXComment_Paragraph +// CHECK-NEXT: (CXComment_Text Text=[subsequent arg text] HasTrailingNewline) // CHECK-NEXT: (CXComment_Text Text=[ ] IsWhitespace))) -// CHECK-NEXT: (CXComment_BlockCommand CommandName=[throw] +// CHECK-NEXT: (CXComment_BlockCommand CommandName=[throw] Arg[0]=Ccc // CHECK-NEXT: (CXComment_Paragraph -// CHECK-NEXT: (CXComment_Text Text=[ Ccc.]))))] +// CHECK-NEXT: (CXComment_Text Text=[subsequent arg text]))))] #endif - diff --git a/clang/unittests/AST/CommentParser.cpp b/clang/unittests/AST/CommentParser.cpp index c3479672ae2a3c..1c57c899f90745 100644 --- a/clang/unittests/AST/CommentParser.cpp +++ b/clang/unittests/AST/CommentParser.cpp @@ -1427,8 +1427,220 @@ TEST_F(CommentParserTest, Deprecated) { } } +TEST_F(CommentParserTest, ThrowsCommandHasArg1) { + const char *Sources[] = { + "/// @throws int This function throws an integer", + ("/// @throws\n" + "/// int This function throws an integer"), + ("/// @throws \n" + "/// int This function throws an integer"), + ("/// @throws\n" + "/// int\n" + "/// This function throws an integer"), + ("/// @throws \n" + "/// int \n" + "/// This function throws an integer"), + }; + + for (size_t i = 0, e = std::size(Sources); i != e; i++) { + FullComment *FC = parseString(Sources[i]); + ASSERT_TRUE(HasChildCount(FC, 2)); + + ASSERT_TRUE(HasParagraphCommentAt(FC, 0, " ")); + { + BlockCommandComment *BCC; + ParagraphComment *PC; + ASSERT_TRUE(HasBlockCommandAt(FC, Traits, 1, BCC, "throws", PC)); + ASSERT_TRUE(HasChildCount(PC, 1)); + ASSERT_TRUE(BCC->getNumArgs() == 1); + ASSERT_TRUE(BCC->getArgText(0) == "int"); + } + } +} + +TEST_F(CommentParserTest, ThrowsCommandHasArg2) { + const char *Sources[] = { + "/// @throws int** This function throws a double pointer to an integer", + }; + + for (size_t i = 0, e = std::size(Sources); i != e; i++) { + FullComment *FC = parseString(Sources[i]); + ASSERT_TRUE(HasChildCount(FC, 2)); + + ASSERT_TRUE(HasParagraphCommentAt(FC, 0, " ")); + { + BlockCommandComment *BCC; + ParagraphComment *PC; + ASSERT_TRUE(HasBlockCommandAt(FC, Traits, 1, BCC, "throws", PC)); + ASSERT_TRUE(HasChildCount(PC, 1)); + ASSERT_TRUE(BCC->getNumArgs() == 1); + ASSERT_TRUE(BCC->getArgText(0) == "int**"); + } + } +} + +TEST_F(CommentParserTest, ThrowsCommandHasArg3) { + const char *Sources[] = { + "/// @throws Error error of type Error", + }; + + for (size_t i = 0, e = std::size(Sources); i != e; i++) { + FullComment *FC = parseString(Sources[i]); + ASSERT_TRUE(HasChildCount(FC, 2)); + + ASSERT_TRUE(HasParagraphCommentAt(FC, 0, " ")); + { + BlockCommandComment *BCC; + ParagraphComment *PC; + ASSERT_TRUE(HasBlockCommandAt(FC, Traits, 1, BCC, "throws", PC)); + ASSERT_TRUE(HasChildCount(PC, 3)); // Extra children because is parsed + // as a series of TextComments + ASSERT_TRUE(BCC->getNumArgs() == 1); + ASSERT_TRUE(BCC->getArgText(0) == "Error"); + } + } +} + +TEST_F(CommentParserTest, ThrowsCommandHasArg4) { + const char *Sources[] = { + "/// @throws Error> nested templates", + }; + + for (size_t i = 0, e = std::size(Sources); i != e; i++) { + FullComment *FC = parseString(Sources[i]); + ASSERT_TRUE(HasChildCount(FC, 2)); + + ASSERT_TRUE(HasParagraphCommentAt(FC, 0, " ")); + { + BlockCommandComment *BCC; + ParagraphComment *PC; + ASSERT_TRUE(HasBlockCommandAt(FC, Traits, 1, BCC, "throws", PC)); + ASSERT_TRUE(HasChildCount(PC, 1)); + ASSERT_TRUE(BCC->getNumArgs() == 1); + ASSERT_TRUE(BCC->getArgText(0) == "Error>"); + } + } +} + +TEST_F(CommentParserTest, ThrowsCommandHasArg5) { + const char *Sources[] = { + "/// @throws Error variadic templates", + }; + + for (size_t i = 0, e = std::size(Sources); i != e; i++) { + FullComment *FC = parseString(Sources[i]); + ASSERT_TRUE(HasChildCount(FC, 2)); + + ASSERT_TRUE(HasParagraphCommentAt(FC, 0, " ")); + { + BlockCommandComment *BCC; + ParagraphComment *PC; + ASSERT_TRUE(HasBlockCommandAt(FC, Traits, 1, BCC, "throws", PC)); + ASSERT_TRUE(HasChildCount(PC, 1)); + ASSERT_TRUE(BCC->getNumArgs() == 1); + ASSERT_TRUE(BCC->getArgText(0) == "Error"); + } + } +} + +TEST_F(CommentParserTest, ThrowsCommandHasArg6) { + const char *Sources[] = { + "/// @throws Foo<(1 > 0)> typo1", + }; + + for (size_t i = 0, e = std::size(Sources); i != e; i++) { + FullComment *FC = parseString(Sources[i]); + ASSERT_TRUE(HasChildCount(FC, 2)); + + ASSERT_TRUE(HasParagraphCommentAt(FC, 0, " ")); + { + BlockCommandComment *BCC; + ParagraphComment *PC; + ASSERT_TRUE(HasBlockCommandAt(FC, Traits, 1, BCC, "throws", PC)); + ASSERT_TRUE(HasChildCount(PC, 1)); + ASSERT_TRUE(BCC->getNumArgs() == 1); + ASSERT_TRUE(BCC->getArgText(0) == "Foo<(1 >"); + } + } +} + +// No matter the number of (unmatched) opening brackets, no type is parsed. +TEST_F(CommentParserTest, ThrowsCommandHasArg7) { + const char *Sources[] = { + "/// @throws Foo<", + "/// @throws Foo<<<", + "/// @throws Foo<<<<<<<", + "/// @throws Foo<\n", + "/// @throws Foo<<<\n", + "/// @throws Foo<<<<<<<\n", + }; + + for (size_t i = 0, e = std::size(Sources); i != e; i++) { + FullComment *FC = parseString(Sources[i]); + ASSERT_TRUE(HasChildCount(FC, 2)); + + ASSERT_TRUE(HasParagraphCommentAt(FC, 0, " ")); + { + BlockCommandComment *BCC; + ParagraphComment *PC; + ASSERT_TRUE(HasBlockCommandAt(FC, Traits, 1, BCC, "throws", PC)); + ASSERT_TRUE(HasChildCount(PC, 0)); + ASSERT_TRUE(BCC->getNumArgs() == 0); + } + } +} + +// Types with a non-matching closing bracket are parsed as if they are a type +TEST_F(CommentParserTest, ThrowsCommandHasArg8) { + const char *Sources[] = { + "/// @throws Foo>", + "/// @throws Foo>\n", + }; + + for (size_t i = 0, e = std::size(Sources); i != e; i++) { + FullComment *FC = parseString(Sources[i]); + ASSERT_TRUE(HasChildCount(FC, 2)); + + ASSERT_TRUE(HasParagraphCommentAt(FC, 0, " ")); + { + BlockCommandComment *BCC; + ParagraphComment *PC; + ASSERT_TRUE(HasBlockCommandAt(FC, Traits, 1, BCC, "throws", PC)); + ASSERT_TRUE(HasChildCount(PC, 0)); + ASSERT_TRUE(BCC->getNumArgs() == 1); + ASSERT_TRUE(BCC->getArgText(0) == "Foo>"); + } + } +} + +// Everying up until the end of the paragraph comment will be +// eaten up if the template sequence is unterminated (i.e. number of +// opening and closing brackets is not equal). +TEST_F(CommentParserTest, ThrowsCommandHasArg9) { + const char *Sources[] = { + "/// @throws Foo\n" + "/// Aaa\n" + "///\n" + "/// Bbb\n" + }; + + for (size_t i = 0, e = std::size(Sources); i != e; i++) { + FullComment *FC = parseString(Sources[i]); + ASSERT_TRUE(HasChildCount(FC, 3)); + + ASSERT_TRUE(HasParagraphCommentAt(FC, 0, " ")); + { + BlockCommandComment *BCC; + ParagraphComment *PC; + ASSERT_TRUE(HasBlockCommandAt(FC, Traits, 1, BCC, "throws", PC)); + ASSERT_TRUE(HasChildCount(PC, 0)); + ASSERT_TRUE(BCC->getNumArgs() == 0); + } + } +} + + } // unnamed namespace } // end namespace comments } // end namespace clang -